import asyncio import httpx import pytest from src import gitea_proxy, main from src.security_event_store import SecurityEventStoreError @pytest.mark.anyio async def test_assigned_pull_detail_reports_completion_state(monkeypatch): async def assigned(repository, number): return (repository, number) == ("stackchain/api", 7) async def detail(repository, number): assert (repository, number) == ("stackchain/api", 7) return { "repository": repository, "number": number, "title": "Ship mobile flow", "body": "Ready to merge", "url": "https://forge.example/stackchain/api/pulls/7", "author": "alex", "head_sha": "abc123", "state": "open", "draft": False, "mergeable": True, "merged": False, "ci_state": "success", "files": [{"filename": "src/api.py", "additions": 8, "deletions": 2}], "comments": [{"id": 9, "author": "sam", "body": "Ship it"}], } monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned, raising=False) monkeypatch.setattr(main.gitea_proxy, "pull_completion_detail", detail, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/repos/stackchain/api/pulls/7/detail") assert response.status_code == 200 assert response.headers["cache-control"] == "no-store" assert response.json()["head_sha"] == "abc123" assert response.json()["mergeable"] is True @pytest.mark.anyio async def test_assigned_pull_detail_rejects_unassigned_pull(monkeypatch): async def assigned(repository, number): return False monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/repos/private/secret/pulls/9/detail") assert response.status_code == 404 @pytest.mark.anyio async def test_gitea_assigned_pull_detail_loads_reading_without_review_resources(): requests = [] async def handler(request): requests.append((request.method, request.url.path)) if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "title": "Read this first", "body": "The discussion should not wait for the diff.", "state": "open", "mergeable": True, "head": {"sha": "abc123"}, "user": {"login": "alex"}, }) if request.url.path.endswith("/issues/7/comments"): return httpx.Response(200, json=[{ "id": 9, "body": "Question", "user": {"login": "sam"}, "created_at": "2026-08-07T18:00:00Z", }], headers={"X-Total-Count": "1"}) raise AssertionError(f"review resource requested during read load: {request.url.path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: detail = await gitea_proxy.pull_completion_detail("stackchain/api", 7) finally: await gitea_proxy.stop_client() assert requests == [ ("GET", "/api/v1/repos/stackchain/api/pulls/7"), ("GET", "/api/v1/repos/stackchain/api/issues/7/comments"), ] assert detail["head_sha"] == "abc123" assert detail["conversation"]["comments"][0]["body"] == "Question" assert "files" not in detail assert "ci_state" not in detail @pytest.mark.anyio async def test_assigned_pull_review_endpoint_loads_review_payload_on_demand(monkeypatch): calls = [] async def assigned(repository, number): calls.append(("assigned", repository, number)) return True async def review(repository, number): calls.append(("review", repository, number)) return { "repository": repository, "number": number, "head_sha": "abc123", "state": "open", "draft": False, "mergeable": True, "merged": False, "ci_state": "success", "files": [{"filename": "src/api.py"}], } monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "pull_completion_review", review, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/repos/stackchain/api/pulls/7/review-data") assert response.status_code == 200 assert response.headers["cache-control"] == "no-store" assert response.json()["files"] == [{"filename": "src/api.py"}] assert calls == [ ("assigned", "stackchain/api", 7), ("review", "stackchain/api", 7), ] @pytest.mark.anyio async def test_gitea_pull_check_status_skips_files_reviews_and_diff(): requests = [] async def handler(request): requests.append((request.method, request.url.path)) if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "state": "open", "draft": False, "mergeable": True, "merged": False, "head": {"sha": "abc123"}, }) if request.url.path.endswith("/commits/abc123/status"): return httpx.Response(200, json={ "state": "success", "statuses": [{"context": "tests", "status": "success", "description": "Passed"}], }) raise AssertionError(f"unexpected request: {request.method} {request.url.path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: status = await gitea_proxy.pull_check_status("stackchain/api", 7) finally: await gitea_proxy.stop_client() assert requests == [ ("GET", "/api/v1/repos/stackchain/api/pulls/7"), ("GET", "/api/v1/repos/stackchain/api/commits/abc123/status"), ] assert status == { "repository": "stackchain/api", "number": 7, "head_sha": "abc123", "state": "open", "draft": False, "mergeable": True, "merged": False, "ci_state": "success", "checks": [{"name": "tests", "state": "success", "description": "Passed", "url": ""}], } @pytest.mark.anyio async def test_assigned_pull_checks_endpoint_authorizes_and_returns_status_only(monkeypatch): calls = [] async def assigned(repository, number): calls.append(("assigned", repository, number)) return True async def checks(repository, number): calls.append(("checks", repository, number)) return {"head_sha": "abc123", "ci_state": "pending", "checks": []} monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "pull_check_status", checks) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/repos/stackchain/api/pulls/7/checks") assert response.status_code == 200 assert response.headers["cache-control"] == "no-store" assert response.json() == {"head_sha": "abc123", "ci_state": "pending", "checks": []} assert calls == [ ("assigned", "stackchain/api", 7), ("checks", "stackchain/api", 7), ] @pytest.mark.anyio async def test_gitea_assigned_pull_review_includes_bounded_diff_previews(): async def handler(request): path = request.url.path if path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "title": "Review this patch", "state": "open", "mergeable": True, "head": {"sha": "abc123"}, }) if path.endswith("/pulls/7/files"): return httpx.Response(200, json=[ {"filename": "src/api.py", "status": "modified", "additions": 1, "deletions": 1}, {"filename": "static/logo.png", "status": "modified"}, ]) if path.endswith("/commits/abc123/status"): return httpx.Response(200, json={"state": "success"}) if path.endswith("/issues/7/comments"): return httpx.Response(200, json=[]) if path.endswith("/pulls/7.diff"): return httpx.Response(200, text=( "diff --git a/src/api.py b/src/api.py\n" "--- a/src/api.py\n+++ b/src/api.py\n" "@@ -1 +1 @@\n-old\n+new\n" "diff --git a/static/logo.png b/static/logo.png\n" "Binary files a/static/logo.png and b/static/logo.png differ\n" )) raise AssertionError(f"unexpected request: {request.method} {path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: detail = await gitea_proxy.pull_completion_review("stackchain/api", 7) finally: await gitea_proxy.stop_client() assert detail["files"][0]["diff_available"] is True assert "+new" in detail["files"][0]["diff_lines"] assert detail["files"][1]["diff_binary"] is True assert detail["files"][1]["diff_available"] is False assert "conversation" not in detail @pytest.mark.anyio async def test_assigned_pull_conversation_endpoint_reuses_issue_thread_with_pull_authorization(monkeypatch): calls = [] async def assigned(repository, number): calls.append(("assigned", repository, number)) return True async def conversation(repository, number, page, limit): calls.append(("conversation", repository, number, page, limit)) return {"comments": [{"id": 41}], "page": 3, "older_page": 2, "total": 47} monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "issue_conversation_page", conversation) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get( "/api/v1/repos/stackchain/api/pulls/7/comments?page=3&limit=20" ) assert response.status_code == 200 assert response.headers["cache-control"] == "no-store" assert response.json() == { "comments": [{"id": 41}], "page": 3, "older_page": 2, "total": 47 } assert calls == [ ("assigned", "stackchain/api", 7), ("conversation", "stackchain/api", 7, 3, 20), ] @pytest.mark.anyio async def test_assigned_pull_comment_posts_only_after_assignment_check(monkeypatch): calls = [] async def assigned(repository, number): return True async def comment(repository, number, body): calls.append((repository, number, body)) return {"id": 91, "author": "timmy", "body": body} monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "comment_on_issue", comment) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/comments", json={"body": " Ready to ship. "}, ) assert response.status_code == 201 assert calls == [("stackchain/api", 7, "Ready to ship.")] @pytest.mark.anyio async def test_assigned_pull_merge_requires_current_eligible_head(monkeypatch): calls = [] async def assigned(repository, number): return True async def merge(repository, number, expected_head_sha): calls.append((repository, number, expected_head_sha)) return {"number": number, "merged": True, "state": "closed"} monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 200 assert response.json() == {"number": 7, "merged": True, "state": "closed"} assert calls == [("stackchain/api", 7, "abc123")] @pytest.mark.anyio async def test_assigned_pull_merge_fails_before_gitea_when_audit_reservation_fails( monkeypatch, ): merge_calls = [] class UnavailableJournal: def reserve(self, *_args, **_kwargs): raise SecurityEventStoreError("unavailable") async def assigned(*_args): return True async def merge(*args): merge_calls.append(args) return {"number": 7, "merged": True, "state": "closed"} monkeypatch.setattr(main, "_security_event_store", lambda: UnavailableJournal()) monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 503 assert response.json() == { "error": "Security activity is temporarily unavailable. Nothing was merged." } assert merge_calls == [] @pytest.mark.anyio async def test_assigned_pull_merge_reports_success_and_retains_pending_audit_when_finalize_fails( monkeypatch, ): lifecycle = [] class InterruptedJournal: def reserve(self, kind, *, target): lifecycle.append(("reserve", kind, target)) return "merge-operation" def finalize(self, operation_id): lifecycle.append(("finalize", operation_id)) raise SecurityEventStoreError("unavailable") def discard(self, operation_id): lifecycle.append(("discard", operation_id)) async def assigned(*_args): return True async def merge(*_args): return {"number": 7, "merged": True, "state": "closed"} monkeypatch.setattr(main, "_security_event_store", lambda: InterruptedJournal()) monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 200 assert response.json() == {"number": 7, "merged": True, "state": "closed"} assert lifecycle == [ ("reserve", "pull_merged", "stackchain/api#7"), ("finalize", "merge-operation"), ] @pytest.mark.anyio async def test_assigned_pull_merge_reconciles_acceptance_before_timeout(monkeypatch): calls = [] merged = False class LifecycleJournal: def reserve(self, kind, *, target): calls.append(("reserve", kind, target)) return "merge-operation" def finalize(self, operation_id): calls.append(("finalize", operation_id)) def discard(self, operation_id): calls.append(("discard", operation_id)) async def assigned(repository, number): return True async def merge(repository, number, expected_head_sha): nonlocal merged calls.append(("merge", repository, number, expected_head_sha)) merged = True await asyncio.sleep(0.05) return {"number": number, "merged": True, "state": "closed"} async def confirm(repository, number, expected_head_sha): calls.append(("confirm", repository, number, expected_head_sha)) return merged monkeypatch.setattr(main, "ISSUE_ACTION_TIMEOUT_SECONDS", 0.01) monkeypatch.setattr(main, "_security_event_store", lambda: LifecycleJournal()) monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge) monkeypatch.setattr(main.gitea_proxy, "is_pull_merged_at_head", confirm, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 200 assert response.json() == {"number": 7, "merged": True, "state": "closed"} assert calls == [ ("reserve", "pull_merged", "stackchain/api#7"), ("merge", "stackchain/api", 7, "abc123"), ("confirm", "stackchain/api", 7, "abc123"), ("finalize", "merge-operation"), ] @pytest.mark.anyio async def test_gitea_merge_confirmation_requires_merged_expected_head(): requests = [] async def handler(request): requests.append((request.method, request.url.path)) return httpx.Response(200, json={ "number": 7, "state": "closed", "merged": True, "head": {"sha": "abc123"}, }) gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: confirmed = await gitea_proxy.is_pull_merged_at_head( "stackchain/api", 7, "abc123" ) finally: await gitea_proxy.stop_client() assert confirmed is True assert requests == [("GET", "/api/v1/repos/stackchain/api/pulls/7")] @pytest.mark.anyio async def test_assigned_pull_merge_reports_unresolved_confirmation_without_false_state(monkeypatch): calls = [] async def assigned(repository, number): return True async def merge(repository, number, expected_head_sha): calls.append(("merge", repository, number, expected_head_sha)) raise httpx.ReadTimeout("upstream response was lost") async def confirm(repository, number, expected_head_sha): calls.append(("confirm", repository, number, expected_head_sha)) return False monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge) monkeypatch.setattr(main.gitea_proxy, "is_pull_merged_at_head", confirm) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 202 assert response.json() == { "number": 7, "merged": False, "state": "unknown", "confirmation_pending": True, "error": "Merge confirmation is pending. Check its status before retrying.", } assert calls == [ ("merge", "stackchain/api", 7, "abc123"), ("confirm", "stackchain/api", 7, "abc123"), ] @pytest.mark.anyio async def test_assigned_pull_merge_returns_conflict_without_mutating_stale_head(monkeypatch): async def assigned(repository, number): return True async def merge(*args): raise gitea_proxy.StalePullError("changed") monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 409 assert "New commits" in response.json()["error"] @pytest.mark.anyio @pytest.mark.parametrize( "rejection", [gitea_proxy.StalePullError("changed"), gitea_proxy.PullNotMergeableError("unsafe")], ) async def test_assigned_pull_merge_discards_audit_reservation_after_definite_rejection( monkeypatch, rejection ): lifecycle = [] class LifecycleJournal: def reserve(self, kind, *, target): lifecycle.append(("reserve", kind, target)) return "merge-operation" def discard(self, operation_id): lifecycle.append(("discard", operation_id)) async def assigned(*_args): return True async def merge(*_args): raise rejection monkeypatch.setattr(main, "_security_event_store", lambda: LifecycleJournal()) monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "merge_assigned_pull", merge) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post( "/api/v1/repos/stackchain/api/pulls/7/merge", json={"expected_head_sha": "abc123"}, ) assert response.status_code == 409 assert lifecycle == [ ("reserve", "pull_merged", "stackchain/api#7"), ("discard", "merge-operation"), ] @pytest.mark.anyio async def test_gitea_merge_rejects_failed_ci_without_upstream_mutation(): requests = [] async def handler(request): requests.append((request.method, request.url.path)) if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "state": "open", "draft": False, "mergeable": True, "merged": False, "head": {"sha": "abc123"}, }) return httpx.Response(200, json={"state": "failure"}) gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: with pytest.raises(gitea_proxy.PullNotMergeableError): await gitea_proxy.merge_assigned_pull("stackchain/api", 7, "abc123") finally: await gitea_proxy.stop_client() assert requests == [ ("GET", "/api/v1/repos/stackchain/api/pulls/7"), ("GET", "/api/v1/repos/stackchain/api/commits/abc123/status"), ] @pytest.mark.anyio async def test_assigned_pull_ownership_endpoints_list_handoff_and_release(monkeypatch): calls = [] async def assigned(repository, number): calls.append(("assigned", repository, number)) return True async def candidates(repository): calls.append(("candidates", repository)) return [{"login": "alex", "name": "Alexander"}] async def handoff(repository, number, recipient): calls.append(("handoff", repository, number, recipient)) return {"repository": repository, "number": number, "assignees": [recipient], "recipient": recipient} async def release(repository, number): calls.append(("release", repository, number)) return {"repository": repository, "number": number, "assignees": []} monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned) monkeypatch.setattr(main.gitea_proxy, "pull_handoff_candidates", candidates, raising=False) monkeypatch.setattr(main.gitea_proxy, "handoff_assigned_pull", handoff, raising=False) monkeypatch.setattr(main.gitea_proxy, "release_assigned_pull", release, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: listed = await client.get("/api/v1/repos/stackchain/api/pulls/7/handoff-candidates") transferred = await client.patch( "/api/v1/repos/stackchain/api/pulls/7/handoff", json={"recipient": "alex"} ) released = await client.patch("/api/v1/repos/stackchain/api/pulls/7/release") assert listed.status_code == 200 assert listed.headers["cache-control"] == "no-store" assert listed.json() == [{"login": "alex", "name": "Alexander"}] assert transferred.status_code == 200 assert transferred.json()["recipient"] == "alex" assert released.status_code == 200 assert released.json()["assignees"] == [] assert calls == [ ("assigned", "stackchain/api", 7), ("candidates", "stackchain/api"), ("handoff", "stackchain/api", 7, "alex"), ("release", "stackchain/api", 7), ] @pytest.mark.anyio async def test_gitea_pull_handoff_preserves_coassignees_and_confirms_ownership_exit(): requests = [] async def handler(request): requests.append((request.method, request.url.path, request.content)) if request.url.path.endswith("/user"): return httpx.Response(200, json={"login": "timmy"}) if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "state": "open", "merged": False, "assignees": [{"login": "timmy"}, {"login": "sam"}], }) if request.url.path.endswith("/assignees"): return httpx.Response(200, json=[ {"login": "timmy"}, {"login": "sam"}, {"login": "alex", "full_name": "Alexander"}, ]) if request.method == "PATCH" and request.url.path.endswith("/issues/7"): assert request.read() return httpx.Response(200, json={ "number": 7, "state": "open", "assignees": [{"login": "sam"}, {"login": "alex"}], }) raise AssertionError(f"unexpected request: {request.method} {request.url.path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: result = await gitea_proxy.handoff_assigned_pull("stackchain/api", 7, "alex") finally: await gitea_proxy.stop_client() assert result == { "repository": "stackchain/api", "number": 7, "state": "open", "assignees": ["sam", "alex"], "recipient": "alex", } assert [(method, path) for method, path, _body in requests] == [ ("GET", "/api/v1/user"), ("GET", "/api/v1/repos/stackchain/api/pulls/7"), ("GET", "/api/v1/user"), ("GET", "/api/v1/repos/stackchain/api/assignees"), ("PATCH", "/api/v1/repos/stackchain/api/issues/7"), ] @pytest.mark.anyio async def test_pull_handoff_candidates_are_bounded_and_exclude_invalid_or_current_logins(): async def handler(request): if request.url.path.endswith("/user"): return httpx.Response(200, json={"login": "timmy"}) if request.url.path.endswith("/assignees"): return httpx.Response(200, json=( [{"login": "timmy"}, {"login": "bad login"}, {"login": ""}] + [{"login": f"user-{index:02d}"} for index in range(30)] )) raise AssertionError(f"unexpected request: {request.method} {request.url.path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: candidates = await gitea_proxy.pull_handoff_candidates("stackchain/api") finally: await gitea_proxy.stop_client() assert len(candidates) == 25 assert [candidate["login"] for candidate in candidates] == [ f"user-{index:02d}" for index in range(25) ] @pytest.mark.anyio async def test_gitea_pull_release_removes_current_login_case_insensitively(): async def handler(request): if request.url.path.endswith("/user"): return httpx.Response(200, json={"login": "timmy"}) if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "state": "open", "merged": False, "assignees": [{"login": "Timmy"}, {"login": "sam"}], }) if request.method == "PATCH" and request.url.path.endswith("/issues/7"): assert request.content == b'{"assignees":["sam"]}' return httpx.Response(200, json={ "number": 7, "state": "open", "assignees": [{"login": "sam"}], }) raise AssertionError(f"unexpected request: {request.method} {request.url.path}") gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: released = await gitea_proxy.release_assigned_pull("stackchain/api", 7) finally: await gitea_proxy.stop_client() assert released["assignees"] == ["sam"] @pytest.mark.anyio async def test_gitea_merge_returns_the_exact_merge_commit_for_release_tracking(): async def handler(request): if request.url.path.endswith("/pulls/7"): return httpx.Response(200, json={ "number": 7, "state": "open", "draft": False, "mergeable": True, "merged": False, "head": {"sha": "abc123"}, }) if request.url.path.endswith("/commits/abc123/status"): return httpx.Response(200, json={"state": "success"}) if request.url.path.endswith("/pulls/7/merge"): return httpx.Response(200, json={"merged": True, "sha": "merge456"}) raise AssertionError(request.url.path) gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: result = await gitea_proxy.merge_assigned_pull("stackchain/api", 7, "abc123") finally: await gitea_proxy.stop_client() assert result == { "number": 7, "merged": True, "state": "closed", "merge_commit_sha": "merge456" } @pytest.mark.anyio async def test_release_receipt_matches_only_the_captured_commit_and_reports_checks(monkeypatch): async def receipt(repository, commit_sha): assert (repository, commit_sha) == ("stackchain/api", "merge456") return { "commit_sha": "merge456", "ci_state": "pending", "checks": [{"name": "browser", "state": "pending", "url": ""}], "release": None, } monkeypatch.setattr(main.gitea_proxy, "release_receipt_status", receipt, raising=False) transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get( "/api/v1/repos/stackchain/api/release-receipt/merge456" ) assert response.status_code == 200 assert response.headers["cache-control"] == "no-store" assert response.json()["checks"] == [ {"name": "browser", "state": "pending", "url": ""} ] assert response.json()["release"] is None @pytest.mark.anyio async def test_gitea_release_receipt_ignores_other_commits_and_normalizes_matching_assets(): forge = gitea_proxy.GITEA_URL.rstrip("/") async def handler(request): if request.url.path.endswith("/commits/merge456/status"): return httpx.Response(200, json={ "state": "success", "statuses": [{"context": "browser", "status": "success", "target_url": ""}], }) if request.url.path.endswith("/releases"): return httpx.Response(200, json=[ {"tag_name": "newer", "target_commitish": "other789", "html_url": f"{forge}/stackchain/api/releases/tag/newer"}, { "tag_name": "rc-42", "target_commitish": "merge456", "html_url": f"{forge}/stackchain/api/releases/tag/rc-42", "assets": [ {"name": "manifest.json", "browser_download_url": f"{forge}/stackchain/api/releases/download/rc-42/manifest.json"}, ], }, ]) raise AssertionError(request.url.path) gitea_proxy.start_client(transport=httpx.MockTransport(handler)) try: result = await gitea_proxy.release_receipt_status("stackchain/api", "merge456") finally: await gitea_proxy.stop_client() assert result["commit_sha"] == "merge456" assert result["ci_state"] == "success" assert result["checks"] == [{"name": "browser", "state": "success", "url": ""}] assert result["release"] == { "tag": "rc-42", "url": f"{forge}/stackchain/api/releases/tag/rc-42", "assets": [{ "name": "manifest.json", "url": f"{forge}/stackchain/api/releases/download/rc-42/manifest.json", }], }