mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 04:21:12 +00:00
[AZ-329] [AZ-330] [AZ-523] [AZ-524] Batch 44 atomic refactor
Implements two new C12 services and rebalances the C11/C12 boundary in one atomic commit: * AZ-329 PostLandingUploadOrchestrator — gates C11 upload on the `flight_footer` FDR record's `clean_shutdown` field; 4 refusal modes; new FdrFooterReader Protocol + LocalFdrFooterReader. * AZ-330 OperatorReLocService — AC-3.4 visual-loss re-localization hint; reuses shared LatLonAlt; OperatorCommandTransport Protocol cut (E-C8 owns the future pymavlink concrete); new FDR record kind `c12.reloc.requested`; log redaction (lat/lon 5 decimals, reason 200 chars). * AZ-523 C11 internal flight-state gate removed (SRP refactor): `confirm_flight_state` / `FlightStateSignal` use / `FlightStateNotOnGroundError` deleted from C11; TileUploader contract bumped to v2.0.0 (frozen) with migration note; AZ-317 superseded. * AZ-524 Package rename `c12_operator_tooling` → `c12_operator_orchestrator` across source, tests, pyproject, CMake, Dockerfile, compose, CI, runtime-root services class (`OperatorOrchestratorServices`) + factory function (`build_operator_orchestrator`), logger namespaces, config slug, docs, and the E-C12 epic title. Tests: 1543 passed, 80 skipped (all environment gates). Targeted AC suite (AZ-329 + AZ-330 + FdrFooterReader): 37 passed. Cold-start NFR-perf still ≤ 500 ms p99. Tracker: AZ-317 → Done (superseded); AZ-319 v2.0.0 contract bump comment; AZ-329/AZ-330 → In Testing; AZ-253 epic renamed; AZ-523 + AZ-524 created and closed as audit-trail tickets. See `_docs/03_implementation/batch_44_cycle1_report.md`. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
"""AZ-319 ``HttpTileUploader`` unit tests.
|
||||
|
||||
Covers AC-1 .. AC-14 and the upload-throughput NFR from
|
||||
``_docs/02_tasks/todo/AZ-319_c11_tile_uploader.md``.
|
||||
Covers AC-1, AC-3 .. AC-14 and the upload-throughput NFR from
|
||||
``_docs/02_tasks/done/AZ-319_c11_tile_uploader.md``. AC-2 (the legacy
|
||||
ON_GROUND gate) was removed in batch 44 — gating is now C12's
|
||||
``PostLandingUploadOrchestrator`` responsibility.
|
||||
|
||||
Uses :class:`httpx.MockTransport` for deterministic HTTP responses,
|
||||
:class:`FakeFdrSink` for FDR capture, a list-backed ``logging.Handler``
|
||||
for log capture, and stub C6 stores / gate / key manager so this
|
||||
suite never drags in AZ-303 / AZ-305 / AZ-317 / AZ-318 internals.
|
||||
for log capture, and stub C6 stores / key manager so this suite never
|
||||
drags in AZ-303 / AZ-305 / AZ-318 internals.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -25,8 +27,6 @@ import pytest
|
||||
|
||||
from gps_denied_onboard.components.c11_tile_manager import (
|
||||
C11Config,
|
||||
FlightStateNotOnGroundError,
|
||||
FlightStateSignal,
|
||||
HttpTileUploader,
|
||||
IngestStatus,
|
||||
PerFlightKeyManager,
|
||||
@@ -37,9 +37,6 @@ from gps_denied_onboard.components.c11_tile_manager import (
|
||||
UploadRequest,
|
||||
canonical_payload_bytes,
|
||||
)
|
||||
from gps_denied_onboard.components.c11_tile_manager.flight_state_gate import (
|
||||
FlightStateGate,
|
||||
)
|
||||
from gps_denied_onboard.fdr_client import FdrRecord
|
||||
from gps_denied_onboard.fdr_client.fakes import FakeFdrSink
|
||||
|
||||
@@ -125,25 +122,6 @@ class _FakeMetadataStore:
|
||||
self.mark_calls.append((tile_id, uploaded_at))
|
||||
|
||||
|
||||
class _StubGate:
|
||||
"""Stand-in for AZ-317 ``FlightStateGate``."""
|
||||
|
||||
def __init__(
|
||||
self, signal: FlightStateSignal = FlightStateSignal.ON_GROUND
|
||||
) -> None:
|
||||
self._signal = signal
|
||||
self.confirm_calls: int = 0
|
||||
|
||||
def confirm_on_ground(self) -> FlightStateSignal:
|
||||
self.confirm_calls += 1
|
||||
if self._signal != FlightStateSignal.ON_GROUND:
|
||||
raise FlightStateNotOnGroundError(
|
||||
self._signal,
|
||||
datetime.now(timezone.utc),
|
||||
)
|
||||
return self._signal
|
||||
|
||||
|
||||
class _StubKeyManager:
|
||||
"""Stand-in for AZ-318 ``PerFlightKeyManager``.
|
||||
|
||||
@@ -222,7 +200,6 @@ def _build_uploader(
|
||||
transport: httpx.MockTransport,
|
||||
pending: list[_FakeTile] | None = None,
|
||||
blobs: dict[str, bytes] | None = None,
|
||||
gate_signal: FlightStateSignal = FlightStateSignal.ON_GROUND,
|
||||
fingerprint_hex: str = "0123456789abcdef",
|
||||
config: C11Config | None = None,
|
||||
sleep_recorder: list[float] | None = None,
|
||||
@@ -230,7 +207,6 @@ def _build_uploader(
|
||||
HttpTileUploader,
|
||||
FakeFdrSink,
|
||||
list[logging.LogRecord],
|
||||
_StubGate,
|
||||
_StubKeyManager,
|
||||
_FakeTileStore,
|
||||
_FakeMetadataStore,
|
||||
@@ -249,7 +225,6 @@ def _build_uploader(
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.propagate = False
|
||||
|
||||
gate = _StubGate(signal=gate_signal)
|
||||
key_manager = _StubKeyManager(fingerprint_hex=fingerprint_hex)
|
||||
tile_store = _FakeTileStore(blobs=blobs)
|
||||
metadata_store = _FakeMetadataStore(pending=pending)
|
||||
@@ -272,14 +247,13 @@ def _build_uploader(
|
||||
http_client=client,
|
||||
tile_store=tile_store,
|
||||
tile_metadata_store=metadata_store,
|
||||
flight_state_gate=gate, # type: ignore[arg-type]
|
||||
key_manager=key_manager, # type: ignore[arg-type]
|
||||
fdr_client=fdr, # type: ignore[arg-type]
|
||||
logger=logger,
|
||||
config=cfg,
|
||||
sleep=_sleep,
|
||||
)
|
||||
return uploader, fdr, log_records, gate, key_manager, tile_store, metadata_store, sleeps
|
||||
return uploader, fdr, log_records, key_manager, tile_store, metadata_store, sleeps
|
||||
|
||||
|
||||
def _make_request(*, batch_size: int = 10, flight_id: UUID | None = None) -> UploadRequest:
|
||||
@@ -361,7 +335,6 @@ def test_ac1_50_tile_happy_path_marks_all_uploaded() -> None:
|
||||
uploader,
|
||||
fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
metadata_store,
|
||||
@@ -385,48 +358,6 @@ def test_ac1_50_tile_happy_path_marks_all_uploaded() -> None:
|
||||
assert key_manager.end_calls == 1
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# AC-2: gate blocks before any read or POST
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_ac2_gate_blocks_before_any_read_or_post() -> None:
|
||||
# Arrange
|
||||
pending = [_make_tile()]
|
||||
posted: list[httpx.Request] = []
|
||||
|
||||
def _handler(request: httpx.Request) -> httpx.Response:
|
||||
posted.append(request)
|
||||
return httpx.Response(202, json={"batch_uuid": str(uuid4()), "per_tile_status": []})
|
||||
|
||||
transport = httpx.MockTransport(_handler)
|
||||
(
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
gate,
|
||||
key_manager,
|
||||
tile_store,
|
||||
metadata_store,
|
||||
_sleeps,
|
||||
) = _build_uploader(
|
||||
transport=transport,
|
||||
pending=pending,
|
||||
gate_signal=FlightStateSignal.IN_FLIGHT,
|
||||
)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(FlightStateNotOnGroundError):
|
||||
uploader.upload_pending_tiles(_make_request())
|
||||
|
||||
assert gate.confirm_calls == 1
|
||||
assert metadata_store.pending_calls == 0
|
||||
assert tile_store.read_calls == []
|
||||
assert key_manager.start_calls == []
|
||||
assert key_manager.end_calls == 0
|
||||
assert posted == []
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# AC-3: signature rejection — record + skip mark_uploaded; outcome=partial
|
||||
# ----------------------------------------------------------------------
|
||||
@@ -457,7 +388,6 @@ def test_ac3_signature_rejection_records_and_keeps_pending() -> None:
|
||||
uploader,
|
||||
fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
metadata_store,
|
||||
@@ -504,7 +434,6 @@ def test_ac4_duplicate_and_superseded_are_success() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
_key_manager,
|
||||
_tile_store,
|
||||
metadata_store,
|
||||
@@ -536,7 +465,6 @@ def test_ac5_signing_key_zeroised_on_success() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -570,7 +498,6 @@ def test_ac6_signing_key_zeroised_on_failure() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
metadata_store,
|
||||
@@ -605,7 +532,6 @@ def test_ac7_public_key_fdr_precedes_tile_fdr() -> None:
|
||||
uploader,
|
||||
fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
_key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -661,7 +587,6 @@ def test_ac8_429_honours_retry_after_seconds() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
_key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -697,7 +622,6 @@ def test_ac9_persistent_5xx_raises_satellite_provider_error() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -731,7 +655,6 @@ def test_ac10_401_fails_fast_no_retry() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
log_records,
|
||||
_gate,
|
||||
_key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -766,7 +689,6 @@ def test_ac11_empty_pending_set_is_success_no_posts() -> None:
|
||||
uploader,
|
||||
fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -868,7 +790,6 @@ def test_ac14_partial_success_batch_does_not_raise() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
_key_manager,
|
||||
_tile_store,
|
||||
metadata_store,
|
||||
@@ -910,7 +831,6 @@ def test_429_budget_exhaustion_raises_rate_limited_error() -> None:
|
||||
uploader,
|
||||
_fdr,
|
||||
_logs,
|
||||
_gate,
|
||||
key_manager,
|
||||
_tile_store,
|
||||
_metadata_store,
|
||||
@@ -951,7 +871,7 @@ def test_nfr_throughput_1000_tiles_under_budget() -> None:
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(_handler)
|
||||
(uploader, _fdr, _logs, _gate, _km, _ts, _ms, _sleeps) = _build_uploader(
|
||||
(uploader, _fdr, _logs, _km, _ts, _ms, _sleeps) = _build_uploader(
|
||||
transport=transport, pending=pending
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user