Files
gps-denied-onboard/tests/unit/shared/test_runtime_contracts.py
T
Oleksandr Bezdieniezhnykh 5156453224 [AZ-220] Add shared runtime contract models
Implement the shared DTO contract surface with validation so runtime components consume one public model set instead of duplicating shapes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 13:22:50 +03:00

158 lines
4.2 KiB
Python

import pytest
from pydantic import ValidationError
from shared.contracts import (
AnchorDecision,
CacheTileRecord,
FdrEvent,
FramePacket,
PositionEstimate,
TelemetrySample,
VioStatePacket,
VprCandidate,
)
def test_runtime_dtos_accept_valid_minimal_values() -> None:
# Arrange
timestamp_ns = 1_000_000
# Act
contracts = [
FramePacket(
frame_id="frame-1",
timestamp_ns=timestamp_ns,
image_ref="frames/frame-1",
calibration_id="calib-1",
occlusion="clear",
quality=0.95,
),
TelemetrySample(
timestamp_ns=timestamp_ns,
imu={"accel_x": 0.1},
attitude={"roll": 0.0},
altitude_m=950.0,
airspeed_mps=16.0,
gps_health="lost",
),
VioStatePacket(
timestamp_ns=timestamp_ns,
relative_pose={"x": 1.0},
velocity_mps=(1.0, 0.0, 0.0),
tracking_quality=0.8,
),
PositionEstimate(
timestamp_ns=timestamp_ns,
latitude_deg=49.9,
longitude_deg=36.2,
altitude_m=950.0,
covariance_semimajor_m=12.0,
source_label="satellite_anchored",
fix_type=3,
horizontal_accuracy_m=12.0,
anchor_age_ms=200,
),
VprCandidate(
chunk_id="chunk-1",
tile_id="tile-1",
score=0.87,
footprint={"min_lat": 49.0},
freshness_status="fresh",
),
AnchorDecision(
candidate_id="candidate-1",
accepted=True,
estimated_pose={"x": 1.0},
inliers=42,
mean_reprojection_error_px=0.8,
),
CacheTileRecord(
tile_id="tile-1",
crs="EPSG:3857",
meters_per_pixel=0.3,
capture_date="2026-05-03",
signature_hash="sha256:abc",
trust_level="trusted",
freshness_status="fresh",
provenance="suite-satellite-service",
),
FdrEvent(
event_type="health",
timestamp_ns=timestamp_ns,
component="shared.contracts",
severity="info",
payload_ref="fdr://segment/1",
mission_id="mission-1",
run_id="run-1",
),
]
# Assert
assert len(contracts) == 8
def test_missing_required_timestamp_is_rejected_with_structured_error() -> None:
# Act
with pytest.raises(ValidationError) as error:
FramePacket(
frame_id="frame-1",
image_ref="frames/frame-1",
calibration_id="calib-1",
occlusion="clear",
quality=0.95,
)
# Assert
assert error.value.errors()[0]["loc"] == ("timestamp_ns",)
assert error.value.errors()[0]["type"] == "missing"
def test_raw_frame_retention_is_rejected() -> None:
# Act
with pytest.raises(ValidationError) as error:
FramePacket(
frame_id="frame-1",
timestamp_ns=1,
image_ref="frames/frame-1",
calibration_id="calib-1",
occlusion="clear",
quality=0.95,
raw_frame_retained=True,
)
# Assert
assert "raw frame payloads must be referenced" in str(error.value)
def test_position_accuracy_cannot_under_report_covariance() -> None:
# Act
with pytest.raises(ValidationError) as error:
PositionEstimate(
timestamp_ns=1,
latitude_deg=49.9,
longitude_deg=36.2,
altitude_m=950.0,
covariance_semimajor_m=50.0,
source_label="satellite_anchored",
fix_type=3,
horizontal_accuracy_m=10.0,
anchor_age_ms=200,
)
# Assert
assert "must not under-report" in str(error.value)
def test_accepted_anchor_requires_estimated_pose() -> None:
# Act
with pytest.raises(ValidationError) as error:
AnchorDecision(
candidate_id="candidate-1",
accepted=True,
inliers=42,
mean_reprojection_error_px=0.8,
)
# Assert
assert "accepted anchor decisions require estimated_pose" in str(error.value)