"""C11 protocol conformance — uploader (AZ-319 AC-12) + downloader (AZ-316 AC-10). Smoke-tests that each concrete impl exposes every method its Protocol requires (positive cases) and that partial fakes omitting one of the required methods are correctly rejected (negative cases). """ from __future__ import annotations import logging import httpx from gps_denied_onboard.clock.wall_clock import WallClock from gps_denied_onboard.components.c11_tile_manager import ( C11Config, C11RetryConfig, HttpTileDownloader, HttpTileUploader, IdempotentRetryTileUploader, ) from gps_denied_onboard.components.c11_tile_manager.interface import ( TileDownloader, TileUploader, ) from gps_denied_onboard.fdr_client.fakes import FakeFdrSink _PRODUCER_ID = "c11_tile_manager.tile_uploader" class _NullSleep: def __call__(self, _seconds: float) -> None: return None class _PartialFakeMissingConfirm: """Conformance counterexample: missing ``confirm_flight_state``.""" def upload_pending_tiles(self, request: object) -> object: # noqa: ARG002 return None def enumerate_pending_tiles( self, flight_id: object | None = None ) -> list[object]: # noqa: ARG002 return [] class _PartialDownloaderMissingEnumerate: """Conformance counterexample: missing ``enumerate_remote_coverage``.""" def download_tiles_for_area(self, request: object) -> object: # noqa: ARG002 return None def test_ac12_concrete_uploader_satisfies_protocol() -> None: # Arrange — supply minimal-yet-valid dependencies; the Protocol # check only inspects method names, not their behaviour. cfg = C11Config( satellite_provider_ingest_url="https://parent-suite.test", upload_batch_size=10, upload_http_timeout_s=5.0, upload_max_retry_after_s=600, companion_id="conformance", ) transport = httpx.MockTransport(lambda r: httpx.Response(202)) uploader = HttpTileUploader( http_client=httpx.Client(transport=transport), tile_store=object(), # type: ignore[arg-type] tile_metadata_store=object(), # type: ignore[arg-type] flight_state_gate=object(), # type: ignore[arg-type] key_manager=object(), # type: ignore[arg-type] fdr_client=FakeFdrSink(_PRODUCER_ID), # type: ignore[arg-type] logger=logging.getLogger("test_az319_conformance"), config=cfg, sleep=_NullSleep(), ) # Assert assert isinstance(uploader, TileUploader) def test_ac12_partial_fake_is_not_protocol_conformant() -> None: # Assert assert not isinstance(_PartialFakeMissingConfirm(), TileUploader) def test_ac10_concrete_downloader_satisfies_protocol() -> None: # Arrange cfg = C11Config( satellite_provider_url="https://parent-suite.test", service_api_key="conformance-test-key", download_http_timeout_s=5.0, download_max_5xx_retries=4, download_max_retry_after_s=600, download_resolution_floor_m_per_px=0.5, ) transport = httpx.MockTransport(lambda r: httpx.Response(200, json={"tiles": []})) downloader = HttpTileDownloader( http_client=httpx.Client(transport=transport, base_url="https://parent-suite.test"), tile_writer=object(), # type: ignore[arg-type] budget_enforcer=object(), # type: ignore[arg-type] logger=logging.getLogger("test_az316_conformance"), config=cfg, sleep=_NullSleep(), ) # Assert assert isinstance(downloader, TileDownloader) def test_ac10_partial_downloader_is_not_protocol_conformant() -> None: # Assert assert not isinstance(_PartialDownloaderMissingEnumerate(), TileDownloader) def test_ac9_idempotent_retry_decorator_satisfies_uploader_protocol() -> None: # Arrange — wrap a Protocol-conformant inner uploader; the decorator # must itself satisfy ``TileUploader`` so the composition root can # bind it transparently in place of ``HttpTileUploader``. cfg = C11Config( satellite_provider_ingest_url="https://parent-suite.test", upload_batch_size=10, upload_http_timeout_s=5.0, upload_max_retry_after_s=600, companion_id="conformance", ) transport = httpx.MockTransport(lambda r: httpx.Response(202)) inner = HttpTileUploader( http_client=httpx.Client(transport=transport), tile_store=object(), # type: ignore[arg-type] tile_metadata_store=object(), # type: ignore[arg-type] flight_state_gate=object(), # type: ignore[arg-type] key_manager=object(), # type: ignore[arg-type] fdr_client=FakeFdrSink(_PRODUCER_ID), # type: ignore[arg-type] logger=logging.getLogger("test_az320_inner"), config=cfg, sleep=_NullSleep(), ) decorator = IdempotentRetryTileUploader( inner=inner, tile_metadata_store=object(), # type: ignore[arg-type] fdr_client=FakeFdrSink("c11_tile_manager.idempotent_retry"), # type: ignore[arg-type] logger=logging.getLogger("test_az320_decorator"), clock=WallClock(), config=C11RetryConfig(), ) # Assert assert isinstance(decorator, TileUploader)