"""Integration: the librarian's persisted search state (requests + checkpoints). These pin the durable-state mechanics that let a search survive a restart: * accepted requests are replayed (re-enqueued) after a restart, * a finished/abandoned search is forgotten (request + checkpoint dropped), * a checkpoint round-trips through disk intact. The RESUME correctness itself (seek past scanned, don't miss, don't re-scan) lives in tests/unit/test_search_bot.py. """ import logging import sys import types import pytest if "habanero" not in sys.modules: _habanero = types.ModuleType("habanero") _habanero.Crossref = object sys.modules["habanero"] = _habanero import conjurer_librarian as lib # noqa: E402 from durable_queue import DiskQueue # noqa: E402 _LOG = logging.getLogger("test-resume-state") _LOG.addHandler(logging.NullHandler()) class _DummyCrossref: def __init__(self, **kwargs): self.kwargs = kwargs @pytest.fixture def state(tmp_path, monkeypatch): monkeypatch.setattr(lib, "_requests", DiskQueue(str(tmp_path / "req"))) monkeypatch.setattr(lib, "_checkpoints", DiskQueue(str(tmp_path / "cp"))) monkeypatch.setattr(lib, "Crossref", _DummyCrossref) monkeypatch.setenv("CONJURER_CROSSREF_MAILTO", "test@example.com") monkeypatch.setattr(lib, "NETRC_FILE", "/nonexistent/conjurer/.netrc") while not lib.librarian_queue.empty(): lib.librarian_queue.get() lib.librarian_list.clear() with lib._active_lock: lib.active_queries.clear() return None def test_accepted_request_is_replayed_after_restart(state): lib._requests.put("u1", {"query": "kwas foliowy", "deep_search": False}) lib.replay_requests(_LOG) item = lib.librarian_queue.get_nowait() assert isinstance(item, lib.Librarian) assert item.uuid == "u1" assert item.query == "kwas foliowy" assert lib.active_queries["u1"] == "queued" # known again to the watchdog def test_forget_search_drops_request_and_checkpoint(state): lib._requests.put("u2", {"query": "x", "deep_search": False}) lib._checkpoints.put("u2", {"dois": {}, "found": [], "positions": {}}) lib._forget_search("u2") assert not lib._requests.contains("u2") assert not lib._checkpoints.contains("u2") def test_checkpoint_round_trips_through_disk(state): checkpoint = { "dois": {"10.1/x": {"DOI": "10.1/x", "title": ["T"], "type": "article"}}, "found": ["10.1/already"], "positions": {"0_chunk.txt": 4096}, } lib._checkpoints.put("u3", checkpoint) assert lib._checkpoints.get("u3") == checkpoint def test_unreadable_replayed_request_is_dropped_not_looped(state, monkeypatch): # A request that can't be reconstructed (e.g. missing Crossref contact) must # be dropped, not retried forever. lib._requests.put("u4", {"query": "x", "deep_search": False}) monkeypatch.delenv("CONJURER_CROSSREF_MAILTO", raising=False) lib.replay_requests(_LOG) assert lib.librarian_queue.empty() assert not lib._requests.contains("u4") # forgotten, not left to loop