"""C-25 owner-return collection pages past 200 replies (STATE-WP-0094-T03).""" from __future__ import annotations import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) from consistency_check import ConsistencyReport, _check_blocked_unblock_sweep # noqa: E402 BODY = ( "```task\nid: DEMO-WP-0001-T01\nstatus: wait\nblocked_on: message-from:provider\n```\n\n" "2026-10-01: waiting on provider." ) def _replies(total: int) -> list[dict]: # Newest first, as the hub orders them. return [ {"id": f"reply-{i:04d}", "from_agent": "provider", "created_at": f"2026-10-03T{(i // 60) % 24:02d}:{i % 60:02d}:00Z", "read_at": None} for i in range(total) ][::-1] def _run(tmp_path, monkeypatch, fake_get): monkeypatch.setattr("consistency_check._api_get", fake_get) report = ConsistencyReport(repo_slug="demo", repo_path=str(tmp_path)) _check_blocked_unblock_sweep( "http://unused", "demo", tmp_path, [(tmp_path / "plan.md", {"status": "active", "flavor": "implementation"}, BODY)], report, ) return report def test_collects_every_reply_across_pages_with_stable_ids(tmp_path, monkeypatch): replies = _replies(450) queries = [] def paged(_api_base, _path, params): queries.append(dict(params)) start = params["offset"] return replies[start:start + params["limit"]] report = _run(tmp_path, monkeypatch, paged) assert [q["offset"] for q in queries] == [0, 200, 400] assert len(report.issues) == 1 ids = report.issues[0]._fix_context["message_ids"] assert len(ids) == len(set(ids)) == 450 assert ids == sorted(ids) # chronological, so the dedup key is stable assert report.issues[0]._fix_context["task_record_id"] == "DEMO-WP-0001-T01" # Overlapping pages (new mail shifting offsets) are deduplicated by id. shifted = [{"id": "reply-new", "from_agent": "provider", "created_at": "2026-10-03T23:59:00Z", "read_at": None}] + replies def shifting(_api_base, _path, params): source = replies if params["offset"] == 0 else shifted return source[params["offset"]:params["offset"] + params["limit"]] again = _run(tmp_path, monkeypatch, shifting) assert len(again.issues[0]._fix_context["message_ids"]) == 450 def test_hub_ignoring_offset_is_reported_not_silently_capped(tmp_path, monkeypatch): replies = _replies(200) report = _run(tmp_path, monkeypatch, lambda _a, _p, params: replies) checks = [(issue.check_id, "incomplete" in issue.message) for issue in report.issues] assert ("C-25", True) in checks owner = [i for i in report.issues if i._fix_context.get("task_record_id")] assert len(owner[0]._fix_context["message_ids"]) == 200 @pytest.mark.asyncio async def test_messages_api_offset_pages_are_disjoint_and_ordered(client): for index in range(5): response = await client.post("/messages/", json={ "from_agent": "provider", "to_agent": "demo", "subject": f"reply {index}", "body": "done", }) assert response.status_code == 201, response.text pages = [] for offset in (0, 2, 4): response = await client.get("/messages/", params={ "to_agent": "demo", "from_agent": "provider", "limit": 2, "offset": offset}) assert response.status_code == 200, response.text pages.append([row["id"] for row in response.json()]) flat = [item for page in pages for item in page] assert [len(page) for page in pages] == [2, 2, 1] assert len(set(flat)) == 5 bad = await client.get("/messages/", params={"offset": -1}) assert bad.status_code == 422