test(observability): validate nav active page contract
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
OoO
2026-05-05 15:48:39 +08:00
parent 3fca720fa1
commit 8643ed12ad
2 changed files with 66 additions and 0 deletions

View File

@@ -30,9 +30,23 @@ TEMPLATE_PATHS = [
"templates/admin/ppt_audit_history.html",
]
OBSERVABILITY_NAV_ITEMS = [
("templates/admin/observability_overview.html", "obs_overview", "/observability/overview", "觀測台總覽"),
("templates/admin/agent_orchestration.html", "obs_agent_orchestration", "/observability/agent_orchestration", "Agent 編排矩陣"),
("templates/admin/business_intel.html", "obs_business_intel", "/observability/business_intel", "商業面 × AI"),
("templates/admin/host_health.html", "obs_host_health", "/observability/host_health", "主機健康"),
("templates/admin/ai_calls_dashboard.html", "obs_ai_calls", "/observability/ai_calls", "AI 呼叫"),
("templates/admin/budget.html", "obs_budget", "/observability/budget", "預算控管"),
("templates/admin/promotion_review.html", "obs_promotion_review", "/observability/promotion_review", "RAG 晉升審核"),
("templates/admin/rag_queries.html", "obs_rag_queries", "/observability/rag_queries", "RAG 召回詳情"),
("templates/admin/quality_trend.html", "obs_quality_trend", "/observability/quality_trend", "反饋趨勢"),
("templates/admin/ppt_audit_history.html", "obs_ppt_audit", "/observability/ppt_audit_history", "PPT 視覺審核"),
]
CSS_PATH = Path("static/css/observability-system.css")
SHELL_PATH = Path("templates/components/_ewoooc_shell.html")
BASE_PATH = Path("templates/ewoooc_base.html")
ROUTE_PATH = Path("routes/admin_observability_routes.py")
@dataclass(frozen=True)
@@ -195,6 +209,55 @@ def scan_shell() -> list[str]:
return findings
def scan_nav_contract() -> list[str]:
shell_path = ROOT / SHELL_PATH
base_path = ROOT / BASE_PATH
route_path = ROOT / ROUTE_PATH
if not shell_path.exists() or not base_path.exists():
return []
shell_text = shell_path.read_text(encoding="utf-8")
base_text = base_path.read_text(encoding="utf-8")
route_text = route_path.read_text(encoding="utf-8") if route_path.exists() else ""
findings: list[str] = []
for template_path, active_page, url, label in OBSERVABILITY_NAV_ITEMS:
template = ROOT / template_path
if not template.exists():
findings.append(f"{template_path}: missing observability template for `{label}`")
continue
template_text = template.read_text(encoding="utf-8")
active_pattern = re.compile(
r"set\s+active_page\s*=\s*['\"]" + re.escape(active_page) + r"['\"]"
)
route_has_contract = template_path.replace("templates/", "") in route_text and active_page in route_text
if not active_pattern.search(template_text) and not route_has_contract:
findings.append(
f"{template_path}: active_page must be `{active_page}` in template or route for `{label}`"
)
if active_page not in shell_text:
findings.append(f"{SHELL_PATH}: sidebar missing active_page `{active_page}`")
if url not in shell_text:
findings.append(f"{SHELL_PATH}: sidebar missing URL `{url}` for `{label}`")
if label not in shell_text:
findings.append(f"{SHELL_PATH}: sidebar missing label `{label}`")
if active_page not in base_text:
findings.append(f"{BASE_PATH}: momo-observability-mode list missing `{active_page}`")
sidebar_observability_links = set(re.findall(r'href="(/observability/[^"]+)"', shell_text))
expected_links = {url for _, _, url, _ in OBSERVABILITY_NAV_ITEMS}
extra_links = sorted(sidebar_observability_links - expected_links)
missing_links = sorted(expected_links - sidebar_observability_links)
for url in extra_links:
findings.append(f"{SHELL_PATH}: unexpected observability sidebar URL `{url}`")
for url in missing_links:
findings.append(f"{SHELL_PATH}: missing observability sidebar URL `{url}`")
return findings
def main() -> int:
findings: list[str] = []
for template_path in TEMPLATE_PATHS:
@@ -202,6 +265,7 @@ def main() -> int:
findings.extend(scan_css())
findings.extend(scan_shell())
findings.extend(scan_required_snippets(BASE_PATH, REQUIRED_BASE_SNIPPETS, "base/topbar"))
findings.extend(scan_nav_contract())
if findings:
print("Observability UI guard: FAIL")
@@ -214,6 +278,7 @@ def main() -> int:
print(f"- css guardrails checked: {len(REQUIRED_CSS_SNIPPETS)}")
print(f"- sidebar/nav guardrails checked: {len(REQUIRED_SHELL_SNIPPETS)}")
print(f"- base/topbar guardrails checked: {len(REQUIRED_BASE_SNIPPETS)}")
print(f"- nav contract checked: {len(OBSERVABILITY_NAV_ITEMS)} pages")
return 0