Initial commit: Agentic OS troubleshooting platform

Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,96 @@
"""Tests for agent helpers that need no external services."""
from __future__ import annotations
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.agent.graph import _parse_json, _report_findings, _scope_checked # noqa: E402
from app.agent.playbooks import playbook_for # noqa: E402
from app.models.enums import DeviceType # noqa: E402
from app.services.llm_usage import estimate_cost, merge_run_history # noqa: E402
from app.services.model_router import plan_reasoning_tiers # noqa: E402
from app.services.model_config import LlmRoutingConfig # noqa: E402
def test_parse_json_plain():
assert _parse_json('{"a": 1}') == {"a": 1}
def test_parse_json_fenced():
text = """Here is the result:\n```json\n{"severity": "high", "plan": ["a"]}\n```"""
out = _parse_json(text)
assert out["severity"] == "high"
assert out["plan"] == ["a"]
def test_parse_json_garbage():
assert _parse_json("no json here") == {}
def test_playbook_lookup():
pb = playbook_for(DeviceType.pfsense, None)
assert pb is not None
assert pb["mcp_server"] == "pfsense-mcp"
assert pb["diagnostics"]
def test_playbook_override():
pb = playbook_for(DeviceType.debian, "custom-ssh-mcp")
assert pb["mcp_server"] == "custom-ssh-mcp"
def test_estimate_cost_local():
assert estimate_cost("qwen2.5:7b-instruct", "ollama", 1000, 500) == 0.0
def test_merge_run_history():
prior = [{"run_number": 1, "run_cost_usd": 0.001, "llm_usage": []}]
report = {"summary": "still broken", "resolved": False, "devices_checked": ["proxmox"]}
llm = {"llm_usage": [{"step": "triage", "cost_usd": 0.0}], "run_cost_usd": 0.002}
merged = merge_run_history(prior, 2, report, llm)
assert merged["run_number"] == 2
assert len(merged["runs"]) == 2
assert merged["total_cost_usd"] == 0.003
def test_plan_reasoning_local_first():
tiers, reason = plan_reasoning_tiers(
{"severity": "low", "needs_cloud_reasoning": False, "recommended_tier": "local"},
[],
cfg=LlmRoutingConfig(),
)
assert tiers == ["local"]
assert "local" in reason.lower()
def test_plan_reasoning_escalates_critical():
tiers, _ = plan_reasoning_tiers(
{"severity": "critical", "needs_cloud_reasoning": True, "recommended_tier": "premium"},
[{"ok": False}, {"ok": False}],
cfg=LlmRoutingConfig(),
)
assert tiers == ["local", "economy", "premium"]
def test_scope_checked_tracks_tools_per_device():
devices = [{"catalog_key": "proxmox", "name": "PVE"}, {"catalog_key": "pfsense", "name": "Firewall"}]
diagnostics = [
{"device": "proxmox", "tool": "proxmox_list_qemu", "ok": True},
{"device": "pfsense", "tool": "pfsense_get_system_status", "ok": True},
]
scope = _scope_checked(devices, diagnostics)
assert scope[0]["device"] == "proxmox"
assert scope[0]["checks"] == ["proxmox_list_qemu"]
assert scope[1]["device"] == "pfsense"
def test_report_findings_does_not_invent_unchecked_devices():
findings = _report_findings(
{"summary": "done", "resolution": "No critical issue found."},
[{"device": "proxmox", "tool": "proxmox_list_qemu", "ok": True}],
["proxmox"],
)
assert "proxmox" in findings[0]["summary"]
assert "pfsense" not in findings[0]["summary"].lower()

View File

@@ -0,0 +1,43 @@
"""Tests for approval filtering."""
from app.services.approval_filter import fix_requires_approval, split_proposed_fixes
def test_connect_tools_never_require_approval():
fix = {
"description": "Connect to Proxmox",
"is_config_change": True,
"tool": "proxmox_connect",
"args": {},
}
assert fix_requires_approval(fix) is False
def test_manual_advice_never_requires_approval():
fix = {
"description": "Install Docker on the VM",
"is_config_change": True,
"tool": "manual-config-change",
}
assert fix_requires_approval(fix) is False
def test_write_tool_requires_approval():
fix = {
"description": "Add NAT rule for SIP",
"is_config_change": True,
"tool": "pfsense_create_firewall_rule",
"args": {"descr": "mcp:sip"},
}
assert fix_requires_approval(fix) is True
def test_split_proposed_fixes():
fixes = [
{"description": "connect", "is_config_change": True, "tool": "pfsense_connect"},
{"description": "add rule", "is_config_change": True, "tool": "pfsense_create_firewall_rule"},
{"description": "note", "is_config_change": False},
]
approval, info = split_proposed_fixes(fixes)
assert len(approval) == 1
assert approval[0]["tool"] == "pfsense_create_firewall_rule"
assert len(info) == 2

