"""AZ-507 — Layer-0 typed-error envelope shim tests. Covers AC-2 (shim re-exports resolve and are identical to the c7 canonical classes) and AC-1 / AC-5 (module-layout.md + architecture.md documentation invariants). """ from __future__ import annotations import re from pathlib import Path from gps_denied_onboard._types.inference_errors import ( CalibrationCacheError as ShimCalibrationCacheError, ) from gps_denied_onboard._types.inference_errors import ( EngineBuildError as ShimEngineBuildError, ) from gps_denied_onboard.components.c7_inference.errors import ( CalibrationCacheError as CanonicalCalibrationCacheError, ) from gps_denied_onboard.components.c7_inference.errors import ( EngineBuildError as CanonicalEngineBuildError, ) _REPO_ROOT = Path(__file__).resolve().parents[2] def test_ac2_shim_engine_build_error_identity_with_canonical() -> None: # The shim must NOT introduce a fresh subclass — consumers catching # ShimEngineBuildError MUST catch what c7 actually raises. assert ShimEngineBuildError is CanonicalEngineBuildError def test_ac2_shim_calibration_cache_error_identity_with_canonical() -> None: assert ShimCalibrationCacheError is CanonicalCalibrationCacheError def test_ac2_shim_module_has_no_runtime_side_effects() -> None: # Importing the shim must not register a component config block or # otherwise mutate global state. Re-importing the module twice in the # same process must remain a no-op. import importlib import gps_denied_onboard._types.inference_errors as shim first = importlib.reload(shim) second = importlib.reload(shim) assert first.EngineBuildError is second.EngineBuildError assert first.CalibrationCacheError is second.CalibrationCacheError def test_ac1_module_layout_has_no_cross_component_public_api_imports() -> None: # AZ-507's primary deliverable: every line that previously said # `components.X (Public API)` in an "Imports from" line must now point # at `_types` (or be removed). We grep for the offending pattern and # assert zero matches. layout = ( _REPO_ROOT / "_docs" / "02_document" / "module-layout.md" ).read_text(encoding="utf-8") offenders = re.findall( r"components\.[a-z0-9_]+\s*\(Public API\)", layout, ) assert offenders == [], ( "module-layout.md still names cross-component Public API imports " f"after AZ-507: {offenders}" ) def test_ac5_architecture_doc_codifies_cross_component_rule() -> None: # AZ-507 AC-5: the architecture doc must carry a one-paragraph rule # that cross-component imports go through `_types/*.py` (DTOs + # typed-error envelopes), never `components.X (Public API)`. arch = ( _REPO_ROOT / "_docs" / "02_document" / "architecture.md" ).read_text(encoding="utf-8") # Look for the rule sentence (case-insensitive) and the explicit # AZ-507 reference so future readers can trace the decision. assert "AZ-507" in arch, "architecture.md must reference AZ-507" assert re.search( r"cross-component imports go through `_types", arch, flags=re.IGNORECASE, ), ( "architecture.md must state the cross-component import rule " "introduced by AZ-507" )