import copy import json import subprocess import sys import unittest from pathlib import Path try: from jsonschema import Draft202012Validator except ModuleNotFoundError: # pragma: no cover - optional test helper dependency Draft202012Validator = None from app.services.case_worksheet_rubric import load_rubric, validate_rubric from app.session_read_model import case_worksheet_template_item_keys REPO_ROOT = Path(__file__).resolve().parents[3] RUBRIC_PATH = REPO_ROOT / "data" / "rubrics" / "case-worksheet-rubric.json" SCHEMA_PATH = REPO_ROOT / "data" / "rubrics" / "case-worksheet-rubric.schema.json" SCRIPT_PATH = REPO_ROOT / "scripts" / "check-case-worksheet-rubric.py" class CaseWorksheetRubricTests(unittest.TestCase): def test_scaffold_matches_generated_worksheet_keys_without_enabling_scoring(self) -> None: rubric = load_rubric(RUBRIC_PATH) report = validate_rubric(rubric, expected_item_keys=case_worksheet_template_item_keys()) self.assertTrue(report["passed"], report) self.assertEqual(report["schema_version"], "vignette.case_worksheet_rubric.v1") self.assertEqual(report["status"], "scaffold_only") self.assertFalse(report["scoring_enabled"]) self.assertEqual(report["sections_total"], 5) self.assertEqual(report["items_total"], 28) self.assertGreaterEqual(len(report["warnings"]), 20) self._validate_with_schema(rubric, SCHEMA_PATH) def test_scoring_requires_clinical_approval(self) -> None: rubric = load_rubric(RUBRIC_PATH) draft = copy.deepcopy(rubric) draft["status"] = "draft" draft["scoring_enabled"] = True report = validate_rubric(draft, expected_item_keys=case_worksheet_template_item_keys()) self.assertFalse(report["passed"]) self.assertIn("scoring_enabled requires status=approved", report["errors"]) def test_missing_worksheet_item_fails_validation(self) -> None: rubric = load_rubric(RUBRIC_PATH) broken = copy.deepcopy(rubric) broken["sections"][0]["items"] = broken["sections"][0]["items"][1:] report = validate_rubric(broken, expected_item_keys=case_worksheet_template_item_keys()) self.assertFalse(report["passed"]) self.assertIn("exploration_11: missing worksheet item: presenting_complaint", report["errors"]) def test_cli_reports_json(self) -> None: completed = subprocess.run( [ sys.executable, "-X", "utf8", str(SCRIPT_PATH), "--rubric", str(RUBRIC_PATH), "--json", ], cwd=str(REPO_ROOT), check=True, capture_output=True, text=True, encoding="utf-8", ) report = json.loads(completed.stdout) self.assertTrue(report["passed"], report) self.assertEqual(report["items_total"], 28) self.assertFalse(report["scoring_enabled"]) self.assertEqual(report["rubric_path"], "data/rubrics/case-worksheet-rubric.json") self.assertRegex(report["content_sha256"], r"^[a-f0-9]{64}$") def _validate_with_schema(self, instance: object, schema_path: Path) -> None: schema = json.loads(schema_path.read_text(encoding="utf-8")) self.assertEqual(schema.get("$schema"), "https://json-schema.org/draft/2020-12/schema") if Draft202012Validator is None: return Draft202012Validator.check_schema(schema) Draft202012Validator(schema).validate(instance) if __name__ == "__main__": unittest.main()