"""Denya logo branding — repo-local assets under app/static/branding/. The official Denya Developers logo derivatives (Gitea release ``logo-assets-v1``, sha256-verified) are committed same-origin under ``app/static/branding/`` like the vendored frontend libraries — no CDN, no CSP change needed (``img-src 'self' data: blob:`` already covers them). Placement contract: * Login page header uses the h96 full lockup (``denya-logo-h96.png``). * Dashboard topbar (base.html nav) uses the h48 full lockup (compact spot: the DEVELOPERS subtext is unreadable below ~48px, so the mark reads as symbol+DENYA — the intended compact treatment). * Favicon: 32x32 declared first, 16x16 declared, both in . """ from __future__ import annotations import re import pytest from httpx import AsyncClient pytestmark = pytest.mark.asyncio BRANDING_ASSETS = ( "denya-logo.png", "denya-logo-trimmed.png", "denya-logo-h48.png", "denya-logo-h96.png", "denya-logo-64x64.png", "denya-logo-32x32.png", "denya-logo-16x16.png", ) def _directive_sources(csp: str, directive: str) -> list[str]: """Return the source list of one CSP directive (e.g. ``img-src``).""" for part in csp.split(";"): tokens = part.split() if tokens and tokens[0].strip() == directive: return [t.strip() for t in tokens[1:]] return [] async def test_login_page_header_uses_h96_logo(client: AsyncClient): """/login must carry the h96 full-lockup logo (same-origin URL).""" resp = await client.get("/login") assert resp.status_code == 200, resp.text assert "/static/branding/denya-logo-h96.png" in resp.text async def test_topbar_and_favicon_on_dashboard_pages(client: AsyncClient): """Dashboard chrome (base.html) carries h48 topbar logo + both favicons.""" resp = await client.get("/dashboard/fm") assert resp.status_code == 200, resp.text assert "/static/branding/denya-logo-h48.png" in resp.text # Favicon 32x32 with 16x16 declared in (link rel="icon"). assert 'rel="icon" type="image/png" sizes="32x32" href="/static/branding/denya-logo-32x32.png"' in resp.text assert 'rel="icon" type="image/png" sizes="16x16" href="/static/branding/denya-logo-16x16.png"' in resp.text async def test_branding_assets_served_same_origin(client: AsyncClient): """Every committed branding asset must resolve locally as a PNG.""" for name in BRANDING_ASSETS: url = f"/static/branding/{name}" resp = await client.get(url) assert resp.status_code == 200, f"{url} -> {resp.status_code}" assert resp.headers.get("content-type", "").startswith("image/png"), f"{url}: {resp.headers.get('content-type')!r}" assert len(resp.content) > 100, f"{url} looks empty" async def test_csp_serves_branding_without_changes(client: AsyncClient): """img-src already allows same-origin PNGs — no external host needed.""" resp = await client.get("/login") assert resp.status_code == 200 csp = resp.headers["content-security-policy"] img_sources = _directive_sources(csp, "img-src") assert img_sources, f"no img-src directive in CSP: {csp}" assert "'self'" in img_sources and "data:" in img_sources and "blob:" in img_sources assert "cdn." not in csp # fully self-contained, like the vendored scripts