feat(k1): complete primary acquisition lifecycle
This commit is contained in:
+49
-13
@@ -103,6 +103,7 @@ def make_recorded_h264_fixture(
|
||||
*,
|
||||
timescale: int = 1_000,
|
||||
sample_duration: int = 500,
|
||||
base_decode_time: int = 0,
|
||||
) -> tuple[bytes, bytes]:
|
||||
def box(box_type: bytes, payload: bytes = b"") -> bytes:
|
||||
return (8 + len(payload)).to_bytes(4, "big") + box_type + payload
|
||||
@@ -137,8 +138,9 @@ def make_recorded_h264_fixture(
|
||||
init = box(b"ftyp", b"isom") + box(b"moov", trak + box(b"mvex", trex) + avcc)
|
||||
|
||||
tfhd = full_box(b"tfhd", track_id.to_bytes(4, "big"), flags=0x020000)
|
||||
tfdt = full_box(b"tfdt", base_decode_time.to_bytes(4, "big"))
|
||||
trun = full_box(b"trun", (1).to_bytes(4, "big"))
|
||||
fragment = box(b"moof", box(b"traf", tfhd + trun)) + box(b"mdat", b"frame")
|
||||
fragment = box(b"moof", box(b"traf", tfhd + tfdt + trun)) + box(b"mdat", b"frame")
|
||||
return init, fragment
|
||||
|
||||
|
||||
@@ -543,8 +545,7 @@ def test_cold_replay_returns_quick_202_then_status_returns_ready_launch(
|
||||
render_file_response(viewer_response, range_header=None)
|
||||
)
|
||||
viewer_headers = {
|
||||
key.decode("latin-1"): value.decode("latin-1")
|
||||
for key, value in viewer_start["headers"]
|
||||
key.decode("latin-1"): value.decode("latin-1") for key, value in viewer_start["headers"]
|
||||
}
|
||||
assert viewer_start["status"] == 200
|
||||
assert viewer_body == payload
|
||||
@@ -880,6 +881,7 @@ def test_production_router_never_materializes_recording_inline(tmp_path: Path) -
|
||||
assert recording_error.value.status_code == 503
|
||||
assert calls == 0
|
||||
|
||||
|
||||
def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -1065,9 +1067,7 @@ def test_session_router_exposes_opaque_recorded_media_manifest_and_ranges(
|
||||
assert manifest["byte_length"] == source["byte_length"]
|
||||
assert manifest["timeline_start_seconds"] == source["timeline_start_seconds"]
|
||||
assert manifest["timeline_end_seconds"] == source["timeline_end_seconds"]
|
||||
assert manifest_response.headers["etag"] == (
|
||||
f'"sha256:{manifest["generation_sha256"]}"'
|
||||
)
|
||||
assert manifest_response.headers["etag"] == (f'"sha256:{manifest["generation_sha256"]}"')
|
||||
init_sha256 = hashlib.sha256(init).hexdigest()
|
||||
segment_sha256 = hashlib.sha256(segment).hexdigest()
|
||||
assert manifest["epochs"] == [
|
||||
@@ -1143,9 +1143,7 @@ def test_session_router_exposes_opaque_recorded_media_manifest_and_ranges(
|
||||
}
|
||||
assert segment_start["status"] == 206
|
||||
assert segment_body == segment[:6]
|
||||
assert headers["cache-control"] == (
|
||||
"private, max-age=31536000, immutable, no-transform"
|
||||
)
|
||||
assert headers["cache-control"] == ("private, max-age=31536000, immutable, no-transform")
|
||||
assert headers["etag"] == f'"sha256:{segment_sha256}"'
|
||||
assert headers["content-length"] == "6"
|
||||
assert headers["x-content-type-options"] == "nosniff"
|
||||
@@ -1224,9 +1222,7 @@ def test_ready_launch_uses_background_prepared_camera_manifest_without_rescan(
|
||||
manager.enqueue(store.prepare_replay(session.name))
|
||||
deadline = time.monotonic() + 2
|
||||
snapshot = manager.status(session.name)
|
||||
while (
|
||||
snapshot is None or snapshot.state != "ready"
|
||||
) and time.monotonic() < deadline:
|
||||
while (snapshot is None or snapshot.state != "ready") and time.monotonic() < deadline:
|
||||
time.sleep(0.005)
|
||||
snapshot = manager.status(session.name)
|
||||
assert snapshot is not None and snapshot.state == "ready"
|
||||
@@ -1276,6 +1272,10 @@ def test_recorded_media_epoch_ends_are_monotonic_generation_bound_and_not_rrd_pa
|
||||
sessions = repository / "sessions"
|
||||
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
|
||||
init, segment = make_recorded_h264_fixture(sample_duration=250)
|
||||
_, following_segment = make_recorded_h264_fixture(
|
||||
sample_duration=250,
|
||||
base_decode_time=250,
|
||||
)
|
||||
first = CameraArchiveWriter(session, "sensor.camera.private-left", 1)
|
||||
first.append("init", init, host_epoch_ns=1_100_000_000, host_monotonic_ns=2_100_000_000)
|
||||
first.append(
|
||||
@@ -1295,7 +1295,7 @@ def test_recorded_media_epoch_ends_are_monotonic_generation_bound_and_not_rrd_pa
|
||||
)
|
||||
second.append(
|
||||
"media",
|
||||
segment,
|
||||
following_segment,
|
||||
host_epoch_ns=2_000_000_000,
|
||||
host_monotonic_ns=3_000_000_000,
|
||||
)
|
||||
@@ -1393,6 +1393,42 @@ def test_recorded_media_rejects_non_monotonic_segments_and_overlapping_epochs(
|
||||
)
|
||||
|
||||
|
||||
def test_recorded_media_rejects_discontinuous_tfdt_decode_timeline(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
repository = tmp_path / "repo"
|
||||
sessions = repository / "sessions"
|
||||
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
|
||||
init, first_segment = make_recorded_h264_fixture(sample_duration=500)
|
||||
_, discontinuous_segment = make_recorded_h264_fixture(
|
||||
sample_duration=500,
|
||||
base_decode_time=1_000,
|
||||
)
|
||||
writer = CameraArchiveWriter(session, "sensor.camera.private-left", 1)
|
||||
writer.append("init", init)
|
||||
writer.append(
|
||||
"media",
|
||||
first_segment,
|
||||
host_epoch_ns=1_250_000_000,
|
||||
host_monotonic_ns=2_250_000_000,
|
||||
)
|
||||
writer.append(
|
||||
"media",
|
||||
discontinuous_segment,
|
||||
host_epoch_ns=1_750_000_000,
|
||||
host_monotonic_ns=2_750_000_000,
|
||||
)
|
||||
writer.close()
|
||||
store = SessionStore(repository, data_dir=tmp_path / "data")
|
||||
store.reconcile_archive(xgrids_k1_archive_source(sessions))
|
||||
|
||||
with pytest.raises(SessionIntegrityError, match="decode timeline is discontinuous"):
|
||||
RecordedMediaInspector().inspect(
|
||||
store.list_recorded_media(session.name)[0],
|
||||
store.prepare_replay(session.name),
|
||||
)
|
||||
|
||||
|
||||
def test_background_preparation_fails_closed_on_unparseable_recorded_media(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user