From 8ef2cc6cdd4560bf85dcda275e072a86a789026c Mon Sep 17 00:00:00 2001 From: yuqei Date: Thu, 30 Apr 2026 23:38:32 -0400 Subject: [PATCH] test: lock training error contract inventory --- .../test_training_public_api_contract_m8.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/tests/unit/test_training_public_api_contract_m8.py b/tests/unit/test_training_public_api_contract_m8.py index d066b39..d0b152f 100644 --- a/tests/unit/test_training_public_api_contract_m8.py +++ b/tests/unit/test_training_public_api_contract_m8.py @@ -57,6 +57,20 @@ PUBLIC_PRIVACY_CONTRACT_TEST_SURFACES = ( "route annotation lifecycle", ) +PUBLIC_VALIDATION_ERROR_CONTRACT_TEST_SURFACES = ( + "invalid path identifiers", + "invalid and blank limit query values", + "malformed/non-object JSON bodies", + "attempt request field validation", + "annotation request field validation", + "submit request field validation", + "import request field validation", + "not_found behavior", + "internal_server_error masking", +) + +VALIDATION_ERROR_CONTRACT_TEST_FILE = ROOT / "tests" / "unit" / "test_training_api_error_contract_m9.py" + PUBLIC_PRIVACY_CONTRACT_TEST_INVENTORY = ( { "surface": "service public export", @@ -114,6 +128,70 @@ PUBLIC_PRIVACY_CONTRACT_TEST_INVENTORY = ( }, ) +PUBLIC_VALIDATION_ERROR_CONTRACT_TEST_INVENTORY = ( + { + "surface": "invalid path identifiers", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ( + "test_invalid_case_path_identifier_returns_public_400", + "test_missing_and_invalid_annotation_fields_return_public_400", + "test_invalid_submit_fields_return_public_400", + ), + }, + { + "surface": "invalid and blank limit query values", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ( + "test_invalid_limits_are_rejected_before_training_handlers_run", + "test_route_request_helper_rejects_blank_training_limits", + ), + }, + { + "surface": "malformed/non-object JSON bodies", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_malformed_or_non_object_bodies_return_public_400",), + }, + { + "surface": "attempt request field validation", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_missing_and_invalid_attempt_fields_return_public_400",), + }, + { + "surface": "annotation request field validation", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_missing_and_invalid_annotation_fields_return_public_400",), + }, + { + "surface": "submit request field validation", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_invalid_submit_fields_return_public_400",), + }, + { + "surface": "import request field validation", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_missing_and_invalid_import_fields_return_public_400",), + }, + { + "surface": "not_found behavior", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_missing_case_returns_public_not_found",), + }, + { + "surface": "internal_server_error masking", + "file": VALIDATION_ERROR_CONTRACT_TEST_FILE, + "class_name": "TrainingApiErrorContractM9Tests", + "test_names": ("test_handler_internal_error_does_not_echo_raw_detail",), + }, +) + class _TrainingRouteProbe: def __init__(self) -> None: @@ -583,6 +661,31 @@ class TrainingPublicApiContractM8Tests(unittest.TestCase): seen_test_names.add(seen_key) self.assertIn(test_name, class_methods[class_name]) + def test_public_validation_error_contract_inventory_names_exist_by_surface(self) -> None: + surfaces = tuple(entry["surface"] for entry in PUBLIC_VALIDATION_ERROR_CONTRACT_TEST_INVENTORY) + self.assertEqual(surfaces, PUBLIC_VALIDATION_ERROR_CONTRACT_TEST_SURFACES) + + parsed_methods_by_path = {} + seen_inventory_entries = set() + for entry in PUBLIC_VALIDATION_ERROR_CONTRACT_TEST_INVENTORY: + path = entry["file"] + class_name = entry["class_name"] + test_names = entry["test_names"] + self.assertGreater(len(test_names), 0, entry["surface"]) + + if path not in parsed_methods_by_path: + self.assertTrue(path.exists(), path) + parsed_methods_by_path[path] = _test_methods_by_class(path) + + class_methods = parsed_methods_by_path[path] + self.assertIn(class_name, class_methods) + for test_name in test_names: + seen_key = (entry["surface"], path, class_name, test_name) + with self.subTest(surface=entry["surface"], test_name=test_name): + self.assertNotIn(seen_key, seen_inventory_entries) + seen_inventory_entries.add(seen_key) + self.assertIn(test_name, class_methods[class_name]) + def test_documented_training_routes_resolve_to_expected_handlers(self) -> None: self.assertEqual(_documented_routes(), DOCUMENTED_TRAINING_ROUTES)