feat(k1): complete canonical control lifecycle
This commit is contained in:
@@ -64,6 +64,23 @@ def _burst_ffmpeg(tmp_path: Path) -> Path:
|
||||
return executable
|
||||
|
||||
|
||||
def _clean_source_end_ffmpeg(tmp_path: Path) -> Path:
|
||||
executable = tmp_path / "clean-source-end-ffmpeg"
|
||||
executable.write_text(
|
||||
f"#!{sys.executable}\n"
|
||||
"import sys, time\n"
|
||||
"def box(kind, payload=b''):\n"
|
||||
" return (8 + len(payload)).to_bytes(4, 'big') + kind + payload\n"
|
||||
"sys.stdout.buffer.write(box(b'ftyp', b'isom') + box(b'moov'))\n"
|
||||
"sys.stdout.buffer.write(box(b'moof') + box(b'mdat', b'final-frame'))\n"
|
||||
"sys.stdout.buffer.flush()\n"
|
||||
"time.sleep(0.15)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
executable.chmod(0o700)
|
||||
return executable
|
||||
|
||||
|
||||
def _buffered_tail_ffmpeg(tmp_path: Path, sentinel: Path, *, fragments: int) -> Path:
|
||||
executable = tmp_path / "buffered-tail-ffmpeg"
|
||||
executable.write_text(
|
||||
@@ -289,6 +306,76 @@ def test_acquisition_records_without_browser_and_source_switch_seals_epochs(
|
||||
gateway.close()
|
||||
|
||||
|
||||
def test_expected_camera_source_end_during_device_stop_seals_complete_epoch(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv(
|
||||
"MISSIONCORE_FFMPEG_BINARY",
|
||||
str(_clean_source_end_ffmpeg(tmp_path)),
|
||||
)
|
||||
gateway = XgridsK1CameraGateway(tmp_path, XGRIDS_K1_PLUGIN_ID)
|
||||
session = tmp_path / "session"
|
||||
session.mkdir()
|
||||
try:
|
||||
selected = gateway.select("sensor.camera.left", "192.168.1.20")
|
||||
gateway.start_recording(session)
|
||||
gateway.expect_source_end_for_device_stop()
|
||||
|
||||
_wait_until(
|
||||
lambda: gateway.snapshot()["recording"]["completed_epochs"] == 1,
|
||||
)
|
||||
state = gateway.snapshot()
|
||||
assert state["phase"] == "idle"
|
||||
assert state["error"] is None
|
||||
assert state["delivery"] is None
|
||||
assert state["recording"]["active"] is True
|
||||
assert state["recording"]["source_end_expected"] is False
|
||||
|
||||
gateway.stop_recording(status="complete")
|
||||
epoch = (
|
||||
session
|
||||
/ "media"
|
||||
/ "sensor.camera.left"
|
||||
/ f"epoch-{selected['generation']}"
|
||||
)
|
||||
summary = json.loads((epoch / "summary.json").read_text(encoding="utf-8"))
|
||||
assert summary["status"] == "complete"
|
||||
assert summary["failure_code"] is None
|
||||
assert summary["media_segment_count"] == 1
|
||||
finally:
|
||||
gateway.close()
|
||||
|
||||
|
||||
def test_unexpected_camera_source_end_remains_a_recording_failure(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv(
|
||||
"MISSIONCORE_FFMPEG_BINARY",
|
||||
str(_clean_source_end_ffmpeg(tmp_path)),
|
||||
)
|
||||
gateway = XgridsK1CameraGateway(tmp_path, XGRIDS_K1_PLUGIN_ID)
|
||||
session = tmp_path / "session"
|
||||
session.mkdir()
|
||||
try:
|
||||
selected = gateway.select("sensor.camera.left", "192.168.1.20")
|
||||
gateway.start_recording(session)
|
||||
|
||||
_wait_until(lambda: gateway.snapshot()["phase"] == "error")
|
||||
epoch = (
|
||||
session
|
||||
/ "media"
|
||||
/ "sensor.camera.left"
|
||||
/ f"epoch-{selected['generation']}"
|
||||
)
|
||||
summary = json.loads((epoch / "summary.json").read_text(encoding="utf-8"))
|
||||
assert summary["status"] == "interrupted"
|
||||
assert summary["failure_code"] == "camera-source-ended"
|
||||
finally:
|
||||
gateway.close()
|
||||
|
||||
|
||||
def test_camera_storage_open_failure_is_loud_and_never_starts_ffmpeg(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -501,7 +588,8 @@ def test_service_publishes_two_dynamic_camera_rows_and_stale_stop_is_safe(
|
||||
service._compatibility_attestation = {
|
||||
"firmware_version": "3.0.2",
|
||||
"topology": "direct-lan",
|
||||
"basis": "operator-attested",
|
||||
"verification": "live-device-info",
|
||||
"basis": "selected-profile-live-device-info-required",
|
||||
"observed_at": "2026-07-16T20:00:00Z",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user