feat(archive): complete recorded session lifecycle

This commit is contained in:
DCCONSTRUCTIONS
2026-07-19 01:07:21 +03:00
parent ffffee1879
commit 71c85e9894
22 changed files with 922 additions and 144 deletions
+111
View File
@@ -23,6 +23,7 @@ from k1link.sessions import (
RecordedMediaInspector,
ReplayCommand,
SessionIntegrityError,
SessionNotFoundError,
SessionRecordingMaterializer,
SessionRecordingPreparationManager,
SessionStore,
@@ -246,6 +247,116 @@ def test_session_router_lists_details_and_dispatches_opaque_replay(tmp_path: Pat
assert_no_local_paths(value, repository)
def test_delete_session_removes_evidence_and_cache_but_refuses_an_open_recording(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
def export_recording(source: Path, destination: Path) -> dict[str, object]:
payload = b"deletable-recording"
destination.write_bytes(payload)
return {
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
"rrd_sha256": hashlib.sha256(payload).hexdigest(),
"rrd_bytes": len(payload),
"timeline": "session_time",
"timeline_start_ns": 0,
"timeline_end_ns": 1,
}
materializer = SessionRecordingMaterializer(
store.data_dir,
exporter=export_recording,
)
recording, release = materializer.materialize_pinned(store.prepare_replay(session.name))
router = build_session_router(store, recording_materializer=materializer)
delete_route = endpoint(router, "/api/v1/observation-sessions/{session_id}", "DELETE")
with pytest.raises(HTTPException) as conflict:
asyncio.run(delete_route(session_id=session.name))
assert conflict.value.status_code == 409
assert session.is_dir()
assert recording.path.is_file()
release()
response = asyncio.run(delete_route(session_id=session.name))
assert response.status_code == 204
assert not session.exists()
assert not recording.path.parent.exists()
with pytest.raises(SessionNotFoundError):
store.get_session(session.name)
def test_completed_recording_get_does_not_hold_delete_for_launch_lease(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
payload = b"recording-opened-and-closed"
def export_recording(source: Path, destination: Path) -> dict[str, object]:
destination.write_bytes(payload)
return {
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
"rrd_sha256": hashlib.sha256(payload).hexdigest(),
"rrd_bytes": len(payload),
"timeline": "session_time",
"timeline_start_ns": 0,
"timeline_end_ns": 1_000_000_000,
}
materializer = SessionRecordingMaterializer(store.data_dir, exporter=export_recording)
command = store.prepare_replay(session.name)
materializer.materialize(command)
manager = SessionRecordingPreparationManager(materializer)
resolved = manager.resolve_cached(command)
assert resolved is not None and resolved.recording is not None
router = build_session_router(
store,
recording_materializer=materializer,
recording_preparation_manager=manager,
)
replay_route = endpoint(
router,
"/api/v1/observation-sessions/{session_id}/replay",
"POST",
)
recording_route = endpoint(
router,
"/api/v1/observation-sessions/{session_id}/recording.rrd",
"GET",
)
delete_route = endpoint(router, "/api/v1/observation-sessions/{session_id}", "DELETE")
try:
replay = asyncio.run(replay_route(session_id=session.name, request=None))
generation = replay["launch"]["sha256"]
file_response = asyncio.run(
recording_route(
session_id=session.name,
generation=generation,
if_match=None,
if_none_match=None,
)
)
_, body = asyncio.run(render_file_response(file_response, range_header=None))
assert body == payload
deleted = asyncio.run(delete_route(session_id=session.name))
assert deleted.status_code == 204
assert not session.exists()
finally:
manager.close()
def test_session_router_returns_seekable_recording_and_serves_byte_ranges(
tmp_path: Path,
) -> None: