feat(webui): refine output timeline and model controls (#4108)
* feat(webui): refine output timeline and composer queue * feat(webui): add provider model picker * fix(webui): polish model settings and heartbeat checks * chore: keep heartbeat changes out of webui pr * refactor(webui): isolate settings routes * fix(providers): align minimax anthropic test * fix(providers): keep minimax anthropic base sdk-compatible * fix(providers): normalize anthropic base urls
This commit is contained in:
@@ -148,7 +148,7 @@ async def test_cli_apps_routes_require_token_and_return_payload(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
"nanobot.channels.websocket.cli_apps_payload",
|
||||
"nanobot.webui.settings_routes.cli_apps_payload",
|
||||
lambda: {
|
||||
"apps": [
|
||||
{
|
||||
@@ -173,7 +173,7 @@ async def test_cli_apps_routes_require_token_and_return_payload(
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.channels.websocket.cli_apps_action",
|
||||
"nanobot.webui.settings_routes.cli_apps_action",
|
||||
lambda action, query: {
|
||||
"apps": [],
|
||||
"installed_count": 1,
|
||||
@@ -280,7 +280,7 @@ async def test_mcp_presets_routes_require_token_and_return_payload(
|
||||
return {"ok": True, "message": "MCP config reloaded.", "requires_restart": False}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.channels.websocket.request_mcp_reload",
|
||||
"nanobot.webui.settings_routes.request_mcp_reload",
|
||||
_hot_reload,
|
||||
)
|
||||
channel = _ch(bus, session_manager=_seed_session(tmp_path), port=29913)
|
||||
|
||||
@@ -453,6 +453,35 @@ async def test_media_route_degrades_non_image_to_octet_stream(
|
||||
assert resp.headers.get("x-content-type-options") == "nosniff"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_media_route_serves_svg_with_strict_csp(
|
||||
bus: MagicMock, tmp_path: Path
|
||||
) -> None:
|
||||
"""Generated SVG can preview as an image without becoming executable HTML."""
|
||||
media = tmp_path / "media"
|
||||
media.mkdir()
|
||||
target = media / "chart.svg"
|
||||
target.write_text("<svg xmlns='http://www.w3.org/2000/svg'><script>alert(1)</script></svg>")
|
||||
|
||||
channel = _ch(bus, port=29928)
|
||||
with patch("nanobot.channels.websocket.get_media_dir", return_value=media):
|
||||
url_path = channel._sign_media_path(target)
|
||||
assert url_path is not None
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
await asyncio.sleep(0.3)
|
||||
try:
|
||||
resp = await _http_get(f"http://127.0.0.1:29928{url_path}")
|
||||
finally:
|
||||
await channel.stop()
|
||||
await server_task
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.headers["content-type"].startswith("image/svg+xml")
|
||||
assert resp.headers.get("x-content-type-options") == "nosniff"
|
||||
assert "default-src 'none'" in resp.headers.get("content-security-policy", "")
|
||||
assert "sandbox" in resp.headers.get("content-security-policy", "")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /api/sessions/<key>/messages: media_urls hydration on session read
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user