mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-12 01:02:39 +02:00
test(backend): add Jira MCP E2E fakes
This commit is contained in:
parent
e6297e2e40
commit
369f0aaff3
5 changed files with 206 additions and 26 deletions
15
surfsense_backend/tests/e2e/fakes/fixtures/jira_issues.json
Normal file
15
surfsense_backend/tests/e2e/fakes/fixtures/jira_issues.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"site": {
|
||||
"cloud_id": "fake-jira-cloud-001",
|
||||
"name": "SurfSense E2E Atlassian",
|
||||
"url": "https://surfsense-e2e.atlassian.net"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"id": "fake-jira-issue-canary-001",
|
||||
"key": "E2E-101",
|
||||
"summary": "E2E Canary Jira Issue",
|
||||
"description": "This Jira issue proves live MCP tool calls work end-to-end. SURFSENSE_E2E_CANARY_TOKEN_JIRA_001"
|
||||
}
|
||||
]
|
||||
}
|
||||
136
surfsense_backend/tests/e2e/fakes/jira_module.py
Normal file
136
surfsense_backend/tests/e2e/fakes/jira_module.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
"""Strict Jira MCP OAuth/tool fakes for Playwright E2E."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from tests.e2e.fakes import mcp_oauth_runtime, mcp_runtime
|
||||
|
||||
_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "jira_issues.json"
|
||||
|
||||
_AUTHORIZATION_URL = "https://mcp.atlassian.com/v1/authorize"
|
||||
_REGISTRATION_URL = "https://cf.mcp.atlassian.com/v1/register"
|
||||
_TOKEN_URL = "https://cf.mcp.atlassian.com/v1/token"
|
||||
_MCP_URL = "https://mcp.atlassian.com/v1/mcp"
|
||||
|
||||
_CLIENT_ID = "fake-jira-mcp-client-id"
|
||||
_CLIENT_SECRET = "fake-jira-mcp-client-secret"
|
||||
_ACCESS_TOKEN = "fake-jira-mcp-access-token"
|
||||
_REFRESH_TOKEN = "fake-jira-mcp-refresh-token"
|
||||
_OAUTH_CODE = "fake-jira-oauth-code"
|
||||
|
||||
|
||||
def _load_fixture() -> dict[str, Any]:
|
||||
with _FIXTURE_PATH.open() as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
_FIXTURE = _load_fixture()
|
||||
|
||||
|
||||
def _issue_text(issue: dict[str, Any]) -> str:
|
||||
return (
|
||||
f"{issue['key']} {issue['summary']}\n"
|
||||
f"id: {issue['id']}\n"
|
||||
f"description: {issue['description']}"
|
||||
)
|
||||
|
||||
|
||||
async def _list_tools() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
tools=[
|
||||
SimpleNamespace(
|
||||
name="getAccessibleAtlassianResources",
|
||||
description="Get Jira sites accessible to the authenticated Atlassian user.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
),
|
||||
SimpleNamespace(
|
||||
name="searchJiraIssuesUsingJql",
|
||||
description="Search Jira issues using a Jira Query Language expression.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"jql": {
|
||||
"type": "string",
|
||||
"description": "JQL query used to search Jira issues.",
|
||||
},
|
||||
"maxResults": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of matching issues to return.",
|
||||
},
|
||||
},
|
||||
"required": ["jql"],
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def _call_tool(
|
||||
tool_name: str, arguments: dict[str, Any] | None = None
|
||||
) -> SimpleNamespace:
|
||||
arguments = arguments or {}
|
||||
site = _FIXTURE["site"]
|
||||
issue = _FIXTURE["issues"][0]
|
||||
|
||||
if tool_name == "getAccessibleAtlassianResources":
|
||||
if arguments:
|
||||
raise ValueError(
|
||||
f"Unexpected Jira getAccessibleAtlassianResources args: {arguments!r}"
|
||||
)
|
||||
text = (
|
||||
f"{site['name']}\n"
|
||||
f"cloud_id: {site['cloud_id']}\n"
|
||||
f"url: {site['url']}"
|
||||
)
|
||||
return SimpleNamespace(content=[SimpleNamespace(text=text)])
|
||||
|
||||
if tool_name == "searchJiraIssuesUsingJql":
|
||||
jql = str(arguments.get("jql", ""))
|
||||
if issue["summary"].lower() not in jql.lower() and issue[
|
||||
"key"
|
||||
].lower() not in jql.lower():
|
||||
raise ValueError(f"Unexpected Jira JQL query: {jql!r}")
|
||||
text = _issue_text(issue)
|
||||
return SimpleNamespace(content=[SimpleNamespace(text=text)])
|
||||
|
||||
raise NotImplementedError(f"Unexpected Jira MCP tool call: {tool_name!r}")
|
||||
|
||||
|
||||
def install(active_patches: list[Any]) -> None:
|
||||
"""Register Jira MCP OAuth/tool handlers with the shared dispatchers."""
|
||||
del active_patches
|
||||
mcp_oauth_runtime.register_service(
|
||||
mcp_url=_MCP_URL,
|
||||
discovery_metadata={
|
||||
"issuer": "https://mcp.atlassian.com",
|
||||
"authorization_endpoint": _AUTHORIZATION_URL,
|
||||
"token_endpoint": _TOKEN_URL,
|
||||
"registration_endpoint": _REGISTRATION_URL,
|
||||
"code_challenge_methods_supported": ["S256"],
|
||||
"grant_types_supported": ["authorization_code", "refresh_token"],
|
||||
"response_types_supported": ["code"],
|
||||
},
|
||||
client_id=_CLIENT_ID,
|
||||
client_secret=_CLIENT_SECRET,
|
||||
token_endpoint=_TOKEN_URL,
|
||||
registration_endpoint=_REGISTRATION_URL,
|
||||
oauth_code=_OAUTH_CODE,
|
||||
access_token=_ACCESS_TOKEN,
|
||||
refresh_token=_REFRESH_TOKEN,
|
||||
scope="read:jira-work read:me write:jira-work",
|
||||
redirect_uri_substring="/api/v1/auth/mcp/jira/connector/callback",
|
||||
)
|
||||
mcp_runtime.register(
|
||||
url=_MCP_URL,
|
||||
expected_bearer=_ACCESS_TOKEN,
|
||||
list_tools=_list_tools,
|
||||
call_tool=_call_tool,
|
||||
)
|
||||
|
|
@ -6,7 +6,8 @@ import json
|
|||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.e2e.fakes import mcp_oauth_runtime, mcp_runtime
|
||||
|
||||
_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "linear_issues.json"
|
||||
|
||||
|
|
@ -262,29 +263,43 @@ def _fake_streamablehttp_client(
|
|||
return _FakeStreamableHttpClient(url, headers=headers, **kwargs)
|
||||
|
||||
|
||||
async def _list_tools() -> SimpleNamespace:
|
||||
return await _FakeClientSession(object(), object()).list_tools()
|
||||
|
||||
|
||||
async def _call_tool(tool_name: str, arguments: dict[str, Any]) -> SimpleNamespace:
|
||||
return await _FakeClientSession(object(), object()).call_tool(
|
||||
tool_name, arguments=arguments
|
||||
)
|
||||
|
||||
|
||||
def install(active_patches: list[Any]) -> None:
|
||||
"""Patch production Linear MCP OAuth/tool boundaries."""
|
||||
targets = [
|
||||
(
|
||||
"app.services.mcp_oauth.discovery.discover_oauth_metadata",
|
||||
_fake_discover_oauth_metadata,
|
||||
),
|
||||
("app.services.mcp_oauth.discovery.register_client", _fake_register_client),
|
||||
(
|
||||
"app.services.mcp_oauth.discovery.exchange_code_for_tokens",
|
||||
_fake_exchange_code_for_tokens,
|
||||
),
|
||||
(
|
||||
"app.services.mcp_oauth.discovery.refresh_access_token",
|
||||
_fake_refresh_access_token,
|
||||
),
|
||||
(
|
||||
"app.agents.new_chat.tools.mcp_tool.streamablehttp_client",
|
||||
_fake_streamablehttp_client,
|
||||
),
|
||||
("app.agents.new_chat.tools.mcp_tool.ClientSession", _FakeClientSession),
|
||||
]
|
||||
for target, replacement in targets:
|
||||
p = patch(target, replacement)
|
||||
p.start()
|
||||
active_patches.append(p)
|
||||
"""Register Linear MCP OAuth/tool handlers with the shared dispatchers."""
|
||||
del active_patches
|
||||
mcp_oauth_runtime.register_service(
|
||||
mcp_url=_MCP_URL,
|
||||
discovery_metadata={
|
||||
"issuer": "https://mcp.linear.app",
|
||||
"authorization_endpoint": _AUTHORIZATION_URL,
|
||||
"token_endpoint": _TOKEN_URL,
|
||||
"registration_endpoint": _REGISTRATION_URL,
|
||||
"code_challenge_methods_supported": ["S256"],
|
||||
"grant_types_supported": ["authorization_code", "refresh_token"],
|
||||
"response_types_supported": ["code"],
|
||||
},
|
||||
client_id=_CLIENT_ID,
|
||||
client_secret=_CLIENT_SECRET,
|
||||
token_endpoint=_TOKEN_URL,
|
||||
registration_endpoint=_REGISTRATION_URL,
|
||||
oauth_code=_OAUTH_CODE,
|
||||
access_token=_ACCESS_TOKEN,
|
||||
refresh_token=_REFRESH_TOKEN,
|
||||
scope="read write",
|
||||
redirect_uri_substring="/api/v1/auth/mcp/linear/connector/callback",
|
||||
)
|
||||
mcp_runtime.register(
|
||||
url=_MCP_URL,
|
||||
expected_bearer=_ACCESS_TOKEN,
|
||||
list_tools=_list_tools,
|
||||
call_tool=_call_tool,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,10 @@ from unittest.mock import patch # noqa: E402
|
|||
from app.app import app # noqa: E402
|
||||
from tests.e2e.fakes import ( # noqa: E402
|
||||
embeddings as _fake_embeddings,
|
||||
jira_module as _fake_jira_module,
|
||||
linear_module as _fake_linear_module,
|
||||
mcp_oauth_runtime as _fake_mcp_oauth_runtime,
|
||||
mcp_runtime as _fake_mcp_runtime,
|
||||
native_google as _fake_native_google,
|
||||
notion_module as _fake_notion_module,
|
||||
)
|
||||
|
|
@ -164,6 +167,9 @@ _fake_embeddings.install(_active_patches)
|
|||
_fake_native_google.install(_active_patches)
|
||||
_fake_notion_module.install(_active_patches)
|
||||
_fake_linear_module.install(_active_patches)
|
||||
_fake_jira_module.install(_active_patches)
|
||||
_fake_mcp_runtime.install(_active_patches)
|
||||
_fake_mcp_oauth_runtime.install(_active_patches)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ from unittest.mock import patch # noqa: E402
|
|||
from app.celery_app import celery_app # noqa: E402
|
||||
from tests.e2e.fakes import ( # noqa: E402
|
||||
embeddings as _fake_embeddings,
|
||||
jira_module as _fake_jira_module,
|
||||
linear_module as _fake_linear_module,
|
||||
mcp_oauth_runtime as _fake_mcp_oauth_runtime,
|
||||
mcp_runtime as _fake_mcp_runtime,
|
||||
native_google as _fake_native_google,
|
||||
notion_module as _fake_notion_module,
|
||||
)
|
||||
|
|
@ -146,6 +150,10 @@ _patch_llm_bindings()
|
|||
_fake_embeddings.install(_active_patches)
|
||||
_fake_native_google.install(_active_patches)
|
||||
_fake_notion_module.install(_active_patches)
|
||||
_fake_linear_module.install(_active_patches)
|
||||
_fake_jira_module.install(_active_patches)
|
||||
_fake_mcp_runtime.install(_active_patches)
|
||||
_fake_mcp_oauth_runtime.install(_active_patches)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue