mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 18:11:13 +00:00
5156453224
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>
158 lines
4.2 KiB
Python
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)
|