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>
113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
"""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", {})
|