mirror of
https://github.com/TencentCloud/Octop.git
synced 2026-10-20 06:19:17 +08:00
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.
79 lines
2.5 KiB
Python
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"
|