Files
Octop/tests/unit/agents/test_browser_profile_middleware.py
jubaoliang fa2f6a7f56 feat(browser): isolate harness-browser profiles per Octop user
Replace the legacy shared/default Playwright session router with
per-user harness-browser profiles (`user-<id>`) so concurrent users no
longer share one Chrome session, cookie jar, or recording. The backend
now always derives the profile from the authenticated user (or the IM
thread's agent owner) instead of trusting client-supplied profile/session
ids, and a BrowserProfileMiddleware pins tool-selected profiles to the
same boundary. Also dedupes ThreadRegistry's session-refresh logic.
2026-09-05 08:35:26 +08:00

79 lines
2.5 KiB
Python

from unittest.mock import AsyncMock
import pytest
from langgraph.prebuilt.tool_node import ToolCallRequest
from octop.infra.agents.middleware import browser_profile
def _request(name: str, args: dict[str, object]) -> ToolCallRequest:
return ToolCallRequest(
tool_call={"name": name, "args": args, "id": "call-1"},
tool=None,
state={},
runtime=None, # type: ignore[arg-type]
)
@pytest.mark.asyncio
async def test_browser_profile_is_forced_from_current_user(monkeypatch) -> None:
monkeypatch.setattr(
browser_profile,
"get_config",
lambda: {"configurable": {"user": "42"}},
)
handler = AsyncMock(return_value=object())
request = _request("browser_use", {"action": "navigate", "profile": "default"})
await browser_profile.BrowserProfileMiddleware().awrap_tool_call(request, handler)
forwarded = handler.await_args.args[0]
assert forwarded.tool_call["args"] == {
"action": "navigate",
"profile": "user-42",
}
@pytest.mark.asyncio
async def test_non_browser_tool_is_unchanged(monkeypatch) -> None:
monkeypatch.setattr(
browser_profile,
"get_config",
lambda: {"configurable": {"user": "42"}},
)
handler = AsyncMock(return_value=object())
request = _request("read_file", {"path": "notes.txt"})
await browser_profile.BrowserProfileMiddleware().awrap_tool_call(request, handler)
assert handler.await_args.args[0] is request
@pytest.mark.asyncio
async def test_browser_profile_blocks_when_user_missing(monkeypatch) -> None:
monkeypatch.setattr(browser_profile, "get_config", lambda: {"configurable": {}})
handler = AsyncMock(return_value=object())
request = _request("browser_use", {"action": "navigate", "profile": "default"})
result = await browser_profile.BrowserProfileMiddleware().awrap_tool_call(request, handler)
handler.assert_not_awaited()
assert getattr(result, "status", None) == "error"
assert "no Octop user id" in str(result.content)
@pytest.mark.asyncio
async def test_browser_profile_blocks_placeholder_user_zero(monkeypatch) -> None:
monkeypatch.setattr(
browser_profile,
"get_config",
lambda: {"configurable": {"user": "0"}},
)
handler = AsyncMock(return_value=object())
request = _request("browser_use", {"action": "navigate"})
result = await browser_profile.BrowserProfileMiddleware().awrap_tool_call(request, handler)
handler.assert_not_awaited()
assert getattr(result, "status", None) == "error"