Files

296 lines
9.9 KiB
Python

from io import BytesIO
from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from starlette.datastructures import UploadFile
from app.gateway.routers import app_identity as app_identity_router
from deerflow.config.system_settings import AppIdentitySettings, SystemSettings
PNG = bytes.fromhex(
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4"
"890000000a49444154789c63000100000500010d0a2db40000000049454e44ae426082"
)
SVG = b'<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="64"><rect width="1920" height="64" fill="#8f1d14"/></svg>'
def _store(monkeypatch: pytest.MonkeyPatch, tmp_path):
current = {"settings": SystemSettings()}
def load():
return current["settings"]
def save(settings: SystemSettings) -> SystemSettings:
current["settings"] = settings
return settings
async def admin_user(_request):
return SimpleNamespace(system_role="admin")
monkeypatch.setattr(app_identity_router, "runtime_home", lambda: tmp_path)
monkeypatch.setattr("deerflow.config.system_settings.runtime_home", lambda: tmp_path)
monkeypatch.setattr(app_identity_router, "load_system_settings", load)
monkeypatch.setattr(app_identity_router, "save_system_settings", save)
monkeypatch.setattr("deerflow.config.system_settings.load_system_settings", load)
monkeypatch.setattr(
"app.gateway.routers.system_settings.get_optional_user_from_request",
admin_user,
)
return current
def test_new_settings_leave_app_identity_empty() -> None:
assert SystemSettings().app_identity == AppIdentitySettings()
assert SystemSettings().app_identity.header_title_font_mode == "adaptive"
assert SystemSettings().app_identity.header_title_font_size == 30
assert SystemSettings().app_identity.light_red_header_background_filename is None
assert SystemSettings().app_identity.light_red_nav_active_filename is None
@pytest.mark.asyncio
async def test_non_admin_cannot_change_app_identity(monkeypatch: pytest.MonkeyPatch) -> None:
async def ordinary_user(_request):
return SimpleNamespace(system_role="user")
monkeypatch.setattr(
"app.gateway.routers.system_settings.get_optional_user_from_request",
ordinary_user,
)
with pytest.raises(HTTPException) as error:
await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="新系统",
remove_logo=False,
logo=None,
)
assert error.value.status_code == 403
@pytest.mark.asyncio
async def test_admin_saves_name_without_replacing_the_static_logo(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
response = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name=" 研判中心 ",
remove_logo=False,
logo=None,
)
assert response.system_name == "研判中心"
assert response.has_logo is False
assert response.logo_url is None
assert current["settings"].app_identity.logo_filename is None
@pytest.mark.asyncio
async def test_admin_saves_header_title_font_settings(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
response = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=None,
header_title_font_mode="fixed",
header_title_font_size=34,
)
assert response.header_title_font_mode == "fixed"
assert response.header_title_font_size == 34
assert current["settings"].app_identity.header_title_font_mode == "fixed"
assert current["settings"].app_identity.header_title_font_size == 34
renamed = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="新研判中心",
remove_logo=False,
logo=None,
)
assert renamed.header_title_font_mode == "fixed"
assert renamed.header_title_font_size == 34
@pytest.mark.asyncio
async def test_header_title_fixed_font_size_is_bounded(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
_store(monkeypatch, tmp_path)
with pytest.raises(HTTPException) as error:
await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=None,
header_title_font_mode="fixed",
header_title_font_size=41,
)
assert error.value.status_code == 422
@pytest.mark.asyncio
async def test_uploaded_logo_is_served_separately_from_generated_pages(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
upload = UploadFile(file=BytesIO(PNG), filename="mark.png")
saved = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=upload,
)
assert saved.has_logo is True
assert saved.logo_url is not None
assert saved.logo_url.startswith("/api/public/app-identity/logo?v=")
logo_file = tmp_path / "app-identity" / current["settings"].app_identity.logo_filename
assert logo_file.is_file()
assert not (tmp_path / "generated-page-branding").exists()
served = await app_identity_router.get_app_identity_logo()
assert served.media_type == "image/png"
@pytest.mark.asyncio
async def test_invalid_logo_is_rejected(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None:
_store(monkeypatch, tmp_path)
upload = UploadFile(file=BytesIO(b"not-an-image"), filename="mark.gif")
with pytest.raises(HTTPException) as error:
await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=upload,
)
assert error.value.status_code == 422
@pytest.mark.asyncio
async def test_red_navigation_images_can_be_uploaded_and_served(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
saved = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=None,
light_red_header_background=UploadFile(
file=BytesIO(SVG),
filename="header.svg",
),
light_red_nav_active=UploadFile(
file=BytesIO(PNG),
filename="active.png",
),
)
assert saved.has_light_red_header_background is True
assert saved.has_light_red_nav_active is True
assert saved.light_red_header_background_url is not None
assert saved.light_red_header_background_url.startswith(
"/api/public/app-identity/light-red-header-background?v="
)
assert saved.light_red_nav_active_url is not None
assert saved.light_red_nav_active_url.startswith(
"/api/public/app-identity/light-red-nav-active?v="
)
identity = current["settings"].app_identity
header_file = tmp_path / "app-identity" / identity.light_red_header_background_filename
active_file = tmp_path / "app-identity" / identity.light_red_nav_active_filename
assert header_file.is_file()
assert header_file.suffix == ".svg"
assert active_file.is_file()
served_header = await app_identity_router.get_light_red_header_background()
served_active = await app_identity_router.get_light_red_nav_active()
assert served_header.media_type == "image/svg+xml"
assert served_header.headers["content-security-policy"].startswith("sandbox;")
assert served_active.media_type == "image/png"
@pytest.mark.asyncio
async def test_restoring_red_navigation_images_uses_built_in_defaults(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=None,
light_red_header_background=UploadFile(
file=BytesIO(PNG),
filename="header.png",
),
light_red_nav_active=UploadFile(
file=BytesIO(PNG),
filename="active.png",
),
)
identity = current["settings"].app_identity
header_file = tmp_path / "app-identity" / identity.light_red_header_background_filename
active_file = tmp_path / "app-identity" / identity.light_red_nav_active_filename
restored = await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=None,
remove_light_red_header_background=True,
remove_light_red_nav_active=True,
)
assert restored.has_light_red_header_background is False
assert restored.has_light_red_nav_active is False
assert header_file.exists() is False
assert active_file.exists() is False
@pytest.mark.asyncio
async def test_clearing_app_identity_removes_only_its_logo(
monkeypatch: pytest.MonkeyPatch,
tmp_path,
) -> None:
current = _store(monkeypatch, tmp_path)
upload = UploadFile(file=BytesIO(PNG), filename="mark.png")
await app_identity_router.update_app_identity_settings(
SimpleNamespace(),
system_name="研判中心",
remove_logo=False,
logo=upload,
)
logo_file = tmp_path / "app-identity" / current["settings"].app_identity.logo_filename
cleared = await app_identity_router.delete_app_identity_settings(SimpleNamespace())
assert cleared.system_name == ""
assert cleared.header_title_font_mode == "adaptive"
assert cleared.header_title_font_size == 30
assert cleared.has_logo is False
assert logo_file.exists() is False
with pytest.raises(HTTPException) as error:
await app_identity_router.get_app_identity_logo()
assert error.value.status_code == 404