open-swe/tests/test_dashboard_repo_optional.py
Christian Bromann abf354bb05
feat(open-swe): stream agent chat via @langchain/react v2 protocol (#1475)
* feat(dashboard): stream agent chat via @langchain/react v2 protocol

Replace the bespoke SSE + React Query polling path with LangGraph’s
v2 event stream through credentialed dashboard proxies. Run starts go
through stream commands; mid-run follow-ups still queue via /messages.

* fix import path

* fix tests after rebase

* format

* PR feedback

* improved model fallback

* fix image handling

* embrace sdk

* cleanup

* cr

* more cleanup

* fix cors

* harden security

---------

Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-11 09:54:35 -07:00

163 lines
5.2 KiB
Python

from __future__ import annotations
import asyncio
from typing import Any
import pytest
from agent.dashboard import thread_api
def test_resolve_repo_config_parses_request_repo() -> None:
assert thread_api._resolve_repo_config("octo/repo") == {"owner": "octo", "name": "repo"}
def test_resolve_repo_config_returns_empty_when_no_repo_given() -> None:
assert thread_api._resolve_repo_config(None) == {}
assert thread_api._resolve_repo_config("") == {}
assert thread_api._resolve_repo_config("not-a-repo") == {}
def test_thread_summary_blanks_repo_when_absent() -> None:
summary = thread_api._thread_summary(
{"thread_id": "t1", "metadata": {"source": "dashboard", "title": "no repo run"}}
)
assert summary["repo"] == ""
assert summary["repoFullName"] == ""
def test_thread_summary_keeps_repo_when_present() -> None:
summary = thread_api._thread_summary(
{
"thread_id": "t2",
"metadata": {
"source": "dashboard",
"title": "repo run",
"repo_owner": "octo",
"repo_name": "repo",
},
}
)
assert summary["repo"] == "repo"
assert summary["repoFullName"] == "octo/repo"
def test_thread_summary_includes_trace_url(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
thread_api,
"get_langsmith_trace_url",
lambda thread_id: f"https://smith.example/t/{thread_id}",
)
summary = thread_api._thread_summary(
{"thread_id": "t3", "metadata": {"source": "dashboard", "title": "traced run"}}
)
assert summary["traceUrl"] == "https://smith.example/t/t3"
class _FakeThreadsClient:
async def create(
self, *, thread_id: str, metadata: dict[str, Any], if_exists: str
) -> dict[str, Any]:
return {"thread_id": thread_id, "metadata": metadata, "if_exists": if_exists}
async def update(self, *, thread_id: str, metadata: dict[str, Any]) -> dict[str, Any]:
return {"thread_id": thread_id, "metadata": metadata}
async def get(self, thread_id: str) -> dict[str, Any]:
return {"thread_id": thread_id, "metadata": {}}
class _FakeRunsClient:
def __init__(self) -> None:
self.configurable: dict[str, Any] | None = None
async def create(
self,
thread_id: str,
assistant_id: str,
*,
input: dict[str, Any],
config: dict[str, Any],
if_not_exists: str = "reject",
stream_mode: list[str] | None = None,
stream_resumable: bool = False,
) -> dict[str, str]:
self.configurable = config["configurable"]
return {"run_id": "run-id"}
class _FakeLangGraphClient:
def __init__(self) -> None:
self.threads = _FakeThreadsClient()
self.runs = _FakeRunsClient()
@pytest.fixture
def dashboard_run_client(monkeypatch: pytest.MonkeyPatch) -> _FakeLangGraphClient:
client = _FakeLangGraphClient()
async def fake_get_profile(login: str) -> dict[str, Any]:
return {}
async def fake_ensure_token(login: str) -> None:
return None
async def fake_resolve_email(login: str, profile: dict[str, Any]) -> str:
return "octo@example.com"
monkeypatch.setattr(thread_api, "langgraph_client", lambda: client)
monkeypatch.setattr(thread_api, "get_profile", fake_get_profile)
monkeypatch.setattr(thread_api, "_ensure_dashboard_github_token", fake_ensure_token)
monkeypatch.setattr(thread_api, "_resolve_run_email", fake_resolve_email)
return client
def test_create_thread_record_omits_repo_less_marker_when_repo_unset(
dashboard_run_client: _FakeLangGraphClient,
) -> None:
# Runs now start client-side via the stream commands endpoint, so the run
# configurable is assembled from thread metadata by
# ``_build_dashboard_configurable``. The thread record must not persist a
# repo-less marker when the repo is simply unset (not explicitly cleared).
asyncio.run(
thread_api._create_dashboard_thread_record(
"thread-id",
login="octo",
repo_config={},
prompt="do work",
)
)
configurable = asyncio.run(
thread_api._build_dashboard_configurable("thread-id", "octo", {"source": "dashboard"})
)
assert "repo_explicitly_none" not in configurable
assert "repo" not in configurable
def test_build_configurable_marks_repo_less_config_when_explicit(
dashboard_run_client: _FakeLangGraphClient,
) -> None:
configurable = asyncio.run(
thread_api._build_dashboard_configurable(
"thread-id",
"octo",
{"source": "dashboard", "repo_explicitly_none": True},
)
)
assert configurable["repo_explicitly_none"] is True
assert "repo" not in configurable
def test_build_configurable_includes_repo_when_configured(
dashboard_run_client: _FakeLangGraphClient,
) -> None:
configurable = asyncio.run(
thread_api._build_dashboard_configurable(
"thread-id",
"octo",
{"source": "dashboard", "repo_owner": "octo", "repo_name": "repo"},
)
)
assert configurable["repo"] == {"owner": "octo", "name": "repo"}
assert "repo_explicitly_none" not in configurable