from __future__ import annotations import asyncio import hashlib import hmac import importlib import json import logging from fastapi.testclient import TestClient from agent import webapp from agent.tools import request_pr_review as request_pr_review_tool from agent.utils import github_comments from agent.utils import slack as slack_utils from agent.utils.slack import GitHubPrRef request_pr_review_module = importlib.import_module("agent.tools.request_pr_review") _TEST_WEBHOOK_SECRET = "test-secret-for-webhook" _TEST_SLACK_SECRET = "test-slack-secret" def _sign_body(body: bytes, secret: str = _TEST_WEBHOOK_SECRET) -> str: """Compute the X-Hub-Signature-256 header value for raw bytes.""" sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return f"sha256={sig}" def _post_github_webhook(client: TestClient, event_type: str, payload: dict) -> object: """Send a signed GitHub webhook POST request.""" body = json.dumps(payload, separators=(",", ":")).encode() return client.post( "/webhooks/github", content=body, headers={ "X-GitHub-Event": event_type, "X-Hub-Signature-256": _sign_body(body), "Content-Type": "application/json", }, ) def _sign_slack_body(body: bytes, timestamp: str = "1700000000") -> str: base_string = f"v0:{timestamp}:{body.decode()}" sig = hmac.new(_TEST_SLACK_SECRET.encode(), base_string.encode(), hashlib.sha256).hexdigest() return f"v0={sig}" def _post_slack_webhook(client: TestClient, payload: dict) -> object: body = json.dumps(payload, separators=(",", ":")).encode() timestamp = "1700000000" return client.post( "/webhooks/slack", content=body, headers={ "X-Slack-Request-Timestamp": timestamp, "X-Slack-Signature": _sign_slack_body(body, timestamp), "Content-Type": "application/json", }, ) def test_generate_thread_id_from_github_issue_is_deterministic() -> None: first = webapp.generate_thread_id_from_github_issue("12345") second = webapp.generate_thread_id_from_github_issue("12345") assert first == second assert len(first) == 36 def test_build_github_issue_prompt_includes_issue_context() -> None: prompt = webapp.build_github_issue_prompt( {"owner": "langchain-ai", "name": "open-swe"}, 42, "12345", "Fix the flaky test", "The test is failing intermittently.", [{"author": "octocat", "body": "Please take a look", "created_at": "2026-03-09T00:00:00Z"}], github_login="octocat", ) assert "Fix the flaky test" in prompt assert "The test is failing intermittently." in prompt assert "Please take a look" in prompt assert "GH_TOKEN=dummy gh issue comment" in prompt def test_build_github_issue_followup_prompt_only_includes_comment() -> None: from agent.dashboard import user_mappings user_mappings.prime_cache( [{"github_login": "bracesproul", "work_email": "brace@x.com", "status": "active"}] ) try: prompt = webapp.build_github_issue_followup_prompt("bracesproul", "Please handle this") finally: user_mappings.clear_cache() assert prompt == "**bracesproul:**\nPlease handle this" assert "## Repository" not in prompt assert "## Title" not in prompt def test_reviewer_enablement_uses_dashboard_opt_in(monkeypatch) -> None: seen: dict[str, str] = {} async def fake_is_review_repo_enabled(owner: str, name: str) -> bool: seen["owner"] = owner seen["name"] = name return owner == "langchain-ai" and name == "open-swe-app" monkeypatch.setattr(webapp, "is_review_repo_enabled", fake_is_review_repo_enabled) assert ( asyncio.run( webapp._is_repo_enabled_for_review({"owner": "langchain-ai", "name": "open-swe-app"}) ) is True ) assert seen == {"owner": "langchain-ai", "name": "open-swe-app"} assert ( asyncio.run( webapp._is_repo_enabled_for_review({"owner": "langchain-ai", "name": "open-swe"}) ) is False ) def test_github_webhook_accepts_issue_events(monkeypatch) -> None: called: dict[str, object] = {} async def fake_process_github_issue(payload: dict[str, object], event_type: str) -> None: called["payload"] = payload called["event_type"] = event_type monkeypatch.setattr(webapp, "process_github_issue", fake_process_github_issue) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "issues", { "action": "opened", "issue": { "id": 12345, "number": 42, "title": "@openswe fix the flaky test", "body": "The test is failing intermittently.", }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "accepted" assert called["event_type"] == "issues" def test_github_webhook_ignores_issue_events_without_body_or_title_change(monkeypatch) -> None: called = False async def fake_process_github_issue(payload: dict[str, object], event_type: str) -> None: nonlocal called called = True monkeypatch.setattr(webapp, "process_github_issue", fake_process_github_issue) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "issues", { "action": "edited", "changes": {"labels": {"from": []}}, "issue": { "id": 12345, "number": 42, "title": "@openswe fix the flaky test", "body": "The test is failing intermittently.", }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "ignored" assert called is False def test_github_webhook_accepts_issue_comment_events(monkeypatch) -> None: called: dict[str, object] = {} async def fake_process_github_issue(payload: dict[str, object], event_type: str) -> None: called["payload"] = payload called["event_type"] = event_type monkeypatch.setattr(webapp, "process_github_issue", fake_process_github_issue) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "issue_comment", { "action": "created", "issue": {"id": 12345, "number": 42, "title": "Fix the flaky test"}, "comment": {"body": "@openswe please handle this"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "accepted" assert called["event_type"] == "issue_comment" def test_github_webhook_ignores_unmentioned_comment_without_info_log(monkeypatch, caplog) -> None: async def fake_process_github_pr_comment(payload: dict[str, object], event_type: str) -> None: raise AssertionError("process_github_pr_comment should not be called") monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_github_pr_comment) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) caplog.set_level(logging.INFO, logger=webapp.logger.name) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request_review_comment", { "action": "created", "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "comment": {"body": "Looks good to me"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json() == { "status": "ignored", "reason": "Comment does not mention @openswe or @open-swe", } assert "does not mention @openswe or @open-swe" not in caplog.text def test_github_webhook_routes_review_comment_reply_without_tag(monkeypatch) -> None: called: dict[str, object] = {} async def fake_process_github_review_finding_reply(payload: dict[str, object]) -> None: called["payload"] = payload async def fake_repo_enabled(_repo_config: dict[str, str]) -> bool: return True monkeypatch.setattr( webapp, "process_github_review_finding_reply", fake_process_github_review_finding_reply ) monkeypatch.setattr(webapp, "_is_repo_enabled_for_review", fake_repo_enabled) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request_review_comment", { "action": "created", "comment": { "id": 222, "in_reply_to_id": 111, "body": "This is handled elsewhere, so the finding is invalid.", }, "pull_request": { "number": 1244, "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "accepted" payload = called["payload"] assert isinstance(payload, dict) assert payload["comment"]["in_reply_to_id"] == 111 def test_process_github_review_finding_reply_uses_rereview_config(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_thread_metadata_safe(_thread_id: str) -> dict[str, object]: return {"kind": webapp.REVIEWER_THREAD_KIND} async def fake_get_token_with_expiry() -> tuple[str, str]: return "app-token", "2026-01-01T00:00:00Z" async def fake_persist_token( thread_id: str, token: str, *, expires_at: str | None = None ) -> str: captured["persist"] = (thread_id, token, expires_at) return "encrypted" async def fake_fetch_threads(**_kwargs: object) -> list[dict[str, object]]: return [] async def fake_reconcile(_thread_id: str, _threads: list[dict[str, object]]) -> None: return None async def fake_list_findings(_thread_id: str) -> list[dict[str, object]]: return [{"id": "f_1", "github_review_comment_id": 111}] async def fake_append_interaction( _thread_id: str, finding_id: str, interaction: dict[str, object] ) -> dict[str, object]: captured["interaction"] = (finding_id, interaction) return {} async def fake_is_thread_active(_thread_id: str) -> bool: return False async def fake_store_current_run_id(_thread_id: str, _run: object) -> None: return None class _FakeRunsClient: async def create(self, thread_id: str, graph: str, **kwargs) -> dict[str, str]: captured["thread_id"] = thread_id captured["graph"] = graph captured["kwargs"] = kwargs return {"run_id": "run-1"} class _FakeLangGraphClient: runs = _FakeRunsClient() monkeypatch.setattr(webapp, "_get_thread_metadata_safe", fake_get_thread_metadata_safe) monkeypatch.setattr( webapp, "get_github_app_installation_token_with_expiry", fake_get_token_with_expiry ) monkeypatch.setattr(webapp, "persist_encrypted_github_token", fake_persist_token) monkeypatch.setattr(webapp, "fetch_pr_review_threads", fake_fetch_threads) monkeypatch.setattr(webapp, "reconcile_findings_with_review_threads", fake_reconcile) monkeypatch.setattr(webapp, "list_reviewer_findings", fake_list_findings) monkeypatch.setattr(webapp, "append_finding_interaction", fake_append_interaction) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "_store_current_reviewer_run_id", fake_store_current_run_id) monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClient()) asyncio.run( webapp.process_github_review_finding_reply( { "comment": { "id": 222, "in_reply_to_id": 111, "body": "Why is this still a problem?", "created_at": "2026-05-27T00:00:00Z", }, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat", "id": 123}, } ) ) kwargs = captured["kwargs"] assert isinstance(kwargs, dict) config = kwargs["config"]["configurable"] assert config["reviewer_event"] == "finding_reply" assert config["re_review"] is True assert config["finding_reply_id"] == "f_1" def test_process_github_review_finding_reply_queues_reply_body_when_active(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_thread_metadata_safe(_thread_id: str) -> dict[str, object]: return {"kind": webapp.REVIEWER_THREAD_KIND} async def fake_get_token_with_expiry() -> tuple[str, str]: return "app-token", "2026-01-01T00:00:00Z" async def fake_persist_token( _thread_id: str, _token: str, *, expires_at: str | None = None ) -> str: captured["expires_at"] = expires_at return "encrypted" async def fake_fetch_threads(**_kwargs: object) -> list[dict[str, object]]: return [] async def fake_reconcile(_thread_id: str, _threads: list[dict[str, object]]) -> None: return None async def fake_list_findings(_thread_id: str) -> list[dict[str, object]]: return [{"id": "f_1", "github_review_comment_id": 111}] async def fake_append_interaction( _thread_id: str, _finding_id: str, _interaction: dict[str, object] ) -> dict[str, object]: return {} async def fake_is_thread_active(_thread_id: str) -> bool: return True async def fake_queue_message_for_thread(thread_id: str, message_content: object) -> bool: captured["queued"] = {"thread_id": thread_id, "message_content": message_content} return True def fail_get_client(*_args: object, **_kwargs: object) -> None: raise AssertionError("active reviewer thread should not create a new run") monkeypatch.setattr(webapp, "_get_thread_metadata_safe", fake_get_thread_metadata_safe) monkeypatch.setattr( webapp, "get_github_app_installation_token_with_expiry", fake_get_token_with_expiry ) monkeypatch.setattr(webapp, "persist_encrypted_github_token", fake_persist_token) monkeypatch.setattr(webapp, "fetch_pr_review_threads", fake_fetch_threads) monkeypatch.setattr(webapp, "reconcile_findings_with_review_threads", fake_reconcile) monkeypatch.setattr(webapp, "list_reviewer_findings", fake_list_findings) monkeypatch.setattr(webapp, "append_finding_interaction", fake_append_interaction) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "queue_message_for_thread", fake_queue_message_for_thread) monkeypatch.setattr(webapp, "get_client", fail_get_client) asyncio.run( webapp.process_github_review_finding_reply( { "comment": { "id": 222, "in_reply_to_id": 111, "body": "\nThis is handled elsewhere.", "created_at": "2026-05-27T00:00:00Z", }, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat", "id": 123}, } ) ) queued = captured["queued"] assert isinstance(queued, dict) message_content = queued["message_content"] assert isinstance(message_content, str) assert "Open SWE finding f_1" in message_content assert "untrusted data from GitHub" in message_content assert "This is handled elsewhere." in message_content assert "\nThis is handled elsewhere." not in message_content assert "" in message_content def test_github_webhook_ignores_unsupported_comment_action(monkeypatch) -> None: async def fake_process_github_pr_comment(payload: dict[str, object], event_type: str) -> None: raise AssertionError("process_github_pr_comment should not be called") monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_github_pr_comment) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request_review", { "action": "dismissed", "review": {"body": "@openswe please check this"}, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json() == { "status": "ignored", "reason": "Unsupported GitHub pull_request_review action: dismissed", } def test_github_webhook_blocks_reviewer_repo_not_enabled_in_dashboard(monkeypatch) -> None: called = False async def fake_process_github_pr_review_request(payload: dict[str, object]) -> None: nonlocal called called = True monkeypatch.setattr( webapp, "process_github_pr_review_request", fake_process_github_pr_review_request ) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) async def fake_is_review_repo_enabled(_owner: str, _name: str) -> bool: return False monkeypatch.setattr(webapp, "is_review_repo_enabled", fake_is_review_repo_enabled) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request", { "action": "review_requested", "requested_reviewer": {"login": "open-swe[bot]"}, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/public-demo/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "public-demo"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json() == {"status": "ignored", "reason": "Repository not enabled for review"} assert called is False def test_github_webhook_accepts_open_swe_review_requested(monkeypatch) -> None: called: dict[str, object] = {} async def fake_process_github_pr_review_request(payload: dict[str, object]) -> None: called["payload"] = payload monkeypatch.setattr( webapp, "process_github_pr_review_request", fake_process_github_pr_review_request ) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request", { "action": "review_requested", "requested_reviewer": {"login": "open-swe[bot]"}, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "accepted" assert called["payload"]["requested_reviewer"]["login"] == "open-swe[bot]" def test_github_webhook_ignores_review_requested_for_other_reviewer(monkeypatch) -> None: called = False async def fake_process_github_pr_review_request(payload: dict[str, object]) -> None: nonlocal called called = True monkeypatch.setattr( webapp, "process_github_pr_review_request", fake_process_github_pr_review_request ) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) client = TestClient(webapp.app) response = _post_github_webhook( client, "pull_request", { "action": "review_requested", "requested_reviewer": {"login": "someone-else"}, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json()["status"] == "ignored" assert called is False def test_slack_webhook_routes_review_command_to_agent(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_slack_repo_config( channel_id: str, thread_ts: str, slack_user_id: str | None = None ) -> dict[str, str]: captured["repo_config_request"] = { "channel_id": channel_id, "thread_ts": thread_ts, "slack_user_id": slack_user_id, } return {"owner": "langchain-ai", "name": "open-swe"} async def fake_process_slack_mention( event_data: dict[str, object], repo_config: dict[str, str] ) -> None: captured["event_data"] = event_data captured["repo_config"] = repo_config monkeypatch.setattr(webapp, "SLACK_SIGNING_SECRET", _TEST_SLACK_SECRET) monkeypatch.setattr(webapp, "SLACK_BOT_USER_ID", "UBOT") monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe") monkeypatch.setattr(slack_utils.time, "time", lambda: 1700000000) monkeypatch.setattr(webapp, "get_slack_repo_config", fake_get_slack_repo_config) monkeypatch.setattr(webapp, "process_slack_mention", fake_process_slack_mention) client = TestClient(webapp.app) response = _post_slack_webhook( client, { "type": "event_callback", "event": { "type": "app_mention", "channel": "C123", "ts": "1700000000.000100", "user": "U123", "text": "<@UBOT> review https://github.com/langchain-ai/open-swe/pull/1244", }, }, ) assert response.status_code == 200 assert response.json()["message"] == "Slack mention queued" assert captured["repo_config"] == {"owner": "langchain-ai", "name": "open-swe"} event_data = captured["event_data"] assert isinstance(event_data, dict) assert event_data["text"] == "<@UBOT> review https://github.com/langchain-ai/open-swe/pull/1244" def test_slack_webhook_malformed_review_command_starts_agent(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_slack_repo_config( channel_id: str, thread_ts: str, slack_user_id: str | None = None ) -> dict[str, str]: return {"owner": "langchain-ai", "name": "open-swe"} async def fake_process_slack_mention( event_data: dict[str, object], repo_config: dict[str, str] ) -> None: captured["event_data"] = event_data captured["repo_config"] = repo_config monkeypatch.setattr(webapp, "SLACK_SIGNING_SECRET", _TEST_SLACK_SECRET) monkeypatch.setattr(webapp, "SLACK_BOT_USER_ID", "UBOT") monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe") monkeypatch.setattr(slack_utils.time, "time", lambda: 1700000000) monkeypatch.setattr(webapp, "get_slack_repo_config", fake_get_slack_repo_config) monkeypatch.setattr(webapp, "process_slack_mention", fake_process_slack_mention) client = TestClient(webapp.app) response = _post_slack_webhook( client, { "type": "event_callback", "event": { "type": "app_mention", "channel": "C123", "ts": "1700000000.000100", "user": "U123", "text": "<@UBOT> review https://github.com/langchain-ai/open-swe/issues/1244", }, }, ) assert response.status_code == 200 assert response.json()["message"] == "Slack mention queued" assert captured["repo_config"] == {"owner": "langchain-ai", "name": "open-swe"} event_data = captured["event_data"] assert isinstance(event_data, dict) assert ( event_data["text"] == "<@UBOT> review https://github.com/langchain-ai/open-swe/issues/1244" ) def test_slack_webhook_non_pr_review_request_starts_agent(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_slack_repo_config( channel_id: str, thread_ts: str, slack_user_id: str | None = None ) -> dict[str, str]: captured["repo_config_request"] = { "channel_id": channel_id, "thread_ts": thread_ts, "slack_user_id": slack_user_id, } return {"owner": "langchain-ai", "name": "open-swe"} async def fake_process_slack_mention( event_data: dict[str, object], repo_config: dict[str, str] ) -> None: captured["event_data"] = event_data captured["repo_config"] = repo_config monkeypatch.setattr(webapp, "SLACK_SIGNING_SECRET", _TEST_SLACK_SECRET) monkeypatch.setattr(webapp, "SLACK_BOT_USER_ID", "UBOT") monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe") monkeypatch.setattr(slack_utils.time, "time", lambda: 1700000000) monkeypatch.setattr(webapp, "get_slack_repo_config", fake_get_slack_repo_config) monkeypatch.setattr(webapp, "process_slack_mention", fake_process_slack_mention) monkeypatch.setattr( webapp, "_is_repo_allowed", lambda repo_config: (_ for _ in ()).throw( AssertionError("Slack webhook should not gate inferred repos with allowlists") ), ) client = TestClient(webapp.app) response = _post_slack_webhook( client, { "type": "event_callback", "event": { "type": "app_mention", "channel": "C123", "ts": "1700000000.000100", "user": "U123", "text": "<@UBOT> review this branch", }, }, ) assert response.status_code == 200 assert response.json()["message"] == "Slack mention queued" assert captured["repo_config"] == {"owner": "langchain-ai", "name": "open-swe"} event_data = captured["event_data"] assert isinstance(event_data, dict) assert event_data["text"] == "<@UBOT> review this branch" def test_slack_webhook_threaded_followup_uses_parent_thread_ts(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_slack_repo_config( channel_id: str, thread_ts: str, slack_user_id: str | None = None ) -> dict[str, str]: captured["repo_config_request"] = { "channel_id": channel_id, "thread_ts": thread_ts, "slack_user_id": slack_user_id, } return {"owner": "langchain-ai", "name": "open-swe"} async def fake_process_slack_mention( event_data: dict[str, object], repo_config: dict[str, str] ) -> None: captured["event_data"] = event_data captured["repo_config"] = repo_config monkeypatch.setattr(webapp, "SLACK_SIGNING_SECRET", _TEST_SLACK_SECRET) monkeypatch.setattr(webapp, "SLACK_BOT_USER_ID", "UBOT") monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe") monkeypatch.setattr(slack_utils.time, "time", lambda: 1700000000) monkeypatch.setattr(webapp, "get_slack_repo_config", fake_get_slack_repo_config) monkeypatch.setattr(webapp, "process_slack_mention", fake_process_slack_mention) client = TestClient(webapp.app) response = _post_slack_webhook( client, { "type": "event_callback", "event": { "type": "app_mention", "channel": "C123", "ts": "1700000000.000200", "thread_ts": "1700000000.000100", "user": "U123", "text": "<@UBOT> continue on the branch", }, }, ) assert response.status_code == 200 assert response.json()["message"] == "Slack mention queued" assert captured["repo_config_request"] == { "channel_id": "C123", "thread_ts": "1700000000.000100", "slack_user_id": "U123", } event_data = captured["event_data"] assert isinstance(event_data, dict) assert event_data["thread_ts"] == "1700000000.000100" assert event_data["event_ts"] == "1700000000.000200" def test_process_slack_pr_review_request_posts_trace_reply(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_trigger_pr_review_from_ref( pr_ref: GitHubPrRef, *, source: str, github_login: str = "", github_user_id: int | None = None, slack_channel_id: str = "", slack_thread_ts: str = "", ) -> dict[str, object]: captured["pr_ref"] = pr_ref captured["source"] = source captured["slack_channel_id"] = slack_channel_id captured["slack_thread_ts"] = slack_thread_ts return {"success": True, "thread_id": "reviewer-thread-id", "pr_url": pr_ref.url} async def fake_post_slack_trace_reply( channel_id: str, thread_ts: str, thread_id: str, *, include_dashboard_link: bool = True ) -> None: captured["trace_reply"] = { "channel_id": channel_id, "thread_ts": thread_ts, "thread_id": thread_id, "include_dashboard_link": include_dashboard_link, } async def fake_set_slack_assistant_status(channel_id: str, thread_ts: str) -> bool: captured.setdefault("status_calls", []).append((channel_id, thread_ts)) return True monkeypatch.setattr(webapp, "trigger_pr_review_from_ref", fake_trigger_pr_review_from_ref) monkeypatch.setattr(webapp, "post_slack_trace_reply", fake_post_slack_trace_reply) monkeypatch.setattr(webapp, "set_slack_assistant_status", fake_set_slack_assistant_status) asyncio.run( webapp.process_slack_pr_review_request( GitHubPrRef( owner="langchain-ai", repo="open-swe", number=1244, url="https://github.com/langchain-ai/open-swe/pull/1244", ), "C123", "1700000000.000100", ) ) assert captured["source"] == "slack" assert captured["slack_channel_id"] == "C123" assert captured["slack_thread_ts"] == "1700000000.000100" assert captured["trace_reply"] == { "channel_id": "C123", "thread_ts": "1700000000.000100", "thread_id": "reviewer-thread-id", "include_dashboard_link": False, } assert captured["status_calls"] == [ ("C123", "1700000000.000100"), ("C123", "1700000000.000100"), ] def test_process_github_pr_review_request_creates_reviewer_run(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_github_app_installation_token() -> str | None: return "app-token" async def fake_get_github_app_installation_token_with_expiry() -> tuple[str | None, str | None]: return "app-token", None async def fake_persist_encrypted_github_token( thread_id: str, token: str, *, expires_at: str | None = None ) -> str: captured["persist_thread_id"] = thread_id captured["persist_token"] = token captured["persist_expires_at"] = expires_at return "encrypted-token" async def fake_is_thread_active(thread_id: str) -> bool: captured["active_thread_id"] = thread_id return False class _FakeRunsClient: async def create(self, thread_id: str, graph: str, **kwargs) -> None: captured["thread_id"] = thread_id captured["graph"] = graph captured["kwargs"] = kwargs class _FakeThreadsClient: async def create(self, **kwargs) -> None: captured["thread_create_kwargs"] = kwargs class _FakeLangGraphClient: runs = _FakeRunsClient() threads = _FakeThreadsClient() async def fake_set_reviewer_thread_metadata(thread_id: str, **kwargs: object) -> None: captured["set_metadata_thread_id"] = thread_id captured["set_metadata_kwargs"] = kwargs monkeypatch.setattr( webapp, "get_github_app_installation_token", fake_get_github_app_installation_token ) monkeypatch.setattr( webapp, "get_github_app_installation_token_with_expiry", fake_get_github_app_installation_token_with_expiry, ) monkeypatch.setattr( webapp, "persist_encrypted_github_token", fake_persist_encrypted_github_token ) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "set_reviewer_thread_metadata", fake_set_reviewer_thread_metadata) monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClient()) asyncio.run( webapp.process_github_pr_review_request( { "action": "review_requested", "requested_reviewer": {"login": "open-swe[bot]"}, "pull_request": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "base": {"sha": "base-sha", "ref": "main"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat", "id": 123}, } ) ) kwargs = captured["kwargs"] prompt = kwargs["input"]["messages"][0]["content"] config = kwargs["config"]["configurable"] assert captured["graph"] == "reviewer" assert captured["thread_create_kwargs"] == { "thread_id": captured["thread_id"], "if_exists": "do_nothing", } assert captured["persist_token"] == "app-token" assert captured["persist_thread_id"] == captured["thread_id"] assert "https://github.com/langchain-ai/open-swe/pull/1244" in prompt assert "Base SHA: base-sha" in prompt assert "Head SHA: head-sha" in prompt assert config["source"] == "github" assert config["repo"] == {"owner": "langchain-ai", "name": "open-swe"} assert config["pr_number"] == 1244 assert config["review_requested"] is True def test_trigger_pr_review_from_ref_creates_reviewer_run(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_github_app_installation_token() -> str | None: return "app-token" async def fake_get_github_app_installation_token_with_expiry() -> tuple[str | None, str | None]: return "app-token", None async def fake_fetch_github_pr_metadata( pr_ref: GitHubPrRef, *, token: str ) -> dict[str, object]: captured["metadata_token"] = token return { "html_url": pr_ref.url, "base": {"sha": "base-sha"}, "head": {"sha": "head-sha", "ref": "feature-branch"}, } async def fake_persist_encrypted_github_token( thread_id: str, token: str, *, expires_at: str | None = None ) -> str: captured["persist_thread_id"] = thread_id captured["persist_token"] = token captured["persist_expires_at"] = expires_at return "encrypted-token" async def fake_is_thread_active(thread_id: str) -> bool: captured["active_thread_id"] = thread_id return False class _FakeRunsClient: async def create(self, thread_id: str, graph: str, **kwargs) -> None: captured["thread_id"] = thread_id captured["graph"] = graph captured["kwargs"] = kwargs class _FakeThreadsClient: async def create(self, **kwargs) -> None: captured["thread_create_kwargs"] = kwargs class _FakeLangGraphClient: runs = _FakeRunsClient() threads = _FakeThreadsClient() async def fake_set_reviewer_thread_metadata(thread_id: str, **kwargs: object) -> None: captured["set_metadata_thread_id"] = thread_id captured["set_metadata_kwargs"] = kwargs monkeypatch.setattr( webapp, "get_github_app_installation_token", fake_get_github_app_installation_token ) monkeypatch.setattr( webapp, "get_github_app_installation_token_with_expiry", fake_get_github_app_installation_token_with_expiry, ) monkeypatch.setattr(webapp, "fetch_github_pr_metadata", fake_fetch_github_pr_metadata) monkeypatch.setattr( webapp, "persist_encrypted_github_token", fake_persist_encrypted_github_token ) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "set_reviewer_thread_metadata", fake_set_reviewer_thread_metadata) monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClient()) result = asyncio.run( webapp.trigger_pr_review_from_ref( GitHubPrRef( owner="langchain-ai", repo="open-swe", number=1244, url="https://github.com/langchain-ai/open-swe/pull/1244", ), source="slack", slack_channel_id="C123", slack_thread_ts="1700000000.000100", ) ) kwargs = captured["kwargs"] prompt = kwargs["input"]["messages"][0]["content"] config = kwargs["config"]["configurable"] assert result["success"] is True assert captured["graph"] == "reviewer" assert captured["thread_create_kwargs"] == { "thread_id": captured["thread_id"], "if_exists": "do_nothing", } assert captured["metadata_token"] == "app-token" assert captured["persist_token"] == "app-token" assert "Base SHA: base-sha" in prompt assert "Head SHA: head-sha" in prompt assert config["source"] == "slack" assert config["repo"] == {"owner": "langchain-ai", "name": "open-swe"} assert config["pr_number"] == 1244 assert config["review_requested"] is True assert config["slack_thread"] == { "channel_id": "C123", "thread_ts": "1700000000.000100", } # The live head must be persisted to metadata so resolve_review_head_sha # doesn't return a stale head left by a prior push/ready dispatch. assert captured["set_metadata_kwargs"]["head_sha"] == "head-sha" def test_trigger_pr_review_from_ref_respects_dashboard_opt_in(monkeypatch) -> None: called = False async def fake_get_github_app_installation_token() -> str | None: nonlocal called called = True return "app-token" monkeypatch.setattr( webapp, "get_github_app_installation_token", fake_get_github_app_installation_token ) async def fake_is_review_repo_enabled(_owner: str, _name: str) -> bool: return False monkeypatch.setattr(webapp, "is_review_repo_enabled", fake_is_review_repo_enabled) result = asyncio.run( webapp.trigger_pr_review_from_ref( GitHubPrRef( owner="langchain-ai", repo="blocked", number=1, url="https://github.com/langchain-ai/blocked/pull/1", ), source="slack", ) ) assert result == {"success": False, "error": "Repository not enabled for review"} assert called is False def test_request_pr_review_tool_uses_shared_trigger(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_trigger_pr_review_from_ref( pr_ref: GitHubPrRef, *, source: str, github_login: str = "", github_user_id: int | None = None, slack_channel_id: str = "", slack_thread_ts: str = "", ) -> dict[str, object]: captured["pr_ref"] = pr_ref captured["source"] = source captured["github_login"] = github_login captured["github_user_id"] = github_user_id captured["slack_channel_id"] = slack_channel_id captured["slack_thread_ts"] = slack_thread_ts return {"success": True, "thread_id": "thread-id"} monkeypatch.setattr( request_pr_review_module, "trigger_pr_review_from_ref", fake_trigger_pr_review_from_ref ) monkeypatch.setattr( request_pr_review_module, "get_config", lambda: { "configurable": { "source": "github", "github_login": "octocat", "github_user_id": 123, "slack_thread": {"channel_id": "C123", "thread_ts": "1700000000.000100"}, } }, ) result = request_pr_review_tool("https://github.com/langchain-ai/open-swe/pull/1244") pr_ref = captured["pr_ref"] assert isinstance(pr_ref, GitHubPrRef) assert pr_ref.number == 1244 assert captured["source"] == "github" assert captured["github_login"] == "octocat" assert captured["github_user_id"] == 123 assert captured["slack_channel_id"] == "C123" assert captured["slack_thread_ts"] == "1700000000.000100" assert result["success"] is True def test_process_github_pr_comment_review_request_without_email_uses_app_token( monkeypatch, ) -> None: captured: dict[str, object] = {} async def fake_extract_pr_context(payload: dict[str, object], event_type: str): return ( {"owner": "langchain-ai", "name": "open-swe"}, 1244, "open-swe/00000000-0000-0000-0000-000000000001", "external-user", "https://github.com/langchain-ai/open-swe/pull/1244", 9, None, ) async def fake_get_app_token_with_expiry() -> tuple[str, str]: return "app-token", "2026-01-01T00:00:00Z" async def fake_persist_token( thread_id: str, token: str, *, expires_at: str | None = None ) -> str: captured["persisted"] = {"thread_id": thread_id, "token": token, "expires_at": expires_at} return "encrypted" async def fake_react(*args, **kwargs) -> bool: captured["reaction_token"] = kwargs["token"] return True async def fake_fetch_comments(repo_config: dict[str, str], pr_number: int, *, token: str): captured["fetch_token"] = token return [{"body": "@open-swe review", "author": "external-user", "created_at": "now"}] async def fake_trigger_or_queue_run(*args, **kwargs) -> None: captured["triggered"] = {"args": args, "kwargs": kwargs} monkeypatch.setattr(webapp, "extract_pr_context", fake_extract_pr_context) monkeypatch.setattr(webapp, "email_for_login", lambda login: asyncio.sleep(0, result=None)) monkeypatch.setattr( webapp, "get_github_app_installation_token_with_expiry", fake_get_app_token_with_expiry ) monkeypatch.setattr(webapp, "persist_encrypted_github_token", fake_persist_token) monkeypatch.setattr(webapp, "react_to_github_comment", fake_react) monkeypatch.setattr(webapp, "fetch_pr_comments_since_last_tag", fake_fetch_comments) monkeypatch.setattr(webapp, "_trigger_or_queue_run", fake_trigger_or_queue_run) asyncio.run( webapp.process_github_pr_comment( { "comment": {"id": 9, "body": "@open-swe review"}, "sender": {"login": "external-user", "id": 123}, }, "issue_comment", ) ) assert captured["reaction_token"] == "app-token" assert captured["fetch_token"] == "app-token" assert captured["persisted"] == { "thread_id": "00000000-0000-0000-0000-000000000001", "token": "app-token", "expires_at": "2026-01-01T00:00:00Z", } assert captured["triggered"] def test_process_github_issue_uses_resolved_user_token_for_reaction(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_or_resolve_thread_github_token(thread_id: str, email: str) -> str | None: captured["thread_id"] = thread_id captured["email"] = email return "user-token" async def fake_get_github_app_installation_token() -> str | None: return None async def fake_react_to_github_comment( repo_config: dict[str, str], comment_id: int, *, event_type: str, token: str, pull_number: int | None = None, node_id: str | None = None, ) -> bool: captured["reaction_token"] = token captured["comment_id"] = comment_id return True async def fake_fetch_issue_comments( repo_config: dict[str, str], issue_number: int, *, token: str | None = None ) -> list[dict[str, object]]: captured["fetch_token"] = token return [] async def fake_is_thread_active(thread_id: str) -> bool: return False class _FakeRunsClient: async def create(self, *args, **kwargs) -> None: captured["run_created"] = True class _FakeLangGraphClient: runs = _FakeRunsClient() monkeypatch.setattr( webapp, "_get_or_resolve_thread_github_token", fake_get_or_resolve_thread_github_token ) monkeypatch.setattr( webapp, "get_github_app_installation_token", fake_get_github_app_installation_token ) monkeypatch.setattr(webapp, "_thread_exists", lambda thread_id: asyncio.sleep(0, result=False)) monkeypatch.setattr(webapp, "react_to_github_comment", fake_react_to_github_comment) monkeypatch.setattr(webapp, "fetch_issue_comments", fake_fetch_issue_comments) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClient()) monkeypatch.setattr( webapp, "email_for_login", lambda login: asyncio.sleep( 0, result="octocat@example.com" if login == "octocat" else None ), ) asyncio.run( webapp.process_github_issue( { "issue": { "id": 12345, "number": 42, "title": "Fix the flaky test", "body": "The test is failing intermittently.", "html_url": "https://github.com/langchain-ai/open-swe/issues/42", }, "comment": {"id": 999, "body": "@openswe please handle this"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, "issue_comment", ) ) assert captured["reaction_token"] == "user-token" assert captured["fetch_token"] == "user-token" assert captured["comment_id"] == 999 assert captured["run_created"] is True def test_process_github_issue_existing_thread_uses_followup_prompt(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_or_resolve_thread_github_token(thread_id: str, email: str) -> str | None: return "user-token" async def fake_get_github_app_installation_token() -> str | None: return None async def fake_react_to_github_comment( repo_config: dict[str, str], comment_id: int, *, event_type: str, token: str, pull_number: int | None = None, node_id: str | None = None, ) -> bool: return True async def fake_fetch_issue_comments( repo_config: dict[str, str], issue_number: int, *, token: str | None = None ) -> list[dict[str, object]]: raise AssertionError("fetch_issue_comments should not be called for follow-up prompts") async def fake_thread_exists(thread_id: str) -> bool: return True async def fake_is_thread_active(thread_id: str) -> bool: return False class _FakeRunsClient: async def create(self, *args, **kwargs) -> None: captured["prompt"] = kwargs["input"]["messages"][0]["content"] class _FakeLangGraphClient: runs = _FakeRunsClient() monkeypatch.setattr( webapp, "_get_or_resolve_thread_github_token", fake_get_or_resolve_thread_github_token ) monkeypatch.setattr( webapp, "get_github_app_installation_token", fake_get_github_app_installation_token ) monkeypatch.setattr(webapp, "_thread_exists", fake_thread_exists) monkeypatch.setattr(webapp, "react_to_github_comment", fake_react_to_github_comment) monkeypatch.setattr(webapp, "fetch_issue_comments", fake_fetch_issue_comments) monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active) monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClient()) monkeypatch.setattr( webapp, "email_for_login", lambda login: asyncio.sleep( 0, result="octocat@example.com" if login == "octocat" else None ), ) monkeypatch.setattr( "agent.dashboard.user_mappings.is_login_mapped", lambda login: login == "octocat", ) asyncio.run( webapp.process_github_issue( { "issue": { "id": 12345, "number": 42, "title": "Fix the flaky test", "body": "The test is failing intermittently.", "html_url": "https://github.com/langchain-ai/open-swe/issues/42", }, "comment": { "id": 999, "body": "@openswe please handle this", "user": {"login": "octocat"}, }, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, "issue_comment", ) ) assert captured["prompt"] == "**octocat:**\n@openswe please handle this" assert "## Repository" not in captured["prompt"] def test_parse_github_review_command_standalone() -> None: is_review, url = github_comments.parse_github_review_command("@open-swe review") assert is_review is True assert url is None def test_parse_github_review_command_with_url() -> None: is_review, url = github_comments.parse_github_review_command( "@open-swe review https://github.com/langchain-ai/open-swe/pull/1244" ) assert is_review is True assert url == "https://github.com/langchain-ai/open-swe/pull/1244" def test_parse_github_review_command_case_insensitive_and_aliases() -> None: assert github_comments.parse_github_review_command("@OpenSWE Review") == (True, None) assert github_comments.parse_github_review_command("@openswe review") == (True, None) assert github_comments.parse_github_review_command("@openswe-dev review") == (True, None) def test_parse_github_review_command_freeform_does_not_match() -> None: assert github_comments.parse_github_review_command("@open-swe review my code") == (False, None) assert github_comments.parse_github_review_command("@open-swe please review") == (False, None) assert github_comments.parse_github_review_command("@open-swe fix this") == (False, None) assert github_comments.parse_github_review_command("") == (False, None) def test_parse_github_review_command_does_not_swallow_trailing_text() -> None: # Trailing text after `review` (on a new line, with punctuation, or extra # words) must NOT be parsed as a URL — otherwise the comment would be # silently dropped instead of falling through to the regular handler. assert github_comments.parse_github_review_command("@open-swe review\nthanks!") == ( False, None, ) assert github_comments.parse_github_review_command("@open-swe review please") == (False, None) assert github_comments.parse_github_review_command("@open-swe review now") == (False, None) # Non-http schemes are not URLs we can route to a PR. assert github_comments.parse_github_review_command("@open-swe review ftp://x/y/pull/1") == ( False, None, ) def test_github_webhook_routes_pr_comment_review_to_agent(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_process_pr_comment(payload: dict[str, object], event_type: str) -> None: captured["payload"] = payload captured["event_type"] = event_type async def fake_process_review_command( payload: dict[str, object], event_type: str, pr_url_override: str | None ) -> None: raise AssertionError("review commands should route through the main agent") monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_pr_comment) monkeypatch.setattr(webapp, "process_github_pr_review_command", fake_process_review_command) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) monkeypatch.setattr(webapp, "ALLOWED_GITHUB_ORGS", frozenset({"langchain-ai"})) client = TestClient(webapp.app) response = _post_github_webhook( client, "issue_comment", { "action": "created", "issue": { "id": 12345, "number": 1244, "pull_request": {"url": "https://api.github.com/repos/x/y/pulls/1244"}, }, "comment": {"id": 9, "body": "@open-swe review"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json() == {"status": "accepted", "message": "Processing issue_comment event"} assert captured["event_type"] == "issue_comment" def test_github_webhook_routes_pr_review_request_comment_to_agent(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_process_pr_comment(payload: dict[str, object], event_type: str) -> None: captured["payload"] = payload captured["event_type"] = event_type monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_pr_comment) monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) monkeypatch.setattr(webapp, "ALLOWED_GITHUB_ORGS", frozenset({"langchain-ai"})) client = TestClient(webapp.app) response = _post_github_webhook( client, "issue_comment", { "action": "created", "issue": { "id": 12345, "number": 1244, "pull_request": {"url": "https://api.github.com/repos/x/y/pulls/1244"}, }, "comment": {"id": 9, "body": "@open-swe review"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "public-demo"}, "sender": {"login": "octocat"}, }, ) assert response.status_code == 200 assert response.json() == {"status": "accepted", "message": "Processing issue_comment event"} assert captured["event_type"] == "issue_comment" def test_process_github_pr_review_command_uses_payload_pr(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_app_token() -> str: return "app-token" async def fake_react(*args, **kwargs) -> None: captured["reacted"] = True async def fake_trigger( pr_ref: GitHubPrRef, *, source: str, github_login: str = "", github_user_id: int | None = None, slack_channel_id: str = "", slack_thread_ts: str = "", ) -> dict[str, object]: captured["pr_ref"] = pr_ref captured["source"] = source captured["github_login"] = github_login captured["github_user_id"] = github_user_id return {"success": True, "thread_id": "tid", "pr_url": pr_ref.url} monkeypatch.setattr(webapp, "get_github_app_installation_token", fake_get_app_token) monkeypatch.setattr(webapp, "react_to_github_comment", fake_react) monkeypatch.setattr(webapp, "trigger_pr_review_from_ref", fake_trigger) asyncio.run( webapp.process_github_pr_review_command( { "issue": { "number": 1244, "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", "pull_request": {"url": "..."}, }, "comment": {"id": 9, "body": "@open-swe review"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, "sender": {"login": "octocat", "id": 123}, }, "issue_comment", None, ) ) pr_ref = captured["pr_ref"] assert isinstance(pr_ref, GitHubPrRef) assert pr_ref.owner == "langchain-ai" assert pr_ref.repo == "open-swe" assert pr_ref.number == 1244 assert pr_ref.url == "https://github.com/langchain-ai/open-swe/pull/1244" assert captured["source"] == "github" assert captured["github_login"] == "octocat" assert captured["github_user_id"] == 123 assert captured.get("reacted") is True def test_process_github_pr_review_command_uses_url_override(monkeypatch) -> None: captured: dict[str, object] = {} async def fake_get_app_token() -> str: return "app-token" async def fake_react(*args, **kwargs) -> None: return None async def fake_trigger( pr_ref: GitHubPrRef, *, source: str, github_login: str = "", github_user_id: int | None = None, slack_channel_id: str = "", slack_thread_ts: str = "", ) -> dict[str, object]: captured["pr_ref"] = pr_ref return {"success": True, "thread_id": "tid", "pr_url": pr_ref.url} monkeypatch.setattr(webapp, "get_github_app_installation_token", fake_get_app_token) monkeypatch.setattr(webapp, "react_to_github_comment", fake_react) monkeypatch.setattr(webapp, "trigger_pr_review_from_ref", fake_trigger) asyncio.run( webapp.process_github_pr_review_command( { "issue": {"number": 99, "pull_request": {"url": "..."}}, "comment": {"id": 1, "body": "@open-swe review https://github.com/x/y/pull/7"}, "repository": {"owner": {"login": "x"}, "name": "y"}, "sender": {"login": "octocat", "id": 1}, }, "issue_comment", "https://github.com/x/y/pull/7", ) ) pr_ref = captured["pr_ref"] assert isinstance(pr_ref, GitHubPrRef) assert pr_ref.number == 7 assert pr_ref.owner == "x" assert pr_ref.repo == "y"