from __future__ import annotations import copy import sys import unittest from pathlib import Path HERE = Path(__file__).resolve().parent CONTRACTS = HERE.parents[1] EVENT_SUPPORT = HERE.parent / "events-v1" sys.path.insert(0, str(EVENT_SUPPORT)) from contract_support import load_json, validate # noqa: E402 class EvidenceV1ContractTests(unittest.TestCase): schema_path = CONTRACTS / "evidence" / "v1" / "evidence-reference.schema.json" schema = load_json(schema_path) def errors_for(self, payload: object) -> list[str]: return validate(payload, self.schema, self.schema_path) def test_pending_success_and_failed_examples_are_valid(self) -> None: for name in ("pending.json", "success.json", "failed.json"): with self.subTest(name=name): payload = load_json(CONTRACTS / "evidence" / "v1" / "examples" / name) self.assertEqual([], self.errors_for(payload)) def test_state_specific_metadata_is_enforced(self) -> None: success = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "success.json") for required in ("content_type", "integrity"): with self.subTest(success_requires=required): candidate = copy.deepcopy(success) del candidate[required] self.assertTrue(self.errors_for(candidate)) legacy_available = copy.deepcopy(success) legacy_available["status"] = "available" self.assertTrue(self.errors_for(legacy_available)) failed = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "failed.json") del failed["failure"] self.assertTrue(self.errors_for(failed)) pending = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "pending.json") pending["content_type"] = "image/jpeg" self.assertTrue(self.errors_for(pending)) def test_sensitive_access_material_and_unknown_version_are_rejected(self) -> None: fixture = load_json(HERE / "fixtures" / "sensitive-field.json") self.assertTrue(any("local_path" in error for error in self.errors_for(fixture))) base = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "pending.json") for forbidden, value in { "camera_password": "forbidden", "user_token": "forbidden", "signed_url": "https://forbidden.invalid/object?signature=forbidden", "face_feature": "forbidden", "alert_state": "acked" }.items(): with self.subTest(forbidden=forbidden): candidate = copy.deepcopy(base) candidate[forbidden] = value self.assertTrue(self.errors_for(candidate)) unknown = copy.deepcopy(base) unknown["schema_version"] = "yovision.evidence-reference/v2" self.assertTrue(self.errors_for(unknown)) def test_openapi_refs_and_degradation_responses(self) -> None: path = CONTRACTS / "evidence" / "v1" / "openapi.json" spec = load_json(path) operation = spec["paths"]["/v1/evidence/{evidence_id}"]["get"] self.assertEqual({"200", "404", "410"}, set(operation["responses"])) refs: list[str] = [] def collect(value: object) -> None: if isinstance(value, dict): refs.extend(item for key, item in value.items() if key == "$ref") for item in value.values(): collect(item) elif isinstance(value, list): for item in value: collect(item) collect(spec) for ref in refs: self.assertTrue((path.parent / ref).resolve().is_file(), ref) if __name__ == "__main__": unittest.main(verbosity=2)