2026-04-03 08:45:19 +03:00
|
|
|
|
"""Главная страница и изолированные UI модулей."""
|
|
|
|
|
|
|
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_root_html_includes_module_cards(client: TestClient) -> None:
|
|
|
|
|
|
r = client.get("/")
|
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
|
body = r.text
|
|
|
|
|
|
assert "Модули" in body
|
|
|
|
|
|
assert "module-card" in body
|
|
|
|
|
|
assert "/ui/modules/schedules/" in body
|
|
|
|
|
|
assert "Календарь дежурств" in body
|
|
|
|
|
|
assert "app-rail" in body
|
|
|
|
|
|
assert "rail-nav" in body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_module_ui_page_schedules(client: TestClient) -> None:
|
|
|
|
|
|
r = client.get("/ui/modules/schedules/")
|
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
|
assert "text/html" in r.headers.get("content-type", "")
|
|
|
|
|
|
assert "Календарь дежурств" in r.text
|
|
|
|
|
|
assert "app-rail" in r.text
|
|
|
|
|
|
assert 'aria-current="page"' in r.text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_rail_lists_all_registered_ui_modules(client: TestClient) -> None:
|
|
|
|
|
|
"""Правое меню синхронизировано с реестром: все модули с ui_router."""
|
|
|
|
|
|
r = client.get("/")
|
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
|
t = r.text
|
|
|
|
|
|
expected = (
|
2026-04-03 13:53:19 +03:00
|
|
|
|
("grafana-catalog", "Каталог Grafana"),
|
2026-04-03 09:03:16 +03:00
|
|
|
|
("incidents", "Инциденты"),
|
|
|
|
|
|
("tasks", "Задачи"),
|
|
|
|
|
|
("escalations", "Эскалации"),
|
2026-04-03 08:45:19 +03:00
|
|
|
|
("schedules", "Календарь дежурств"),
|
|
|
|
|
|
("contacts", "Контакты"),
|
|
|
|
|
|
("statusboard", "Светофор"),
|
|
|
|
|
|
)
|
|
|
|
|
|
for slug, title in expected:
|
|
|
|
|
|
assert f"/ui/modules/{slug}/" in t
|
|
|
|
|
|
assert title in t
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_each_module_page_single_active_nav_item(client: TestClient) -> None:
|
|
|
|
|
|
"""На странице модуля ровно один пункт с aria-current (текущий раздел)."""
|
2026-04-03 09:03:16 +03:00
|
|
|
|
for slug in (
|
2026-04-03 13:53:19 +03:00
|
|
|
|
"grafana-catalog",
|
2026-04-03 09:03:16 +03:00
|
|
|
|
"incidents",
|
|
|
|
|
|
"tasks",
|
|
|
|
|
|
"escalations",
|
|
|
|
|
|
"schedules",
|
|
|
|
|
|
"contacts",
|
|
|
|
|
|
"statusboard",
|
|
|
|
|
|
):
|
2026-04-03 08:45:19 +03:00
|
|
|
|
r = client.get(f"/ui/modules/{slug}/")
|
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
|
assert r.text.count('aria-current="page"') == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_root_survives_broken_module_fragment(client: TestClient) -> None:
|
|
|
|
|
|
"""MODULE_MOUNTS держит ссылки на функции при импорте — ломаем фрагмент через обёртку safe_fragment."""
|
|
|
|
|
|
|
|
|
|
|
|
async def bad_fragment(_request):
|
|
|
|
|
|
raise RuntimeError("simulated module bug")
|
|
|
|
|
|
|
|
|
|
|
|
async def patched_safe_fragment(slug, fn, request):
|
|
|
|
|
|
from onguard24.modules import ui_support as us
|
|
|
|
|
|
|
|
|
|
|
|
if slug == "schedules":
|
|
|
|
|
|
return await us.safe_fragment(slug, bad_fragment, request)
|
|
|
|
|
|
return await us.safe_fragment(slug, fn, request)
|
|
|
|
|
|
|
|
|
|
|
|
with patch("onguard24.root_html.safe_fragment", new=patched_safe_fragment):
|
|
|
|
|
|
r = client.get("/")
|
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
|
assert "module-err" in r.text
|
|
|
|
|
|
assert "schedules" in r.text
|