"""AZ-304 AC-10 / AC-11 — UUIDv5 namespace determinism + cross-repo coordination. The expected UUIDs locked below are the *cross-repo coordination evidence* between ``gps-denied-onboard`` (Python ``uuid.uuid5``) and ``satellite-provider`` (C# Guid.NewGuid v5 implementation per ``AZ-TBD_tile_identity_uuidv5_bulk_list``). Both sides MUST emit byte-identical UUIDs for these input vectors; changing any expected value here without a coordinated cross-workspace release breaks the correlation key joining the two systems. """ from __future__ import annotations from uuid import UUID import pytest from gps_denied_onboard.components.c6_tile_cache._types import TileSource from gps_denied_onboard.components.c6_tile_cache._uuid_namespace import ( TILE_NAMESPACE_UUID, derive_location_hash, derive_tile_id, ) _EXPECTED_NAMESPACE = UUID("5b8d0c2e-1a4f-4b3a-8c9d-e7f6a3b2c1d0") # AC-10 — five locked tile-id vectors. (z, x, y, source, flight_id) -> expected uuidv5. _TILE_ID_VECTORS: list[tuple[int, int, int, str, str | None, str]] = [ (18, 72346, 46342, "googlemaps", None, "6f49531b-1351-55ba-b733-66d3f1fca1a5"), ( 18, 72346, 46342, "onboard_ingest", "11111111-2222-3333-4444-555555555555", "c7f6eda4-3b95-5818-a0b7-1aa8cbb5aa95", ), (10, 300, 200, "googlemaps", None, "3604dd59-1018-5889-97dc-ba5635761ac5"), ( 21, 999999, 999999, "onboard_ingest", "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "36c6e0c0-54a1-56ab-9dfd-a4f8f184cb22", ), (15, 1000, 2000, "googlemaps", None, "955df722-8d4e-5375-8a23-4f45dc16fef1"), ] # AC-11 — four locked location-hash vectors. (z, x, y) -> expected uuidv5. _LOCATION_HASH_VECTORS: list[tuple[int, int, int, str]] = [ (18, 72346, 46342, "e95c7edb-550e-58eb-8f94-3056f73a57d3"), (10, 300, 200, "76aa22b7-fd8e-5089-8b20-c45fb4a0f5e8"), (21, 999999, 999999, "4337a27e-f118-524f-8d74-82cf9295c632"), (15, 1000, 2000, "0501b2fc-0fc8-5330-a407-c7ccbf1fb9c7"), ] # ---------------------------------------------------------------------- # AC-10: namespace + derivation are deterministic and locked. def test_ac10_namespace_uuid_locked() -> None: # Assert assert TILE_NAMESPACE_UUID == _EXPECTED_NAMESPACE assert TILE_NAMESPACE_UUID.version == 4 # the namespace itself was a one-time UUIDv4 pick assert str(TILE_NAMESPACE_UUID) == "5b8d0c2e-1a4f-4b3a-8c9d-e7f6a3b2c1d0" @pytest.mark.parametrize("z,x,y,source,flight_id,expected", _TILE_ID_VECTORS) def test_ac10_derive_tile_id_locked_vectors( z: int, x: int, y: int, source: str, flight_id: str | None, expected: str ) -> None: # Act result = derive_tile_id(z, x, y, source, flight_id) # Assert assert result == UUID(expected) assert result.version == 5 @pytest.mark.parametrize("z,x,y,source,flight_id,expected", _TILE_ID_VECTORS) def test_ac10_derive_tile_id_idempotent_on_second_call( z: int, x: int, y: int, source: str, flight_id: str | None, expected: str ) -> None: # Act first = derive_tile_id(z, x, y, source, flight_id) second = derive_tile_id(z, x, y, source, flight_id) # Assert assert first == second == UUID(expected) def test_ac10_derive_tile_id_accepts_tile_source_enum() -> None: """Passing a `TileSource` enum yields the same UUID as its string value.""" # Act from_enum = derive_tile_id(18, 72346, 46342, TileSource.GOOGLEMAPS, None) from_str = derive_tile_id(18, 72346, 46342, "googlemaps", None) # Assert assert from_enum == from_str assert from_enum == UUID("6f49531b-1351-55ba-b733-66d3f1fca1a5") def test_ac10_derive_tile_id_accepts_uuid_flight_id() -> None: """Passing a `UUID` instance for ``flight_id`` matches the string form.""" # Arrange flight_uuid_str = "11111111-2222-3333-4444-555555555555" # Act from_uuid = derive_tile_id(18, 72346, 46342, "onboard_ingest", UUID(flight_uuid_str)) from_str = derive_tile_id(18, 72346, 46342, "onboard_ingest", flight_uuid_str) # Assert assert from_uuid == from_str assert from_uuid == UUID("c7f6eda4-3b95-5818-a0b7-1aa8cbb5aa95") def test_ac10_derive_tile_id_rejects_unknown_source_type() -> None: # Act + Assert with pytest.raises(TypeError, match="source must be"): derive_tile_id(18, 0, 0, 123, None) def test_ac10_derive_tile_id_rejects_unknown_flight_id_type() -> None: # Act + Assert with pytest.raises(TypeError, match="flight_id must be"): derive_tile_id(18, 0, 0, "googlemaps", 12345) def test_ac10_derive_tile_id_rejects_malformed_flight_id_string() -> None: # Act + Assert with pytest.raises(ValueError): derive_tile_id(18, 0, 0, "googlemaps", "not-a-uuid") # ---------------------------------------------------------------------- # AC-11: location_hash is invariant across source / flight_id. @pytest.mark.parametrize("z,x,y,expected", _LOCATION_HASH_VECTORS) def test_ac11_derive_location_hash_locked_vectors(z: int, x: int, y: int, expected: str) -> None: # Act result = derive_location_hash(z, x, y) # Assert assert result == UUID(expected) assert result.version == 5 def test_ac11_location_hash_invariant_across_source_and_flight() -> None: """Same (z, x, y) yields the same location_hash for any source + flight_id.""" # Arrange cell = (18, 72346, 46342) cell_lh = derive_location_hash(*cell) # Act + Assert — `derive_tile_id` produces _different_ tile UUIDs for # different source/flight combos but the cell-bag is the same; this is # explicitly tested via the locked vectors above. Here we only need to # confirm that `location_hash` NEVER depends on these inputs. for _source in ("googlemaps", "onboard_ingest"): for _flight_id in (None, "11111111-2222-3333-4444-555555555555"): assert derive_location_hash(*cell) == cell_lh def test_ac11_different_cells_yield_different_location_hashes() -> None: # Act lh1 = derive_location_hash(18, 72346, 46342) lh2 = derive_location_hash(18, 72346, 46343) # neighbour cell lh3 = derive_location_hash(19, 72346, 46342) # same x,y at different zoom # Assert assert lh1 != lh2 assert lh1 != lh3 assert lh2 != lh3