Files
Oleksandr Bezdieniezhnykh 702a0c0ff3 [AZ-408] [AZ-410] [AZ-411] Batch 69: synth injectors + FT-P-02/03/14
AZ-408 (3pt) — Replace AZ-406 injector scaffolds with concrete generators:
- outlier.py: deterministic stride + far-away tile replacement; AC-2 ≥350m offset
- blackout_spoof.py: paired video blackout + FC GPS spoof with ≤40ms alignment;
  AC-4 realistic fix_type/hdop; AC-NEW-8 200-500m inter-spoof deltas
- multi_segment.py: ≥3 disjoint windows, ≥30s gaps, ≤25% coverage
- fc_proxy.py: timed-splice runtime proxy with pre-activate RuntimeError guard
- _common.py: derive_rng + tile-manifest reader + tmpfs helpers
- injector_fixtures.py: pytest fixtures wired via runner conftest

AZ-410 (3pt) — FT-P-02 cumulative drift between satellite anchors:
- anchor_pair_detector.py: AC-1 detection, AC-2/3 pass-fraction,
  AC-4 monotonicity check, CSV evidence
- test_ft_p_02_derkachi_drift.py: scenario gated on upstream helper
  NotImplementedError (frame_source_replay / fdr_reader / imu_replay)

AZ-411 (2pt) — FT-P-03 + FT-P-14 schema + WGS84:
- estimate_schema.py: AC-1 schema completeness, AC-2 source-label set
  containment, AC-3 WGS84 range + int32 1e-7 decode
- test_ft_p_03_14_schema_wgs84.py: shared single-image-push scenario

Tests: 248 unit tests pass (+91 vs batch 68).
Reports: batch_69_report.md, batch_69_review.md (PASS),
cumulative_review_batches_67-69_cycle1_report.md (PASS).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 17:54:00 +03:00

142 lines
4.3 KiB
Python

"""Public-surface contract tests for the AZ-408 injector dataclasses.
AZ-406 commits to module locations; AZ-408 owns the concrete dataclass
shapes. These tests assert the API surface (frozen dataclasses, public
``build()`` functions returning typed reports). Behavioural tests live
in their own files (``test_outlier.py``, ``test_blackout_spoof.py``,
``test_multi_segment.py``, ``test_fc_proxy.py``).
"""
from __future__ import annotations
from pathlib import Path
import pytest
from fixtures.injectors.blackout_spoof import BlackoutSpoofPlan, BlackoutSpoofReport
from fixtures.injectors.cold_boot import ColdBootFixture
from fixtures.injectors.cold_boot import load as load_cold_boot
from fixtures.injectors.fc_proxy import BlackoutSpoofProxy, SpoofGpsRecord
from fixtures.injectors.multi_segment import MultiSegmentPlan, MultiSegmentReport
from fixtures.injectors.outlier import OutlierInjectionPlan, OutlierInjectionReport
def test_outlier_plan_dataclass_is_frozen() -> None:
# Arrange
plan = OutlierInjectionPlan(
source_frames_dir=Path("/tmp/frames"),
tile_cache_dir=Path("/tmp/tile-cache"),
density="medium",
)
# Act / Assert
with pytest.raises(AttributeError):
plan.density = "heavy" # type: ignore[misc]
assert plan.min_offset_m == 350.0
def test_outlier_plan_density_literal_round_trip() -> None:
# Arrange / Act
for density in ("light", "medium", "heavy"):
plan = OutlierInjectionPlan(
source_frames_dir=Path("/tmp"),
tile_cache_dir=Path("/tmp"),
density=density, # type: ignore[arg-type]
)
# Assert
assert plan.density == density
def test_outlier_report_is_frozen_dataclass() -> None:
# Arrange
report = OutlierInjectionReport(
out_root=Path("/tmp/out"),
total_source_frames=100,
replaced_frame_count=10,
density="medium",
min_geodesic_offset_m=400.0,
max_geodesic_offset_m=900.0,
)
# Act / Assert
with pytest.raises(AttributeError):
report.replaced_frame_count = 20 # type: ignore[misc]
def test_blackout_spoof_plan_round_trip() -> None:
# Arrange / Act
plan = BlackoutSpoofPlan(
source_frames_dir=Path("/tmp/frames"),
blackout_seconds=35.0,
spoof_offset_m=120.0,
spoof_bearing_deg=90.0,
)
# Assert
assert plan.blackout_seconds == 35.0
assert plan.max_alignment_err_ms == 40.0 # default per AC-3
def test_blackout_spoof_report_is_frozen_dataclass() -> None:
# Arrange
proxy = BlackoutSpoofProxy(window_start_ms=0, window_end_ms=1000, spoof_gps=[])
# Assert that the report type is constructible (smoke check)
assert proxy.activation_report is None
def test_multi_segment_plan_defaults() -> None:
# Arrange / Act
plan = MultiSegmentPlan(source_frames_dir=Path("/tmp/frames"))
# Assert
assert plan.n_segments == 3
assert plan.segment_seconds == 12.0
def test_multi_segment_report_is_frozen_dataclass() -> None:
# Arrange
report = MultiSegmentReport(
out_root=Path("/tmp/out"),
segments=[],
source_duration_ms=300_000,
total_blackout_frames=300,
total_blackout_fraction=0.10,
)
# Act / Assert
with pytest.raises(AttributeError):
report.source_duration_ms = 0 # type: ignore[misc]
def test_spoof_gps_record_is_frozen_dataclass() -> None:
# Arrange
rec = SpoofGpsRecord(
monotonic_ms=1000,
lat_deg=50.1,
lon_deg=36.2,
alt_m=300.0,
fix_type=3,
hdop=1.0,
)
# Act / Assert
with pytest.raises(AttributeError):
rec.lat_deg = 0.0 # type: ignore[misc]
# Cold-boot tests are unchanged from AZ-406 — the cold-boot loader is
# still owned by AZ-419, not AZ-408.
def test_cold_boot_fixture_dataclass_is_frozen() -> None:
# Arrange
fx = ColdBootFixture(
lat_deg=50.0, lon_deg=30.0, alt_m=300.0, yaw_deg=180.0, last_valid_fix_age_s=2.5
)
# Act / Assert
with pytest.raises(AttributeError):
fx.alt_m = 999.0 # type: ignore[misc]
def test_cold_boot_load_raises_until_az419_lands(tmp_path: Path) -> None:
# Arrange
fixture_path = tmp_path / "cold_boot_fixture.json"
fixture_path.write_text("{}", encoding="utf-8")
# Act / Assert
with pytest.raises(NotImplementedError, match="AZ-419"):
load_cold_boot(fixture_path)