Fix WebUI startup blocking on slow gateway routes
This commit is contained in:
@@ -2654,7 +2654,7 @@ def test_sessions_list_includes_active_run_started_at(monkeypatch) -> None:
|
||||
try:
|
||||
wth._WEBSOCKET_TURN_WALL_STARTED_AT["chat-1"] = 1_700_000_000.0
|
||||
req = Request("/api/sessions", Headers([("Authorization", "Bearer tok")]))
|
||||
resp = channel.gateway.http._handle_sessions_list(req)
|
||||
resp = asyncio.run(channel.gateway.http._handle_sessions_list(req))
|
||||
finally:
|
||||
wth._WEBSOCKET_TURN_WALL_STARTED_AT.clear()
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
@@ -462,6 +464,85 @@ async def test_cli_apps_routes_require_token_and_return_payload(
|
||||
await server_task
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cli_apps_catalog_does_not_block_other_webui_http_routes(
|
||||
bus: MagicMock,
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
entered = threading.Event()
|
||||
release = threading.Event()
|
||||
|
||||
def slow_payload() -> dict[str, Any]:
|
||||
entered.set()
|
||||
release.wait(2.0)
|
||||
return {"apps": [], "installed_count": 0, "catalog_updated_at": None}
|
||||
|
||||
monkeypatch.setattr("nanobot.webui.settings_routes.cli_apps_payload", slow_payload)
|
||||
channel = _ch(bus, session_manager=_seed_session(tmp_path), port=29935)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
await asyncio.sleep(0.3)
|
||||
try:
|
||||
boot = await _http_get("http://127.0.0.1:29935/webui/bootstrap")
|
||||
token = boot.json()["token"]
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
started = time.perf_counter()
|
||||
catalog_task = asyncio.create_task(
|
||||
_http_get("http://127.0.0.1:29935/api/settings/cli-apps", headers=auth)
|
||||
)
|
||||
assert await asyncio.to_thread(entered.wait, 2.0)
|
||||
assert time.perf_counter() - started < 1.0
|
||||
|
||||
workspaces_started = time.perf_counter()
|
||||
workspaces = await _http_get("http://127.0.0.1:29935/api/workspaces", headers=auth)
|
||||
assert time.perf_counter() - workspaces_started < 1.0
|
||||
assert workspaces.status_code == 200
|
||||
|
||||
release.set()
|
||||
catalog = await catalog_task
|
||||
assert catalog.status_code == 200
|
||||
assert catalog.json()["apps"] == []
|
||||
finally:
|
||||
release.set()
|
||||
await channel.stop()
|
||||
await server_task
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cli_apps_route_supports_installed_only_payload(
|
||||
bus: MagicMock,
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
calls: list[bool] = []
|
||||
|
||||
def payload(*, installed_only: bool = False) -> dict[str, Any]:
|
||||
calls.append(installed_only)
|
||||
return {"apps": [], "installed_count": 0, "catalog_updated_at": None}
|
||||
|
||||
monkeypatch.setattr("nanobot.webui.settings_routes.cli_apps_payload", payload)
|
||||
channel = _ch(bus, session_manager=_seed_session(tmp_path), port=29936)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
await asyncio.sleep(0.3)
|
||||
try:
|
||||
boot = await _http_get("http://127.0.0.1:29936/webui/bootstrap")
|
||||
token = boot.json()["token"]
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
resp = await _http_get(
|
||||
"http://127.0.0.1:29936/api/settings/cli-apps?installed_only=1",
|
||||
headers=auth,
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["apps"] == []
|
||||
assert calls == [True]
|
||||
finally:
|
||||
await channel.stop()
|
||||
await server_task
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_presets_routes_require_token_and_return_payload(
|
||||
bus: MagicMock,
|
||||
|
||||
@@ -7,8 +7,8 @@ from nanobot.webui.workspaces import (
|
||||
read_webui_default_access_mode,
|
||||
read_webui_workspace_state,
|
||||
webui_workspace_state_path,
|
||||
write_webui_default_access_mode,
|
||||
workspaces_payload,
|
||||
write_webui_default_access_mode,
|
||||
)
|
||||
|
||||
|
||||
@@ -152,3 +152,32 @@ def test_webui_default_access_does_not_override_explicit_session_scope(tmp_path,
|
||||
|
||||
assert scope.project_path == project.resolve()
|
||||
assert scope.access_mode == "full"
|
||||
|
||||
|
||||
def test_scope_for_session_key_reads_metadata_without_full_history(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr("nanobot.webui.workspaces.get_webui_dir", lambda: tmp_path / "webui")
|
||||
default = tmp_path / "default"
|
||||
project = tmp_path / "project"
|
||||
default.mkdir()
|
||||
project.mkdir()
|
||||
sessions = SessionManager(tmp_path / "sessions")
|
||||
controller = WebUIWorkspaceController(
|
||||
session_manager=sessions,
|
||||
default_workspace=default,
|
||||
default_restrict_to_workspace=True,
|
||||
)
|
||||
explicit = default_workspace_scope(project, restrict_to_workspace=False)
|
||||
controller.persist_scope("metadata-only", explicit)
|
||||
|
||||
def fail_full_read(_key: str) -> None:
|
||||
raise AssertionError("scope lookup should not read full session history")
|
||||
|
||||
monkeypatch.setattr(sessions, "read_session_file", fail_full_read)
|
||||
|
||||
scope = controller.scope_for_session_key("websocket:metadata-only")
|
||||
|
||||
assert scope.project_path == project.resolve()
|
||||
assert scope.access_mode == "full"
|
||||
|
||||
Reference in New Issue
Block a user