View File

@@ -0,0 +1,60 @@
"""Tests for Asterisk deployment profiles."""
from __future__ import annotations
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.agent.asterisk_profiles import ( # noqa: E402
_docker_asterisk,
_ssh_connect,
)
from app.agent.playbooks import playbook_for # noqa: E402
from app.models.enums import DeviceType # noqa: E402
from app.services.vessel_devices import validate_selections # noqa: E402
from app.schemas import VesselDeviceSelection # noqa: E402
def test_geneseasx_ssh_connect_uses_voip_port_default():
os.environ["DEVICE_ASTERISK_GENESEASX_PORT"] = "6022"
args = _ssh_connect(
{"address": "10.0.0.1", "username": "root", "secret": "pw", "catalog_key": "asterisk_geneseasx"}
)
assert args["port"] == 6022
assert args["password"] == "pw"
def test_docker_asterisk_command_uses_voip_container():
cmd = _docker_asterisk("pjsip show registrations")(
{"asterisk_container": "voip", "catalog_key": "asterisk_geneseasx"}
)
assert "docker exec voip asterisk -rx" in cmd["command"]
assert "pjsip show registrations" in cmd["command"]
def test_playbook_geneseasx_uses_ssh_mcp():
pb = playbook_for(DeviceType.asterisk, None, "asterisk_geneseasx")
assert pb is not None
assert pb["mcp_server"] == "ssh-generic-mcp"
assert pb["connect"][0] == "ssh_connect"
def test_playbook_satbox_uses_ssh_mcp():
pb = playbook_for(DeviceType.asterisk, None, "asterisk_satbox")
assert pb is not None
assert pb["mcp_server"] == "ssh-generic-mcp"
assert pb["connect"][0] == "ssh_connect"
def test_only_one_asterisk_slot_per_vessel():
sels = [
VesselDeviceSelection(catalog_key="asterisk_geneseasx", enabled=True),
VesselDeviceSelection(catalog_key="asterisk_satbox", enabled=True),
]
try:
validate_selections(sels)
raised = False
except ValueError as exc:
raised = True
assert "one Asterisk" in str(exc)
assert raised

View File

@@ -0,0 +1,49 @@
"""Unit tests for core security primitives (no external services required)."""
from __future__ import annotations
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.core.crypto import decrypt_secret, encrypt_secret # noqa: E402
from app.core.security import ( # noqa: E402
create_access_token,
decode_access_token,
hash_password,
verify_password,
)
from app.models.enums import Role # noqa: E402
def test_password_hash_roundtrip():
h = hash_password("s3cret!")
assert h != "s3cret!"
assert verify_password("s3cret!", h)
assert not verify_password("wrong", h)
def test_jwt_roundtrip():
token = create_access_token(subject="admin@agentic.local", role="admin")
payload = decode_access_token(token)
assert payload is not None
assert payload["sub"] == "admin@agentic.local"
assert payload["role"] == "admin"
def test_jwt_invalid():
assert decode_access_token("not-a-token") is None
def test_credential_encryption_roundtrip():
token = encrypt_secret("device-password")
assert token is not None
assert token != "device-password"
assert decrypt_secret(token) == "device-password"
assert encrypt_secret(None) is None
assert decrypt_secret(None) is None
def test_role_ranking():
from app.core.deps import _ROLE_RANK
assert _ROLE_RANK[Role.admin] > _ROLE_RANK[Role.support] > _ROLE_RANK[Role.readonly]

View File

