"""Syntactic / structural checks on docker-compose.test.yml. We can't run `docker compose config` in a unit test (no Docker), but we can load the YAML and assert the structural invariants AZ-406 commits to: - All required service names are present. - `e2e-net.internal` is `true` (RESTRICT-SAT-1 / NFT-SEC-02). - The e2e-runner consumes the required volumes for input data, fixtures, fdr-output read-only, tlog-output read-only, results. - The mavlink_passkey secret is wired. """ from __future__ import annotations from pathlib import Path import yaml COMPOSE_FILE = Path(__file__).resolve().parents[2] / "docker" / "docker-compose.test.yml" def _load_compose() -> dict: return yaml.safe_load(COMPOSE_FILE.read_text(encoding="utf-8")) def test_required_services_present() -> None: cfg = _load_compose() services = cfg["services"] for name in ( "gps-denied-onboard", "ardupilot-plane-sitl", "inav-sitl", "mock-suite-sat-service", "mavproxy-listener", "e2e-runner", ): assert name in services, f"docker-compose missing service: {name}" def test_e2e_net_is_internal() -> None: cfg = _load_compose() assert cfg["networks"]["e2e-net"]["internal"] is True, ( "RESTRICT-SAT-1 / NFT-SEC-02 violation: e2e-net must be internal=true" ) def test_runner_mounts_required_paths() -> None: cfg = _load_compose() runner = cfg["services"]["e2e-runner"] volumes_text = "\n".join(runner["volumes"]) for required in ( "/test-data:ro", "/expected:ro", "/test-fixtures:ro", "/test-suite:ro", "/fdr:ro", "/tlogs:ro", "/e2e-results", "/mock-audit:ro", ): assert required in volumes_text, ( f"e2e-runner must mount {required}; current volumes:\n{volumes_text}" ) def test_mavlink_passkey_secret_wired() -> None: cfg = _load_compose() secrets = cfg.get("secrets", {}) assert "mavlink_passkey" in secrets, "Top-level secrets must include mavlink_passkey" sut = cfg["services"]["gps-denied-onboard"] assert "mavlink_passkey" in [ s if isinstance(s, str) else s.get("source", "") for s in sut.get("secrets", []) ], "gps-denied-onboard must declare the mavlink_passkey secret" def test_fdr_output_volume_size_cap_present() -> None: """AC-NEW-3 — the FDR volume must have a size cap declared (belt-and-suspenders).""" cfg = _load_compose() fdr_vol = cfg["volumes"]["fdr-output"] opts = fdr_vol.get("driver_opts", {}) assert "size" in opts.get("o", ""), ( "fdr-output volume must declare a size cap (AC-NEW-3 belt-and-suspenders)" )