Files
m-senior-developer 8c9a174ddc WYL-44 follow-up: add tests and extract poll_once for testability
R1: Add unit tests (22 total, all passing)
- tests/test_queue.py: enqueue/persist, is_issue_pending_or_running for all
  statuses, update_status mutate+persist, save/load roundtrip, corrupt file
  handling, pending() filtering
- tests/test_watcher.py: FakeMulticaClient drives poll_once across first
  observation, same updated_at dedupe, updated_at change while pending/running
  dedupe, re-enqueue after done, multi-issue, mix of new and seen

Refactor: extract poll_once(client, state, queue, logger) from watch_loop so
tests can call it directly without mocking time.sleep.

R2: Document known race near is_issue_pending_or_running — comment added
noting that orchestrator marking round done before updating issue status can
fire a second round; WYL-45 must resolve atomically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 21:20:23 +00:00

137 lines
4.3 KiB
Python

"""Unit tests for coordinator.queue."""
import json
import pathlib
import tempfile
import pytest
from coordinator.queue import DebateQueue, Round
@pytest.fixture
def tmp_queue(tmp_path):
return DebateQueue.load(tmp_path / "queue.json")
# ---------------------------------------------------------------------------
# enqueue
# ---------------------------------------------------------------------------
def test_enqueue_appends(tmp_queue):
r = tmp_queue.enqueue("issue-1", "WYL-1", "Some title")
assert len(tmp_queue.rounds) == 1
assert r.issue_id == "issue-1"
assert r.identifier == "WYL-1"
assert r.title == "Some title"
assert r.status == "pending"
def test_enqueue_persists(tmp_path):
q = DebateQueue.load(tmp_path / "queue.json")
r = q.enqueue("issue-1", "WYL-1", "Title")
# reload from disk
q2 = DebateQueue.load(tmp_path / "queue.json")
assert len(q2.rounds) == 1
assert q2.rounds[0].round_id == r.round_id
assert q2.rounds[0].issue_id == "issue-1"
def test_enqueue_multiple(tmp_queue):
tmp_queue.enqueue("issue-1", "WYL-1", "A")
tmp_queue.enqueue("issue-2", "WYL-2", "B")
assert len(tmp_queue.rounds) == 2
# ---------------------------------------------------------------------------
# is_issue_pending_or_running
# ---------------------------------------------------------------------------
def test_pending_returns_true(tmp_queue):
tmp_queue.enqueue("issue-1", "WYL-1", "T")
assert tmp_queue.is_issue_pending_or_running("issue-1") is True
def test_running_returns_true(tmp_queue):
r = tmp_queue.enqueue("issue-1", "WYL-1", "T")
tmp_queue.update_status(r.round_id, "running")
assert tmp_queue.is_issue_pending_or_running("issue-1") is True
def test_done_returns_false(tmp_queue):
r = tmp_queue.enqueue("issue-1", "WYL-1", "T")
tmp_queue.update_status(r.round_id, "done")
assert tmp_queue.is_issue_pending_or_running("issue-1") is False
def test_error_returns_false(tmp_queue):
r = tmp_queue.enqueue("issue-1", "WYL-1", "T")
tmp_queue.update_status(r.round_id, "error")
assert tmp_queue.is_issue_pending_or_running("issue-1") is False
def test_unknown_issue_returns_false(tmp_queue):
assert tmp_queue.is_issue_pending_or_running("no-such-issue") is False
# ---------------------------------------------------------------------------
# update_status
# ---------------------------------------------------------------------------
def test_update_status_mutates(tmp_queue):
r = tmp_queue.enqueue("issue-1", "WYL-1", "T")
tmp_queue.update_status(r.round_id, "running")
assert tmp_queue.rounds[0].status == "running"
def test_update_status_persists(tmp_path):
q = DebateQueue.load(tmp_path / "queue.json")
r = q.enqueue("issue-1", "WYL-1", "T")
q.update_status(r.round_id, "done")
q2 = DebateQueue.load(tmp_path / "queue.json")
assert q2.rounds[0].status == "done"
def test_update_status_unknown_raises(tmp_queue):
with pytest.raises(KeyError):
tmp_queue.update_status("nonexistent-round-id", "done")
# ---------------------------------------------------------------------------
# save / load roundtrip
# ---------------------------------------------------------------------------
def test_roundtrip_preserves_all_fields(tmp_path):
q = DebateQueue.load(tmp_path / "queue.json")
r = q.enqueue("issue-42", "WYL-42", "Full roundtrip title")
q.update_status(r.round_id, "running")
q2 = DebateQueue.load(tmp_path / "queue.json")
r2 = q2.rounds[0]
assert r2.round_id == r.round_id
assert r2.issue_id == "issue-42"
assert r2.identifier == "WYL-42"
assert r2.title == "Full roundtrip title"
assert r2.enqueued_at == r.enqueued_at
assert r2.status == "running"
def test_load_missing_file_returns_empty(tmp_path):
q = DebateQueue.load(tmp_path / "queue.json")
assert q.rounds == []
def test_load_corrupt_file_returns_empty(tmp_path):
p = tmp_path / "queue.json"
p.write_text("not valid json{{{")
q = DebateQueue.load(p)
assert q.rounds == []
def test_pending_returns_only_pending(tmp_queue):
r1 = tmp_queue.enqueue("issue-1", "WYL-1", "A")
r2 = tmp_queue.enqueue("issue-2", "WYL-2", "B")
tmp_queue.update_status(r1.round_id, "done")
pending = tmp_queue.pending()
assert len(pending) == 1
assert pending[0].round_id == r2.round_id