@@ -0,0 +1,68 @@
"""Tests for device default credentials from env."""
from __future__ import annotations
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
os.environ["DEVICE_SSH_USERNAME"] = "shipadmin"
os.environ["DEVICE_SSH_PASSWORD"] = "ssh-secret"
os.environ["DEVICE_PFSENSE_PORT"] = "40443"
os.environ["DEVICE_PFSENSE_API_KEY"] = "pf-key-123"
os.environ["DEVICE_PROXMOX_PASSWORD"] = "pve-pass"
os.environ["DEVICE_PROXMOX_TOKEN_ID"] = "root@pam!agentic"
os.environ["DEVICE_PROXMOX_TOKEN_SECRET"] = "token-uuid"
from app.inventory_catalog import catalog_by_key # noqa: E402
from app.services.device_defaults import ( # noqa: E402
merge_selection_with_defaults,
proxmox_api_configured,
resolve_proxmox_api_defaults,
resolve_slot_defaults,
)
def test_ssh_slot_uses_global_fallback():
entry = catalog_by_key()["docker_vm"]
d = resolve_slot_defaults("docker_vm", entry)
assert d.username == "shipadmin"
assert d.secret == "ssh-secret"
assert d.secret_kind == "password"
def test_per_slot_overrides_global():
entry = catalog_by_key()["proxmox"]
d = resolve_slot_defaults("proxmox", entry)
assert d.secret == "pve-pass"
assert d.username == "shipadmin"
def test_api_key_slot():
entry = catalog_by_key()["pfsense"]
d = resolve_slot_defaults("pfsense", entry)
assert d.port == 40443
assert d.secret == "pf-key-123"
assert d.secret_kind == "api_key"
def test_merge_applies_env_when_blank():
entry = catalog_by_key()["pfsense"]
port, user, secret = merge_selection_with_defaults(
"pfsense", entry, port=None, username=None, secret=None
)
assert port == 40443
assert secret == "pf-key-123"
def test_proxmox_api_env():
api = resolve_proxmox_api_defaults("10.1.2.3")
assert api["proxmox_token_id"] == "root@pam!agentic"
assert api["proxmox_token_secret"] == "token-uuid"
assert proxmox_api_configured(api)
def test_merge_ui_override_wins():
entry = catalog_by_key()["pfsense"]
_, _, secret = merge_selection_with_defaults(
"pfsense", entry, port=8443, username="admin", secret="custom-key"
)
assert secret == "custom-key"

View File

@@ -0,0 +1,82 @@
"""Tests for diagnostic output formatting."""
from __future__ import annotations
import json
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.services.diagnostic_format import ( # noqa: E402
diagnostics_to_markdown,
format_tool_output_markdown,
prepare_report_diagnostics,
)
def test_firewall_rules_table_from_json():
payload = {
"code": 200,
"data": [
{
"type": "pass",
"interface": ["vtnet2"],
"protocol": "tcp",
"source": "any",
"destination": "GENESEASX_LOCAL_SERVICES",
"descr": "crew to local services",
}
],
}
md = format_tool_output_markdown("pfsense_list_firewall_rules", json.dumps(payload))
assert "| Action |" in md
assert "crew to local services" in md
assert "pass" in md
def test_prepare_report_diagnostics_includes_formatted():
diags = [
{
"device": "pfsense",
"tool": "pfsense_list_firewall_rules",
"ok": True,
"output": json.dumps({"data": [{"type": "pass", "descr": "test rule"}]}),
}
]
out = prepare_report_diagnostics(diags)
assert len(out) == 1
assert out[0]["formatted"]
assert "test rule" in out[0]["formatted"]
assert out[0]["raw_available"] is True
assert "output" not in out[0]
def test_diagnostics_to_markdown_section():
diags = prepare_report_diagnostics(
[
{
"device": "pfsense",
"tool": "pfsense_list_interfaces",
"ok": True,
"output": json.dumps({"data": [{"id": "wan", "if": "vtnet0", "descr": "WAN"}]}),
}
]
)
md = diagnostics_to_markdown(diags)
assert "### [pfsense] pfsense_list_interfaces" in md
assert "vtnet0" in md
def test_prepare_report_diagnostics_fallback_output_is_clipped():
out = prepare_report_diagnostics(
[
{
"device": "docker",
"tool": "unknown_tool",
"ok": True,
"output": "x" * 5000,
}
]
)
assert out[0]["raw_available"] is True
assert out[0]["truncated"] is True
assert len(out[0]["output"]) < 4100

View File

