mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 18:33:15 +00:00
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
* Fix: Fix Improper privilege management in server.py (#1789) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 3ea29d3f231dd66bd7627769b5659564be4525df) * Fix: Fix Stored XSS in ReplyCard.tsx (#1785) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 31263f832a2ecedf669eee2e27b827a358a6a4d7) * feat: add structured Linear issue filters (#1775) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit b5e529252c3012818289eca1b1cdeb8014721310) * chore(triage): mark #1775 #1785 #1789 landed Move the three clean cherry-picks in this batch from deferred to landed in the upstream-sync ledger and re-render triage.md. --------- Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
185 lines
5.8 KiB
Python
185 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from agent.utils import linear
|
|
|
|
linear_search_tool = importlib.import_module("agent.tools.linear_search_issues")
|
|
|
|
|
|
async def test_search_issues_returns_results_and_pagination(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
async def fake_graphql_request(
|
|
query: str, variables: dict[str, Any] | None = None
|
|
) -> dict[str, Any]:
|
|
captured.update({"query": query, "variables": variables})
|
|
return {
|
|
"searchIssues": {
|
|
"nodes": [
|
|
{
|
|
"id": "issue-id",
|
|
"identifier": "DCD-20",
|
|
"title": "User-message styling improvement",
|
|
}
|
|
],
|
|
"totalCount": 12,
|
|
"pageInfo": {"hasNextPage": True, "endCursor": "next-page"},
|
|
}
|
|
}
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
|
|
|
result = await linear.search_issues(
|
|
" user message styling ",
|
|
team_id="team-id",
|
|
limit=5,
|
|
include_archived=True,
|
|
include_comments=True,
|
|
after="current-page",
|
|
)
|
|
|
|
assert "searchIssues" in captured["query"]
|
|
assert captured["variables"] == {
|
|
"query": "user message styling",
|
|
"filter": {"team": {"id": {"eq": "team-id"}}},
|
|
"limit": 5,
|
|
"includeArchived": True,
|
|
"includeComments": True,
|
|
"after": "current-page",
|
|
}
|
|
assert result == {
|
|
"issues": [
|
|
{
|
|
"id": "issue-id",
|
|
"identifier": "DCD-20",
|
|
"title": "User-message styling improvement",
|
|
}
|
|
],
|
|
"total_count": 12,
|
|
"page_info": {"hasNextPage": True, "endCursor": "next-page"},
|
|
}
|
|
|
|
|
|
async def test_search_issues_filters_without_text(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
async def fake_graphql_request(
|
|
query: str, variables: dict[str, Any] | None = None
|
|
) -> dict[str, Any]:
|
|
captured.update({"query": query, "variables": variables})
|
|
return {
|
|
"issues": {
|
|
"nodes": [{"id": "issue-id", "identifier": "DCD-21", "title": "Fix filters"}],
|
|
"totalCount": 1,
|
|
"pageInfo": {"hasNextPage": False, "endCursor": None},
|
|
}
|
|
}
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
|
filters = {"labels": {"some": {"name": {"eq": "open-swe"}}}}
|
|
|
|
result = await linear.search_issues(filters=filters, limit=1)
|
|
|
|
assert "issues(" in captured["query"]
|
|
assert "searchIssues" not in captured["query"]
|
|
assert captured["variables"] == {
|
|
"filter": filters,
|
|
"limit": 1,
|
|
"includeArchived": False,
|
|
"after": None,
|
|
}
|
|
assert result["issues"][0]["identifier"] == "DCD-21"
|
|
|
|
|
|
async def test_search_issues_combines_filters_with_team(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
async def fake_graphql_request(
|
|
_query: str, variables: dict[str, Any] | None = None
|
|
) -> dict[str, Any]:
|
|
captured["variables"] = variables
|
|
return {"searchIssues": {"nodes": [], "totalCount": 0, "pageInfo": {}}}
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
|
filters = {"state": {"name": {"eq": "Todo"}}}
|
|
|
|
await linear.search_issues("fix", team_id="team-id", filters=filters)
|
|
|
|
assert captured["variables"]["filter"] == {
|
|
"and": [filters, {"team": {"id": {"eq": "team-id"}}}]
|
|
}
|
|
|
|
|
|
async def test_search_issues_rejects_missing_query_and_filters(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
async def unexpected_request(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
|
pytest.fail("GraphQL request should not be made")
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", unexpected_request)
|
|
|
|
result = await linear.search_issues(" ")
|
|
|
|
assert result == {"error": "Search query or filters must be provided"}
|
|
|
|
|
|
@pytest.mark.parametrize("limit", [0, 51])
|
|
async def test_search_issues_rejects_invalid_limit(
|
|
monkeypatch: pytest.MonkeyPatch, limit: int
|
|
) -> None:
|
|
async def unexpected_request(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
|
pytest.fail("GraphQL request should not be made")
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", unexpected_request)
|
|
|
|
result = await linear.search_issues("styling", limit=limit)
|
|
|
|
assert result == {"error": "Search limit must be between 1 and 50"}
|
|
|
|
|
|
async def test_search_issues_propagates_graphql_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
async def fake_graphql_request(
|
|
_query: str, _variables: dict[str, Any] | None = None
|
|
) -> dict[str, Any]:
|
|
return {"error": "rate limited"}
|
|
|
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
|
|
|
assert await linear.search_issues("styling") == {"error": "rate limited"}
|
|
|
|
|
|
async def test_linear_search_issues_tool_delegates(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
async def fake_search_issues(**kwargs: Any) -> dict[str, Any]:
|
|
captured.update(kwargs)
|
|
return {"issues": []}
|
|
|
|
monkeypatch.setattr(linear_search_tool, "search_issues", fake_search_issues)
|
|
|
|
result = await linear_search_tool.linear_search_issues(
|
|
"styling",
|
|
team_id="team-id",
|
|
filters={"priority": {"eq": 1}},
|
|
limit=20,
|
|
include_archived=True,
|
|
include_comments=True,
|
|
after="cursor",
|
|
)
|
|
|
|
assert result == {"issues": []}
|
|
assert captured == {
|
|
"query": "styling",
|
|
"team_id": "team-id",
|
|
"filters": {"priority": {"eq": 1}},
|
|
"limit": 20,
|
|
"include_archived": True,
|
|
"include_comments": True,
|
|
"after": "cursor",
|
|
}
|