"""Ensure runtime error_code values stay within the OpenAPI registry.""" from __future__ import annotations import re from pathlib import Path import pytest from app.core.error_codes import ALL_ERROR_CODES, ERROR_CODE_ENUM from app.core.errors import ( _STATUS_TO_ERROR_CODE, AppError, AuthenticationError, AuthorizationError, BadRequestError, ConflictError, GatewayTimeoutError, NotFoundError, ProviderError, QuotaExceededError, RateLimitedError, ServiceUnavailableError, ValidationError, ) from app.features.auth.service_errors import _AUTH_CODE_MAP from app.features.payment.payment_exceptions import _PAYMENT_CODE_MAP _APP_FEATURES_ROOT = Path(__file__).resolve().parents[1] / "app" / "features" _LITERAL_ERROR_CODE_RE = re.compile(r"""error_code\s*=\s*["']([A-Z][A-Z0-9_]*)["']""") def _app_error_subclass_codes() -> set[str]: codes: set[str] = set() for cls in ( NotFoundError, BadRequestError, AuthenticationError, AuthorizationError, ValidationError, ConflictError, ServiceUnavailableError, GatewayTimeoutError, ProviderError, QuotaExceededError, RateLimitedError, ): # Instantiate with defaults to read resolved error_code from AppError base. instance = cls() codes.add(instance.error_code) return codes def _auth_runtime_codes() -> set[str]: return {external for _, external in _AUTH_CODE_MAP.values()} def _payment_runtime_codes() -> set[str]: return {external for _, external in _PAYMENT_CODE_MAP.values()} def _literal_feature_error_codes() -> set[str]: codes: set[str] = set() for path in _APP_FEATURES_ROOT.rglob("*.py"): text = path.read_text(encoding="utf-8") codes.update(_LITERAL_ERROR_CODE_RE.findall(text)) return codes def _runtime_error_codes() -> set[str]: return ( _app_error_subclass_codes() | _auth_runtime_codes() | _payment_runtime_codes() | set(_STATUS_TO_ERROR_CODE.values()) | _literal_feature_error_codes() ) def test_runtime_error_codes_are_registered_in_openapi_enum() -> None: runtime = _runtime_error_codes() registry = set(ERROR_CODE_ENUM) missing = runtime - registry assert not missing, f"Unregistered runtime error_code values: {sorted(missing)}" def test_auth_and_payment_registry_http_status_matches_runtime_maps() -> None: registry_by_code = {entry["code"]: entry for entry in ALL_ERROR_CODES} for internal, (status_code, external) in {**_AUTH_CODE_MAP, **_PAYMENT_CODE_MAP}.items(): if external not in registry_by_code: continue entry = registry_by_code[external] assert entry["http_status"] == status_code, ( f"{internal} maps to {external} with HTTP {status_code}, " f"but registry lists {entry['http_status']}" ) @pytest.mark.parametrize( "error_cls,expected_code", [ (NotFoundError, "NOT_FOUND"), (BadRequestError, "BAD_REQUEST"), (AuthenticationError, "AUTHENTICATION_FAILED"), (AuthorizationError, "FORBIDDEN"), (ValidationError, "VALIDATION_ERROR"), (ConflictError, "CONFLICT"), (ServiceUnavailableError, "SERVICE_UNAVAILABLE"), (GatewayTimeoutError, "GATEWAY_TIMEOUT"), (ProviderError, "PROVIDER_ERROR"), (QuotaExceededError, "QUOTA_EXCEEDED"), (RateLimitedError, "RATE_LIMITED"), ], ) def test_app_error_subclasses_use_registered_codes(error_cls: type[AppError], expected_code: str) -> None: assert error_cls().error_code == expected_code assert expected_code in ERROR_CODE_ENUM