@@ -0,0 +1,45 @@
"""Tests for investigation context gathering."""
from __future__ import annotations
import os
import tempfile
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.services.investigation_context import ( # noqa: E402
_score_obsidian_file,
extract_search_keywords,
_obsidian_title,
)
def test_extract_search_keywords_from_issue():
terms = extract_search_keywords("Asterisk health status on GeneseasX voip container")
assert "asterisk" in terms
assert "geneseasx" in terms
assert "voip" in terms
assert "health" not in terms # stopword
def test_obsidian_title_from_markdown():
text = "# Asterisk health status\n\nSome body"
assert _obsidian_title(text, "2026-06-13-asterisk.md") == "Asterisk health status"
def test_memory_vessel_from_tags():
from app.services.investigation_context import _memory_vessel_from_hit, _obsidian_vessel_from_text
hit = {"tags": ["agentic-os", "vessel:geneseasx-test"], "content": "", "title": "[Task] foo"}
assert _memory_vessel_from_hit(hit) == "geneseasx test"
text = "---\ntags: [agentic-os]\nvessel: test\nstatus: unresolved\n---\n# Title"
assert _obsidian_vessel_from_text(text) == "test"
def test_score_obsidian_file_matches_keywords():
score = _score_obsidian_file(
"/vault/agentic-os-tasks/2026-06-13-asterisk-health.md",
"Asterisk pjsip registration failed on voip container",
["asterisk", "voip"],
)
assert score >= 3

View File

@@ -0,0 +1,13 @@
from app.agent.mcp_development import is_mcp_bug_or_gap
def test_unknown_tool_is_repairable():
assert is_mcp_bug_or_gap("pfsense_get_firewall_log", "unknown tool: pfsense_get_firewall_log", None)
def test_traceback_is_repairable():
assert is_mcp_bug_or_gap("ssh_run", None, "Traceback (most recent call last):")
def test_normal_failure_not_repairable():
assert not is_mcp_bug_or_gap("ssh_run", None, "connection refused")

View File

@@ -0,0 +1,67 @@
"""Tests for tiered model routing."""
from __future__ import annotations
import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.services.model_config import ( # noqa: E402
LlmRoutingConfig,
resolve_tier_model,
resolve_tier_models,
)
from app.services.model_router import plan_reasoning_tiers # noqa: E402
def test_resolve_tier_models_provider_order(monkeypatch):
monkeypatch.setenv("GEMINI_API_KEY", "test-gemini")
monkeypatch.setenv("DEEPSEEK_API_KEY", "test-deepseek")
monkeypatch.setenv("OPENROUTER_API_KEY", "test-openrouter")
from app.config import get_settings
get_settings.cache_clear()
cfg = LlmRoutingConfig(
cloud_provider_order=["gemini", "deepseek", "openrouter"],
)
models = resolve_tier_models(cfg, "economy")
backends = [m.backend for m in models]
assert backends == ["gemini", "deepseek", "openrouter"]
get_settings.cache_clear()
def test_resolve_tier_model_returns_first_provider(monkeypatch):
monkeypatch.setenv("GEMINI_API_KEY", "test-gemini")
monkeypatch.setenv("DEEPSEEK_API_KEY", "test-deepseek")
from app.config import get_settings
get_settings.cache_clear()
cfg = LlmRoutingConfig(cloud_provider_order=["gemini", "deepseek", "openrouter"])
entry = resolve_tier_model(cfg, "economy")
assert entry is not None
assert entry.backend == "gemini"
get_settings.cache_clear()
def test_plan_reasoning_local_first():
tiers, reason = plan_reasoning_tiers(
{"severity": "low", "needs_cloud_reasoning": False, "recommended_tier": "local"},
[],
cfg=LlmRoutingConfig(),
)
assert tiers == ["local"]
assert "local" in reason.lower()
def test_plan_reasoning_escalates_critical():
tiers, _ = plan_reasoning_tiers(
{"severity": "critical", "needs_cloud_reasoning": True, "recommended_tier": "premium"},
[{"ok": False}, {"ok": False}],
cfg=LlmRoutingConfig(),
)
assert tiers == ["local", "economy", "premium"]

View File

