from types import SimpleNamespace import pytest from fastapi import HTTPException from app.gateway.routers import system_settings as system_settings_router from deerflow.config.html_runtime_config import ( DEFAULT_HTML_MAP_TILE_URL, DEFAULT_HTML_RUNTIME_BASE_URL, ) from deerflow.config.system_settings import HtmlRuntimeSettings, SystemSettings from deerflow.html_runtime.addresses import ( html_map_tile_url, html_runtime_base_url, normalize_html_map_tile_url, normalize_html_runtime_base_url, ) def test_domain_only_runtime_address_receives_the_public_asset_path() -> None: assert ( normalize_html_runtime_base_url("https://cm.example.com/") == "https://cm.example.com/api/public/html-runtime" ) assert ( normalize_html_runtime_base_url("https://cm.example.com/prefix/api/public/html-runtime/") == "https://cm.example.com/prefix/api/public/html-runtime" ) def test_runtime_address_rejects_a_bare_host_and_credentials() -> None: with pytest.raises(ValueError, match="http://"): normalize_html_runtime_base_url("cm.example.com") with pytest.raises(ValueError, match="账号或密码"): normalize_html_runtime_base_url("https://user:secret@cm.example.com") def test_tile_address_requires_xyz_placeholders() -> None: assert normalize_html_map_tile_url(DEFAULT_HTML_MAP_TILE_URL) == DEFAULT_HTML_MAP_TILE_URL with pytest.raises(ValueError, match="占位符"): normalize_html_map_tile_url("https://tiles.example/tile.png") def test_saved_dialog_address_overrides_config(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( "deerflow.html_runtime.addresses._saved", lambda field: { "base_url": "https://users.example/api/public/html-runtime", "map_tile_url": "https://tiles.example/{z}/{x}/{y}.png", }[field], ) monkeypatch.setattr( "deerflow.html_runtime.addresses._config_value", lambda _field: "http://server.internal/api/public/html-runtime", ) assert html_runtime_base_url(fallback="http://fallback.example/api/public/html-runtime") == ( "https://users.example/api/public/html-runtime" ) assert html_map_tile_url() == "https://tiles.example/{z}/{x}/{y}.png" def test_unset_dialog_address_keeps_the_config_fallback(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("deerflow.html_runtime.addresses._saved", lambda _field: "") monkeypatch.setattr( "deerflow.html_runtime.addresses._config_value", lambda field: { "base_url": "http://legacy.example/api/public/html-runtime", "map_tile_url": "http://legacy.example/tiles/{z}/{x}/{y}.png", }[field], ) assert html_runtime_base_url() == "http://legacy.example/api/public/html-runtime" assert html_map_tile_url() == "http://legacy.example/tiles/{z}/{x}/{y}.png" def test_missing_config_uses_the_built_in_default(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("deerflow.html_runtime.addresses._saved", lambda _field: "") monkeypatch.setattr("deerflow.html_runtime.addresses._config_value", lambda _field: "") assert html_runtime_base_url() == DEFAULT_HTML_RUNTIME_BASE_URL assert html_map_tile_url() == DEFAULT_HTML_MAP_TILE_URL def test_new_settings_leave_html_runtime_unset() -> None: assert SystemSettings().html_runtime == HtmlRuntimeSettings() @pytest.mark.asyncio async def test_non_admin_cannot_change_html_runtime_addresses( monkeypatch: pytest.MonkeyPatch, ) -> None: async def ordinary_user(_request): return SimpleNamespace(system_role="user") monkeypatch.setattr( system_settings_router, "get_optional_user_from_request", ordinary_user, ) with pytest.raises(HTTPException) as error: await system_settings_router.update_html_runtime_settings( system_settings_router.HtmlRuntimeSettingsUpdateRequest( base_url="https://users.example", map_tile_url="https://tiles.example/{z}/{x}/{y}.png", ), SimpleNamespace(), ) assert error.value.status_code == 403 @pytest.mark.asyncio async def test_admin_can_save_html_runtime_addresses( monkeypatch: pytest.MonkeyPatch, ) -> None: current = SystemSettings() saved: list[SystemSettings] = [] async def admin_user(_request): return SimpleNamespace(system_role="admin") monkeypatch.setattr(system_settings_router, "get_optional_user_from_request", admin_user) monkeypatch.setattr(system_settings_router, "load_system_settings", lambda: current) monkeypatch.setattr( system_settings_router, "save_system_settings", lambda settings: saved.append(settings) or settings, ) response = await system_settings_router.update_html_runtime_settings( system_settings_router.HtmlRuntimeSettingsUpdateRequest( base_url="https://users.example", map_tile_url="https://tiles.example/{z}/{x}/{y}.png", ), SimpleNamespace(), ) assert response.base_url == "https://users.example/api/public/html-runtime" assert response.map_tile_url == "https://tiles.example/{z}/{x}/{y}.png" assert saved[0].html_runtime.base_url == response.base_url assert saved[0].prompt_prefix == current.prompt_prefix @pytest.mark.asyncio async def test_invalid_html_runtime_address_is_rejected( monkeypatch: pytest.MonkeyPatch, ) -> None: async def admin_user(_request): return SimpleNamespace(system_role="admin") monkeypatch.setattr(system_settings_router, "get_optional_user_from_request", admin_user) with pytest.raises(HTTPException) as error: await system_settings_router.update_html_runtime_settings( system_settings_router.HtmlRuntimeSettingsUpdateRequest( base_url="not a url", map_tile_url="https://tiles.example/{z}/{x}/{y}.png", ), SimpleNamespace(), ) assert error.value.status_code == 400