"""Unit tests for the poll_once watcher logic.""" import logging import pathlib from typing import Any import pytest from coordinator.__main__ import poll_once from coordinator.queue import DebateQueue from coordinator.state import SeenState # --------------------------------------------------------------------------- # Fake client # --------------------------------------------------------------------------- class FakeMulticaClient: """Minimal stand-in for MulticaClient. ``responses`` is a list of lists; each call to ``list_issues_by_status`` pops the next item from the front. If the list is exhausted the last entry is repeated (steady-state behaviour). """ def __init__(self, responses: list[list[dict[str, Any]]]): self._responses = list(responses) def list_issues_by_status(self, status: str, limit: int = 200) -> list[dict[str, Any]]: if len(self._responses) > 1: return self._responses.pop(0) return self._responses[0] def _issue(iid: str, updated_at: str, identifier: str = "WYL-X", title: str = "T") -> dict: return { "id": iid, "updated_at": updated_at, "identifier": identifier, "title": title, } @pytest.fixture def setup(tmp_path): """Return (state, queue, logger) wired to tmp_path.""" state = SeenState.load(tmp_path / "seen.json") queue = DebateQueue.load(tmp_path / "queue.json") logger = logging.getLogger("test.watcher") return state, queue, logger # --------------------------------------------------------------------------- # Scenario (a): first observation of a new in_review issue → enqueue # --------------------------------------------------------------------------- def test_first_observation_enqueues(setup): state, queue, logger = setup client = FakeMulticaClient([[_issue("id-1", "2026-01-01T00:00:00Z", "WYL-1", "Title A")]]) poll_once(client, state, queue, logger) assert len(queue.rounds) == 1 assert queue.rounds[0].issue_id == "id-1" assert queue.rounds[0].identifier == "WYL-1" assert queue.rounds[0].status == "pending" assert state.last_seen("id-1") == "2026-01-01T00:00:00Z" # --------------------------------------------------------------------------- # Scenario (b): same issue at same updated_at → no second enqueue # --------------------------------------------------------------------------- def test_same_updated_at_no_reenqueue(setup): state, queue, logger = setup issue = _issue("id-1", "2026-01-01T00:00:00Z") client = FakeMulticaClient([[issue]]) # steady-state poll_once(client, state, queue, logger) poll_once(client, state, queue, logger) assert len(queue.rounds) == 1 # only the first poll fired # --------------------------------------------------------------------------- # Scenario (c): updated_at changes while round still pending → dedupe, no re-enqueue # --------------------------------------------------------------------------- def test_updated_at_changes_while_pending_no_reenqueue(setup): state, queue, logger = setup client = FakeMulticaClient([ [_issue("id-1", "2026-01-01T00:00:00Z")], # first poll: enqueue [_issue("id-1", "2026-01-01T00:01:00Z")], # second poll: new updated_at, but round pending ]) poll_once(client, state, queue, logger) assert len(queue.rounds) == 1 poll_once(client, state, queue, logger) assert len(queue.rounds) == 1 # dedupe fired, no second round def test_updated_at_changes_while_running_no_reenqueue(setup): state, queue, logger = setup client = FakeMulticaClient([ [_issue("id-1", "2026-01-01T00:00:00Z")], [_issue("id-1", "2026-01-01T00:01:00Z")], ]) poll_once(client, state, queue, logger) queue.update_status(queue.rounds[0].round_id, "running") poll_once(client, state, queue, logger) assert len(queue.rounds) == 1 # --------------------------------------------------------------------------- # After round completes, a *new* updated_at triggers a fresh round # --------------------------------------------------------------------------- def test_reenqueue_after_round_done(setup): state, queue, logger = setup client = FakeMulticaClient([ [_issue("id-1", "2026-01-01T00:00:00Z")], # first poll [_issue("id-1", "2026-01-01T00:02:00Z")], # third poll: new updated_at, round done ]) poll_once(client, state, queue, logger) queue.update_status(queue.rounds[0].round_id, "done") poll_once(client, state, queue, logger) assert len(queue.rounds) == 2 assert queue.rounds[1].status == "pending" # --------------------------------------------------------------------------- # Multiple distinct issues in one poll # --------------------------------------------------------------------------- def test_multiple_issues_each_enqueued_once(setup): state, queue, logger = setup client = FakeMulticaClient([[ _issue("id-1", "2026-01-01T00:00:00Z", "WYL-1"), _issue("id-2", "2026-01-01T00:00:00Z", "WYL-2"), _issue("id-3", "2026-01-01T00:00:00Z", "WYL-3"), ]]) poll_once(client, state, queue, logger) assert len(queue.rounds) == 3 def test_mix_new_and_seen(setup): state, queue, logger = setup client = FakeMulticaClient([ [_issue("id-1", "t1"), _issue("id-2", "t2")], # first poll: both new [_issue("id-1", "t1"), _issue("id-2", "t2")], # second poll: same updated_at ]) poll_once(client, state, queue, logger) assert len(queue.rounds) == 2 poll_once(client, state, queue, logger) assert len(queue.rounds) == 2 # nothing new