@@ -0,0 +1,24 @@
"""Tests for pfSense issue-aware diagnostics."""
from app.agent.pfsense_profiles import (
diagnostics_for_issue,
wants_firewall_inventory,
)
def test_wants_firewall_inventory():
assert wants_firewall_inventory("list Firewall rules", "policies for all interfaces")
assert wants_firewall_inventory("", "show me NAT port forwards")
def test_default_health_diagnostics():
tools = [t for t, _ in diagnostics_for_issue("Asterisk health", "check voip")]
assert "pfsense_list_firewall_rules" not in tools
assert "pfsense_get_system_status" in tools
def test_firewall_diagnostics():
tools = [t for t, _ in diagnostics_for_issue("list rules", "firewall policies")]
assert tools[0] == "pfsense_list_interfaces"
assert "pfsense_list_firewall_rules" in tools
assert "pfsense_list_port_forwards" in tools
assert "pfsense_list_outbound_nat_mappings" in tools

View File

@@ -0,0 +1,57 @@
"""Tests for Proxmox profile helpers."""
from app.agent.proxmox_profiles import (
_api_guest_inventory_empty,
_first_proxmox_node,
_parse_qemu_vms,
wants_vm_inventory,
)
def test_first_proxmox_node_from_json_list():
out = '[{"node":"genx","status":"online"}]'
assert _first_proxmox_node(out) == "genx"
def test_first_proxmox_node_from_regex():
out = 'nodes: {"node":"pve1"}'
assert _first_proxmox_node(out) == "pve1"
def test_wants_vm_inventory_from_task4_issue():
assert wants_vm_inventory(
"Proxmox VM status",
"Check status of all running VMs and provide config for each VM",
)
def test_parse_qemu_vms_json():
out = '[{"vmid":100,"name":"pfsense","status":"running","cpus":2}]'
vms = _parse_qemu_vms(out)
assert len(vms) == 1
assert vms[0]["vmid"] == 100
def test_parse_qemu_vms_qm_list_text():
out = """ VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
100 pfsense running 4096 32.00 1234
101 debian-docker running 8192 64.00 5678"""
vms = _parse_qemu_vms(out)
assert len(vms) == 2
assert vms[0]["vmid"] == 100
assert vms[0]["status"] == "running"
def test_api_guest_inventory_empty():
results = [
{"tool": "proxmox_list_qemu", "output": "[]"},
{"tool": "proxmox_list_lxc", "output": "[]"},
]
assert _api_guest_inventory_empty(results)
def test_api_guest_inventory_not_empty_when_qemu_has_rows():
results = [
{"tool": "proxmox_list_qemu", "output": '[{"vmid":100,"name":"pfsense","status":"running"}]'},
{"tool": "proxmox_list_lxc", "output": "[]"},
]
assert not _api_guest_inventory_empty(results)

View File

@@ -0,0 +1,40 @@
"""Tests for compact task presentation helpers."""
from __future__ import annotations
import os
from datetime import datetime, timezone
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
from app.models.enums import TaskStatus # noqa: E402
from app.models.task import Task # noqa: E402
from app.services.task_present import task_to_overview # noqa: E402
def test_task_to_overview_omits_heavy_diagnostics():
now = datetime.now(timezone.utc)
task = Task(
id=7,
title="Status",
issue="check proxmox and pfsense",
status=TaskStatus.succeeded,
vessel_id=2,
report={
"executive_summary": "Checked requested systems.",
"findings": [{"title": "No critical issue", "summary": "Both devices answered."}],
"devices_checked": ["proxmox", "pfsense"],
"diagnostics": [{"output": "large raw data"}],
"resolved": True,
"run_cost_usd": 0.0,
},
created_at=now,
updated_at=now,
)
task.memory_id = "mem-1"
task.obsidian_path = "agentic-os-tasks/status.md"
overview = task_to_overview(task, vessel_name="IRINAs")
assert overview.summary == "Checked requested systems."
assert overview.key_finding == "Both devices answered."
assert overview.devices_checked == ["proxmox", "pfsense"]
assert not hasattr(overview, "diagnostics")

View File

