"""The archive fault adapter omits receipts without changing surviving clocks.""" import importlib.util from pathlib import Path from types import SimpleNamespace import pytest def adapter_module(): path = Path(__file__).resolve().parents[1] / "scripts/planning_archive_source.py" spec = importlib.util.spec_from_file_location("archive_fault_probe", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def test_fault_preserves_survivor_identity_payload_and_receipt_offsets(monkeypatch): module = adapter_module() original = [ SimpleNamespace( received_monotonic_ns=int((100 + t) * 1e9), received_at_epoch_ns=int((1000 + t) * 1e9), topic="fixture/lio_pcl" if i % 2 else "fixture/lio_pose", sequence=i + 1, payload=bytes([i]), ) for i, t in enumerate([0, 1, 2, 3, 3.9, 4, 5]) ] monkeypatch.setattr(module, "iter_replay_messages", lambda _: iter(original)) clock = [int(500e9)] def wait(delay): clock[0] += int(delay * 1e9) return False monkeypatch.setattr(module, "time", SimpleNamespace(monotonic_ns=lambda: clock[0])) source = module.ReceiptQueueArchiveSource(Path("unused"), "B", 10, drop_interval_s=(2, 4)) source.stop = SimpleNamespace(wait=wait) source.started = clock[0] published = [] source.ingress = SimpleNamespace( publish=lambda **kw: published.append(kw) or True, end_session=lambda session: None, ) source.publish() kept = [original[i] for i in [0, 1, 5, 6]] assert source.error is None assert [x["sequence"] for x in source.dropped_receipts] == [3, 4, 5] for out, entry in zip(published, kept, strict=True): assert out["source_sequence"] == entry.sequence assert out["payload"] is entry.payload assert out["captured_at_epoch_ns"] == entry.received_at_epoch_ns assert out["received_monotonic_ns"] == source.started + entry.received_monotonic_ns - int( 100e9 ) @pytest.mark.parametrize("interval", [(2, 2), (4, 2), (0, 2), (1, 11), (1, float("nan"))]) def test_invalid_fault_interval_is_rejected(interval): with pytest.raises(ValueError, match="fault interval"): adapter_module().ReceiptQueueArchiveSource( Path("unused"), "B", 10, drop_interval_s=interval )