8c9a174ddc
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>
137 lines
4.3 KiB
Python
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
|