Files
yovision/tests/test_bell_alert_contract.py
T
QiuSW 8208118904
Harness governance / validate (pull_request) Has been cancelled
feat(bell): add alert acknowledgement vertical slice
2026-08-11 17:01:53 +08:00

79 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Static contract checks for the T-020 Bell alert vertical slice."""
from __future__ import annotations
import json
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OPENAPI = ROOT / "docs" / "contracts" / "bell-alert-console-v1.openapi.json"
WEB = ROOT / "Bell" / "web" / "assets"
class BellAlertContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.contract = json.loads(OPENAPI.read_text(encoding="utf-8"))
cls.html = (WEB / "index.html").read_text(encoding="utf-8")
cls.css = (WEB / "style.css").read_text(encoding="utf-8")
cls.js = (WEB / "app.js").read_text(encoding="utf-8")
def test_loopback_routes_and_bounds_are_frozen(self) -> None:
self.assertEqual("3.1.0", self.contract["openapi"])
self.assertTrue(self.contract["servers"][0]["url"].startswith("http://127.0.0.1:"))
paths = self.contract["paths"]
self.assertEqual(
{"/alerts", "/alerts/{alert_id}", "/alerts/{alert_id}:ack", "/alerts/{alert_id}:close"},
set(paths),
)
limit = paths["/alerts"]["get"]["parameters"][1]["schema"]
self.assertEqual({"type": "integer", "minimum": 1, "maximum": 100, "default": 16}, limit)
key = self.contract["components"]["parameters"]["IdempotencyKey"]["schema"]
self.assertEqual((8, 128), (key["minLength"], key["maxLength"]))
def test_alert_is_separate_from_event_and_missing_capabilities_are_explicit(self) -> None:
detail = self.contract["components"]["schemas"]["AlertDetail"]
encoded = json.dumps(detail, ensure_ascii=False)
self.assertIn("events", encoded)
self.assertIn("transitions", encoded)
self.assertIn("evidence_status", encoded)
self.assertIn("delivery_status", encoded)
self.assertIn("证据切片尚未启用", self.html)
self.assertIn("升级与通知尚未启用", self.html)
self.assertNotRegex(self.html + self.js, r"短信已发送|语音已接通|倒计时[::]\s*\d")
def test_console_is_dependency_free_and_does_not_embed_secrets(self) -> None:
combined = self.html + self.css + self.js
self.assertNotRegex(combined, r"https?://|cdn\.|localStorage|sessionStorage")
self.assertNotRegex(combined, r"postgres(?:ql)?://|rtsp://|BELL_DB_DSN")
self.assertIn('type="password"', self.html)
self.assertIn('Cache-Control", "no-store"', (ROOT / "Bell" / "web" / "web.go").read_text(encoding="utf-8"))
def test_accessibility_and_small_screen_contract(self) -> None:
for marker in (
'aria-live="polite"',
'aria-live="assertive"',
'aria-busy="false"',
'aria-pressed="true"',
'href="#alerts"',
):
self.assertIn(marker, self.html)
self.assertIn("@media(max-width:480px)", self.css.replace(" ", ""))
self.assertIn("prefers-reduced-motion:reduce", self.css.replace(" ", ""))
self.assertIn("[hidden]{display:none!important}", self.css.replace(" ", ""))
self.assertRegex(self.css, r"min-height:44px")
self.assertIn("textContent", self.js)
self.assertNotIn("innerHTML", self.js)
def test_console_has_loading_empty_retry_conflict_and_keyboard_paths(self) -> None:
for marker in ("skeleton", "没有预警", "加载失败,重试", "already_acknowledged", "keydown"):
self.assertIn(marker, self.js)
self.assertRegex(self.js, re.escape('limit:"16"'))
if __name__ == "__main__":
unittest.main()