feat: extend operational investigation workflows
Generic Container CI/CD / test-build-publish (push) Successful in 3m53s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 3m52s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent 63af998045
commit 05183aff93
82 changed files with 4644 additions and 330 deletions
+43
View File
@@ -40,6 +40,26 @@ def test_report_ui_round_trips_with_a_version_and_keeps_legacy_reports_readable(
assert InvestigationReportRevision.model_validate(old).report.ui_blocks == []
@pytest.mark.parametrize("phase", [None, "final"])
def test_report_ui_accepts_final_and_legacy_phases(phase):
data = rich_report()
if phase is not None:
data["ui_blocks"][0]["block"]["phase"] = phase
report = InvestigationReportInput.model_validate(data)
assert report.ui_blocks[0].block.phase == phase
serialized = report.model_dump()["ui_blocks"][0]["block"]
assert ("phase" in serialized) == (phase is not None)
assert InvestigationReportInput.model_validate(report.model_dump()).ui_blocks[0].block.phase == phase
@pytest.mark.parametrize("phase", ["process", "unknown", None])
def test_report_ui_rejects_process_and_unknown_phases(phase):
data = rich_report()
data["ui_blocks"][0]["block"]["phase"] = phase
with pytest.raises(ValidationError):
InvestigationReportInput.model_validate(data)
@pytest.mark.parametrize("defect", ["unknown", "script", "unaligned", "nan", "cycle", "unreachable", "duplicate", "foreign", "many", "oversized"])
def test_report_ui_rejects_invalid_or_unbound_content(defect):
data = rich_report()
@@ -72,3 +92,26 @@ def test_report_ui_rejects_invalid_or_unbound_content(defect):
data["ui_blocks"] = [entry, {**deepcopy(entry), "block": {**deepcopy(entry["block"]), "block_id": "second"}}]
with pytest.raises(ValidationError):
InvestigationReportInput.model_validate(data)
def test_report_ui_accepts_semantic_results_without_allowing_raw_presentation_code():
data = rich_report()
spec = data["ui_blocks"][0]["block"]["spec"]
spec["root"] = "layout"
spec["elements"] = {
"layout": {"type": "Stack", "props": {"columns": "one", "gap": "normal", "intent": "report", "density": "compact"}, "children": ["section"]},
"section": {"type": "Section", "props": {"title": "演练结果", "description": None, "role": "result"}, "children": ["result", "note"]},
"result": {"type": "Result", "props": {"title": "演练记录已保存", "summary": "现场处置仍待核验。", "status": "partial", "source": "演练回执"}, "children": []},
"note": {"type": "Text", "props": {"title": "核验说明", "text": "结果仅限演练。", "tone": "neutral"}, "children": []},
}
restored = InvestigationReportInput.model_validate(data)
assert restored.ui_blocks[0].block.spec.elements["result"].props["status"] == "partial"
for extra in ({"style": {"color": "red"}}, {"className": "text-red-500"}, {"css": "display:none"}, {"action": "dispatch"}):
invalid = deepcopy(data)
invalid["ui_blocks"][0]["block"]["spec"]["elements"]["result"]["props"].update(extra)
with pytest.raises(ValidationError):
InvestigationReportInput.model_validate(invalid)
invalid = deepcopy(data)
invalid["ui_blocks"][0]["block"]["spec"]["elements"]["result"]["children"] = ["note"]
with pytest.raises(ValidationError):
InvestigationReportInput.model_validate(invalid)