@@ -0,0 +1,28 @@
"""Tests for per-task runtime flags."""
from app.services.task_runtime import (
CANCELLABLE_STATUSES,
TaskCancelledError,
apply_min_tier_floor,
)
def test_cancellable_statuses():
assert "running" in CANCELLABLE_STATUSES
assert "queued" in CANCELLABLE_STATUSES
assert "succeeded" not in CANCELLABLE_STATUSES
def test_task_cancelled_error_message():
err = TaskCancelledError("Task cancelled by user")
assert "cancelled" in str(err).lower()
def test_apply_min_tier_floor_skips_local():
assert apply_min_tier_floor(["local", "economy", "premium"], "economy") == [
"economy",
"premium",
]
def test_apply_min_tier_floor_premium():
assert apply_min_tier_floor(["local", "economy"], "premium") == ["premium"]

View File

@@ -0,0 +1,18 @@
from app.agent.tool_events import sanitize_tool_args, tool_event_payload
def test_sanitize_secrets():
safe = sanitize_tool_args({"command": "uptime", "password": "secret123"})
assert safe["command"] == "uptime"
assert safe["password"] == "••••"
def test_tool_event_includes_command():
p = tool_event_payload(
device="docker_vm",
tool="ssh_run",
args={"command": "docker ps"},
status="running",
)
assert p["command"] == "docker ps"
assert p["device"] == "docker_vm"

View File

@@ -0,0 +1,112 @@
"""Tests for troubleshooting decision rules."""
from __future__ import annotations
import os
from pathlib import Path
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only")
_test_dir = Path(__file__).resolve().parent
_backend_root = _test_dir.parent
_rules_file = _backend_root / "rules" / "troubleshooting.yaml"
if not _rules_file.is_file():
_rules_file = _backend_root.parent / "rules" / "troubleshooting.yaml"
os.environ["TROUBLESHOOTING_RULES_PATH"] = str(_rules_file)
from app.services.troubleshooting_rules import ( # noqa: E402
invalidate_rules_cache,
load_rules,
match_rule,
select_devices_for_issue,
)
def _devices():
return [
{"catalog_key": "proxmox", "name": "pve"},
{"catalog_key": "pfsense", "name": "fw"},
{"catalog_key": "docker_vm", "name": "docker"},
{"catalog_key": "asterisk_geneseasx", "name": "voip"},
]
def test_match_no_registration_rule():
invalidate_rules_cache()
rule = match_rule("SIP phones down", "Phone won't register on crew VLAN")
assert rule is not None
assert rule.id == "no-registration"
assert "pfsense" in rule.devices
def test_match_broad_health():
invalidate_rules_cache()
rule = match_rule("Full check", "health check of all systems")
assert rule is not None
assert rule.broad is True
def test_task4_issue_selects_proxmox_only():
"""Scoped VM task must not trigger full-stack sweep."""
invalidate_rules_cache()
selected, rule = select_devices_for_issue(
_devices(),
"Check status of all running VMs and provide config for each VM",
title="Proxmox VM status",
)
keys = [d["catalog_key"] for d in selected]
assert keys == ["proxmox"]
assert rule is None or rule.id == "proxmox-vm-inventory"
def test_multi_device_status_includes_all_named():
"""Issue naming two devices must diagnose both even when a single-device rule matches."""
invalidate_rules_cache()
selected, rule = select_devices_for_issue(
_devices(),
"check status of proxmox and pfsense",
title="Irinas box",
)
keys = [d["catalog_key"] for d in selected]
assert keys == ["proxmox", "pfsense"]
assert rule is not None
assert rule.id == "proxmox-vm-inventory"
def test_unmatched_issue_selects_nothing():
invalidate_rules_cache()
selected, _rule = select_devices_for_issue(
_devices(),
"something completely unrelated with no device keywords",
title="Mystery",
)
assert selected == []
def test_broad_issue_selects_all_devices():
invalidate_rules_cache()
selected, rule = select_devices_for_issue(
_devices(),
"Run a full stack health check on everything",
title="Weekly audit",
)
assert rule is not None
assert rule.broad is True
assert len(selected) == 4
def test_select_devices_order():
invalidate_rules_cache()
selected, rule = select_devices_for_issue(
_devices(),
"one-way audio on SIP calls",
title="VoIP issue",
)
assert rule is not None
assert rule.id == "one-way-audio"
keys = [d["catalog_key"] for d in selected]
assert keys == ["pfsense", "asterisk_geneseasx"]
def test_load_rules_has_device_keywords():
invalidate_rules_cache()
data = load_rules(force=True)
assert "asterisk_geneseasx" in data.get("device_keywords", {})