feat(sessions): add durable observation archive and replay API

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 17:50:54 +03:00
parent aa2df560b7
commit 656f0c524d
23 changed files with 12492 additions and 5 deletions
+83
View File
@@ -10,6 +10,7 @@ import pytest
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.reasoncodes import ReasonCode
import k1link.mqtt.capture as capture_module
from k1link.mqtt.capture import (
FRAME_HEADER,
RAW_MAGIC,
@@ -256,3 +257,85 @@ def test_capture_reader_rejects_invalid_or_unbounded_frames(
with pytest.raises(CaptureFormatError, match=message):
list(iter_capture_frames(path, max_payload_bytes=4))
def _mqtt_message(topic: str, payload: bytes) -> mqtt.MQTTMessage:
message = mqtt.MQTTMessage(topic=topic.encode("utf-8"))
message.payload = payload
message.qos = 0
message.retain = False
message.dup = False
return message
def test_group_commit_fsyncs_raw_before_publishing_metadata(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
writer = capture_module._CaptureWriter(tmp_path / "capture", 1024) # noqa: SLF001
writer.open()
assert writer._raw is not None # noqa: SLF001
assert writer._metadata is not None # noqa: SLF001
raw_fd = writer._raw.fileno() # noqa: SLF001
metadata_fd = writer._metadata.fileno() # noqa: SLF001
fsync_calls: list[int] = []
real_fsync = capture_module.os.fsync
def observe_fsync(descriptor: int) -> None:
fsync_calls.append(descriptor)
real_fsync(descriptor)
monkeypatch.setattr(capture_module.os, "fsync", observe_fsync)
monkeypatch.setattr(capture_module, "GROUP_COMMIT_MAX_MESSAGES", 2)
writer.record(_mqtt_message("RealtimePath", b"one"))
assert writer.metadata_path.read_bytes() == b""
writer.record(_mqtt_message("RealtimePath", b"two"))
assert fsync_calls[:2] == [raw_fd, metadata_fd]
assert len(writer.metadata_path.read_text(encoding="utf-8").splitlines()) == 2
writer.close()
def test_group_commit_timer_bounds_quiet_stream_rpo(
tmp_path: Path,
) -> None:
writer = capture_module._CaptureWriter(tmp_path / "capture", 1024) # noqa: SLF001
writer.open()
writer.record(_mqtt_message("RealtimePath", b"one"))
assert writer.metadata_path.read_bytes() == b""
writer.maybe_commit(
writer._last_commit_monotonic # noqa: SLF001
+ capture_module.GROUP_COMMIT_INTERVAL_SECONDS
)
assert len(writer.metadata_path.read_text(encoding="utf-8").splitlines()) == 1
writer.close()
def test_raw_fsync_failure_never_publishes_metadata_ahead_of_raw(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
writer = capture_module._CaptureWriter(tmp_path / "capture", 1024) # noqa: SLF001
writer.open()
writer.record(_mqtt_message("RealtimePath", b"one"))
assert writer._raw is not None # noqa: SLF001
raw_fd = writer._raw.fileno() # noqa: SLF001
real_fsync = capture_module.os.fsync
def fail_raw_fsync(descriptor: int) -> None:
if descriptor == raw_fd:
raise OSError("synthetic raw fsync failure")
real_fsync(descriptor)
monkeypatch.setattr(capture_module.os, "fsync", fail_raw_fsync)
with pytest.raises(OSError, match="synthetic raw fsync failure"):
writer._commit_pending() # noqa: SLF001
assert writer.metadata_path.read_bytes() == b""
# Restore durability primitive so the fixture can close normally.
monkeypatch.setattr(capture_module.os, "fsync", real_fsync)
writer.close()