Files
denya-onecare/tests/test_branding_assets.py
T
root a6eb799efa feat(whatsapp,branding): WhatsApp demo-number wiring + Denya logo assets
WhatsApp demo path (relay #748):
- WHATSAPP_DEMO_TO config under the WhatsApp section (env-based, .env-only;
  .env.example keeps an empty placeholder; real numbers never enter source).
- build_demo_webhook_payload() in app/routers/whatsapp.py builds the Meta
  demo payload from it (fails closed when unset), so the webhook round trip
  logs from_number = demo number (surfaces in GET /api/whatsapp/mock-log) and
  the auto-reply targets the same number.
- tests/test_whatsapp_demo_number.py: default empty + never committed in
  tracked files, payload builder from/to, 200/403/401 gates unchanged.

Branding (logo-assets-v1, sha256-verified, same-origin app/static/branding):
- Login header uses h96 full lockup; logged-in topbar (base.html) uses h48 on
  a light chip (logo ink is ~2:1 vs the dark nav); favicons 32x32 + 16x16 in
  <head>. img-src 'self' data: blob: already allows /static/branding/*.
- tests/test_branding_assets.py: page placement + same-origin serving + CSP.
- AGENTS.md synced.
2026-09-09 19:32:21 +00:00

81 lines
3.3 KiB
Python

"""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 <head>.
"""
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 <head> (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