Files
ai-exchange/tests/unit/test_training_dashboard_m3.py
2026-05-06 22:02:08 +08:00

142 lines
4.9 KiB
Python

import re
import unittest
from html.parser import HTMLParser
from pathlib import Path
WEB_ROOT = Path("src/web")
def _read_web_file(name: str) -> str:
return (WEB_ROOT / name).read_text(encoding="utf-8")
class _MarkupParser(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.elements: list[tuple[str, dict[str, str]]] = []
self.buttons_by_id: dict[str, dict[str, str]] = {}
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
attr_map = {key: value or "" for key, value in attrs}
self.elements.append((tag, attr_map))
if tag == "button" and attr_map.get("id"):
self.buttons_by_id[attr_map["id"]] = attr_map
def _parse_training_html() -> _MarkupParser:
parser = _MarkupParser()
parser.feed(_read_web_file("training.html"))
return parser
class TrainingDashboardM3Tests(unittest.TestCase):
def test_training_shell_is_moved_to_dedicated_page(self) -> None:
index_html = _read_web_file("index.html")
training_html = _read_web_file("training.html")
self.assertNotIn('id="trainingSection"', index_html)
for expected in (
'id="trainingSection"',
'id="trainingCasesList"',
'id="trainingCaseDetail"',
'id="trainingProgress"',
'id="trainingAttemptStatus"',
'id="trainingImportText"',
'id="trainingAnnotationText"',
'/static/training.js',
):
self.assertIn(expected, training_html)
def test_training_page_keeps_public_workflow_sections(self) -> None:
training_html = _read_web_file("training.html")
styles_css = _read_web_file("styles.css")
parser = _parse_training_html()
attrs_by_id = {attrs["id"]: attrs for _tag, attrs in parser.elements if attrs.get("id")}
for expected in (
"Training workflow",
"Import / Export",
"Cases",
"Case Detail",
"Progress",
"Only public training context is shown.",
):
self.assertIn(expected, training_html)
for expected in (
".training-grid",
".training-toolbar",
".training-case-list",
".training-detail",
".training-actions",
".training-error",
):
self.assertIn(expected, styles_css)
self.assertEqual(attrs_by_id["trainingError"].get("role"), "alert")
self.assertEqual(attrs_by_id["trainingCasesList"].get("role"), "list")
self.assertEqual(attrs_by_id["trainingProgress"].get("role"), "list")
self.assertEqual(parser.buttons_by_id["trainingImportBtn"].get("type"), "button")
self.assertEqual(parser.buttons_by_id["trainingSubmitBtn"].get("type"), "button")
def test_training_app_uses_public_route_registry(self) -> None:
training_js = _read_web_file("training.js")
for expected in (
"TRAINING_PUBLIC_ROUTES",
"list: '/training/cases'",
"detail: (caseId) => `/training/cases/${caseId}`",
"export: '/training/cases/export'",
"import: '/training/cases/import'",
"createAttempt: '/training/attempts'",
"annotateAttempt: (attemptId) => `/training/attempts/${attemptId}/annotations`",
"submitAttempt: (attemptId) => `/training/attempts/${attemptId}/submit`",
"progress: '/training/progress'",
"loadTrainingDashboard",
"loadTrainingCaseDetail",
"startTrainingAttempt",
"saveTrainingAnnotation",
"submitTrainingAttempt",
"refreshTrainingProgress",
):
self.assertIn(expected, training_js)
self.assertRegex(training_js, re.compile(r"method:\s*'POST'"))
self.assertIn("annotation_type: 'note'", training_js)
self.assertIn("body: JSON.stringify({})", training_js)
def test_training_frontend_keeps_private_material_out(self) -> None:
web_source = "\n".join(
_read_web_file(name).lower()
for name in ("training.html", "training.js", "styles.css")
)
for forbidden in (
"answer_key",
"solution_key",
"internal solution details",
"private solution material",
"postgresql",
"password",
"secret",
"api_key",
):
self.assertNotIn(forbidden, web_source)
def test_training_optional_controls_are_guarded(self) -> None:
training_js = _read_web_file("training.js")
for expected in (
"if (trainingSaveAnnotationBtn)",
"if (trainingSubmitBtn)",
"if (trainingImportBtn)",
"if (trainingExportBtn)",
"if (trainingReloadBtn)",
"if (trainingRefreshProgressBtn)",
):
self.assertIn(expected, training_js)
if __name__ == "__main__":
unittest.main()