import asyncio import json import pytest from src import gitea_proxy from src import main @pytest.mark.anyio async def test_work_collections_include_supported_review_request_search(monkeypatch): requested_paths = [] async def fake_fetch(path): requested_paths.append(path) return [] monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) assert await gitea_proxy.issues() == [] assert await gitea_proxy.pull_requests() == [] assert requested_paths == [ "repos/issues/search?state=open&assigned=true&type=issues&limit=50", "repos/issues/search?state=open&assigned=true&type=pulls&limit=50", "repos/issues/search?state=open&review_requested=true&type=pulls&limit=50", ] @pytest.mark.anyio async def test_pull_requests_merge_assignment_and_review_responsibilities(monkeypatch): assigned = { "id": 11, "number": 7, "title": "Review API", "repository": {"full_name": "stackchain/api"}, } review_only = { "id": 12, "number": 8, "title": "Review mobile", "repository": {"full_name": "stackchain/mobile"}, } async def fake_fetch(path): if "assigned=true" in path: return [assigned] return [assigned.copy(), review_only] monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) pulls = await gitea_proxy.pull_requests() assert [pull["id"] for pull in pulls] == [11, 12] assert pulls[0]["work_reasons"] == ["assigned_to_me", "review_requested"] assert pulls[1]["work_reasons"] == ["review_requested"] @pytest.mark.anyio async def test_pull_request_searches_start_concurrently(monkeypatch): started = [asyncio.Event(), asyncio.Event()] release = asyncio.Event() async def fake_fetch(path): index = 0 if "assigned=true" in path else 1 started[index].set() await release.wait() return [] monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) task = asyncio.create_task(gitea_proxy.pull_requests()) try: await asyncio.wait_for( asyncio.gather(*(event.wait() for event in started)), timeout=1 ) finally: release.set() assert await task == [] @pytest.mark.anyio async def test_requested_review_guard_uses_only_dedicated_review_search(monkeypatch): requested_paths = [] async def fake_fetch(path): requested_paths.append(path) return [ { "number": 7, "repository": {"full_name": "stackchain/api"}, }, { "number": 8, "repository": {"full_name": "stackchain/api"}, }, ] monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) assert await gitea_proxy.is_requested_review("stackchain/api", 7) is True assert await gitea_proxy.is_requested_review("stackchain/api", 9) is False assert await gitea_proxy.is_requested_review("stackchain/private", 7) is False assert requested_paths == [ "repos/issues/search?state=open&review_requested=true&type=pulls&limit=50", "repos/issues/search?state=open&review_requested=true&type=pulls&limit=50", "repos/issues/search?state=open&review_requested=true&type=pulls&limit=50", ] @pytest.mark.anyio async def test_context_preserves_repository_and_update_time_for_cross_repo_work(monkeypatch): async def user(): return {"id": 1, "login": "timmy"} async def repositories(): return [] async def issues(): return [{ "id": 10, "number": 7, "title": "Ship mobile flow", "state": "open", "labels": [{"name": "P0"}], "assignees": [{"login": "timmy"}], "repository": {"full_name": "stackchain/mobile"}, "updated_at": "2026-08-06T12:00:00Z", "html_url": "https://forge.example/stackchain/mobile/issues/7", }] async def pulls(): return [{ "id": 11, "number": 7, "title": "Review API", "state": "open", "user": {"login": "alex"}, "labels": [{"name": "priority-high"}], "assignees": [{"login": "timmy"}], "work_reasons": ["assigned_to_me", "review_requested"], "repository": {"full_name": "stackchain/api"}, "updated_at": "2026-08-06T11:00:00Z", "html_url": "https://forge.example/stackchain/api/pulls/7", }] monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", repositories) monkeypatch.setattr(main, "issues", issues) monkeypatch.setattr(main, "pull_requests", pulls) payload = json.loads((await main.context()).body) assert payload["issues"][0]["repository"] == "stackchain/mobile" assert payload["issues"][0]["updated_at"] == "2026-08-06T12:00:00Z" assert payload["pull_requests"][0]["repository"] == "stackchain/api" assert payload["pull_requests"][0]["updated_at"] == "2026-08-06T11:00:00Z" assert payload["pull_requests"][0]["labels"] == ["priority-high"] assert payload["pull_requests"][0]["assignees"] == ["timmy"] assert payload["pull_requests"][0]["work_reasons"] == [ "assigned_to_me", "review_requested", ] @pytest.mark.anyio async def test_pull_review_detail_combines_pr_files_status_and_reviews(monkeypatch): requested_paths = [] async def fake_fetch(path): requested_paths.append(path) if path.endswith("/pulls/7"): return { "title": "Review API", "body": "Please check the retry flow.", "html_url": "https://forge.example/stackchain/api/pulls/7", "user": {"login": "alex"}, "head": {"sha": "abc123"}, } if path.endswith("/files"): return [ {"filename": "src/api.py", "status": "modified", "additions": 8, "deletions": 2}, None, ] if "/commits/abc123/status" in path: return {"state": "success"} return [ {"user": {"login": "sam"}, "state": "APPROVED", "body": "Looks good"}, "malformed", ] monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) async def fake_fetch_text(path, max_bytes): assert path == "repos/stackchain/api/pulls/7.diff" assert max_bytes == gitea_proxy.REVIEW_DIFF_MAX_BYTES return "", False monkeypatch.setattr(gitea_proxy, "fetch_text", fake_fetch_text) detail = await gitea_proxy.pull_review_detail("stackchain/api", 7) assert requested_paths == [ "repos/stackchain/api/pulls/7", "repos/stackchain/api/pulls/7/files", "repos/stackchain/api/commits/abc123/status", "repos/stackchain/api/pulls/7/reviews", ] assert detail["author"] == "alex" assert detail["head_sha"] == "abc123" assert detail["ci_state"] == "success" assert detail["files"][0]["filename"] == "src/api.py" assert detail["reviews"][0]["state"] == "APPROVED" assert len(detail["files"]) == 1 assert len(detail["reviews"]) == 1 @pytest.mark.anyio async def test_pull_review_detail_fetches_independent_head_resources_concurrently(monkeypatch): started = {name: asyncio.Event() for name in ("files", "status", "reviews", "diff")} release = asyncio.Event() async def wait_for_release(name, result): started[name].set() await release.wait() return result async def fake_fetch(path): if path.endswith("/pulls/7"): return {"head": {"sha": "abc123"}, "user": {"login": "alex"}} if path.endswith("/files"): return await wait_for_release("files", []) if path.endswith("/reviews"): return await wait_for_release("reviews", []) return await wait_for_release("status", {"state": "success"}) async def fake_fetch_text(path, max_bytes): return await wait_for_release("diff", ("", False)) monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) monkeypatch.setattr(gitea_proxy, "fetch_text", fake_fetch_text) detail_task = asyncio.create_task( gitea_proxy.pull_review_detail("stackchain/api", 7) ) try: await asyncio.wait_for( asyncio.gather(*(event.wait() for event in started.values())), timeout=1 ) finally: release.set() detail = await detail_task assert detail["head_sha"] == "abc123" @pytest.mark.anyio async def test_pull_review_detail_attaches_bounded_per_file_diff_previews(monkeypatch): async def fake_fetch(path): if path.endswith("/pulls/7"): return { "title": "Review API", "head": {"sha": "abc123"}, "user": {"login": "alex"}, } if path.endswith("/files"): return [ {"filename": "src/api.py", "status": "modified"}, {"filename": "assets/logo.png", "status": "modified"}, ] if path.endswith("/reviews"): return [] return {"state": "success"} diff = """diff --git a/src/api.py b/src/api.py index 123..456 100644 --- a/src/api.py +++ b/src/api.py @@ -1,2 +1,3 @@ context -old +new diff --git a/assets/logo.png b/assets/logo.png Binary files a/assets/logo.png and b/assets/logo.png differ """ async def fake_fetch_text(path, max_bytes): assert path == "repos/stackchain/api/pulls/7.diff" return diff, True monkeypatch.setattr(gitea_proxy, "fetch", fake_fetch) monkeypatch.setattr(gitea_proxy, "fetch_text", fake_fetch_text) detail = await gitea_proxy.pull_review_detail("stackchain/api", 7) source, binary = detail["files"] assert source["diff_lines"] == [ "@@ -1,2 +1,3 @@", " context", "-old ", "+new ", ] assert source["diff_truncated"] is True assert source["diff_available"] is True assert binary["diff_available"] is False assert binary["diff_binary"] is True