Files
brain-of-reese/tests/unit/test_frontend_brand.py
T
ducoterra fe55be0c35
Build and Push Containers / build-and-push (push) Successful in 1m50s
feat(brand): configurable app name — BOR_APP_NAME drives /api/config + the frontend brand layer
One env var (BOR_APP_NAME, default "Brain of Reese") now drives the app's
display name everywhere (TODO.md L12 — owner ask: "a way to customize the
name for 'Brain of'. Should be an env var."). The existing app_name setting
is the source of truth (phase locked decision — no new variable, no rename);
with the variable unset the app is byte-identical to before.

Endpoint (A10 public/stateless, no secrets):
  GET /api/config → exactly {app_name, version} (app/api/config.py, the
  health.py pattern; registered before the static mount). Integration tests:
  anonymous 200, default values, a Settings override follows, key set is
  exactly two keys — no other setting may leak in later.

Frontend brand layer (A11 — runtime fetch, static templates stay static):
  assets/brand.js — a CLASSIC script, first on all six pages, so its top
  level runs at parse time: window.BOR_BRAND = "Brain of Reese"
  synchronously (the default renders immediately, no blank flash), then a
  no-store fetch of /api/config applies the name — document.title (global
  replace), every .brand-text (a name starting "Brain of " keeps the bold
  split Brain of <strong>rest</strong>, any other name renders plain; the
  operator-controlled name is HTML-escaped before innerHTML), a TreeWalker
  over text nodes (script/style rejected — page source never rewritten),
  and the aria-label/placeholder/meta-content attributes. Fetch failure
  keeps the default + console.warn (the loadHealth house style).
  app.js (status labels, typing label, elapsed-hint aria, tool labels) and
  document.js (viewer titles) read window.BOR_BRAND at CALL time via
  brand() — a label set after the fetch lands carries the configured name.
  Containerfile: esbuild minify line for brand.js (classic, like markdown.js);
  the phase-33 ?v= cache-busting picks the new asset ref up automatically.

E2E (A16 — one story, one file, isolated): test_configurable_brand.py boots
a SECOND app instance (same DB/mock-LLM/admin-auth env block, port APP_PORT+1,
BOR_APP_NAME="Brain of Testy") — the shared conftest server keeps the
default name so every other suite's title/label assertions stay untouched —
and asserts /api/config on both instances, the index title/brand/greeting/
#messages aria-label, the sources + login page titles, and one pre-token
chat turn (think out loud marker) whose #send-status reads "Brain of Testy
is thinking"; the no-op regression pins the shared server's default bytes.

Docs: .env.example App section + README configuration reference — what it
affects (titles, header brand, status labels, aria text), the default, the
bold-split rendering rule.

Gates: 695 unit+integration passed, app/ coverage 99% (>90%), story E2E
green in isolation (two consecutive runs), brand-string suites (smoke,
shared header, header consistency, chat persistence) green, ruff + pyright
clean.
2026-08-27 02:24:16 -04:00

174 lines
7.3 KiB
Python

"""Unit: the phase-39 brand layer (BOR_APP_NAME → window.BOR_BRAND).
No Python logic exists for this task — the behavior lives in
``frontend/assets/brand.js`` + the templates + the page scripts, and it
is E2E-gated by the story suite (task 03). Like the other
frontend-adjacent unit files, this module pins the JS markers the story
depends on, so a silent regression in the brand layer is caught without
a browser.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
ROOT = FRONTEND.parent
BRAND_JS = FRONTEND / "assets" / "brand.js"
APP_JS = FRONTEND / "assets" / "app.js"
DOCUMENT_JS = FRONTEND / "assets" / "document.js"
CONTAINERFILE = ROOT / "Containerfile"
#: Every page in the app ships the brand layer (phase 39: "every visible
#: brand string on every page resolves from one place").
HTML_PAGES = (
"index.html",
"sources.html",
"tuning.html",
"document.html",
"login.html",
"git-sources.html",
)
def _text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def test_brand_js_is_a_classic_script_with_synchronous_default() -> None:
"""brand.js is NOT a module (its top level must run at parse time,
before the page's module scripts evaluate) and sets window.BOR_BRAND
synchronously, BEFORE the /api/config fetch starts."""
js = _text(BRAND_JS)
assert not re.search(r"^\s*import\s", js, re.M), (
"brand.js must be a classic script — no import statements"
)
assert not re.search(r"^\s*export\s", js, re.M), (
"brand.js must be a classic script — no export statements"
)
default_idx = js.find('window.BOR_BRAND = "Brain of Reese"')
fetch_idx = js.find('fetch("/api/config"')
assert 0 <= default_idx < fetch_idx, (
"the default name must be set at top level before the fetch"
)
def test_brand_js_fetches_api_config_no_store() -> None:
"""The single source of the name is GET /api/config, never cached —
a renamed app must not serve a stale name from the HTTP cache — and
a fetch failure only warns (the loadHealth house style: the page
never breaks)."""
js = _text(BRAND_JS)
assert 'fetch("/api/config", { cache: "no-store" })' in js
assert "console.warn" in js
def test_brand_js_defers_dom_application_until_ready() -> None:
"""The DOM passes run on DOMContentLoaded while parsing, otherwise
immediately (the script sits at the end of <body>)."""
js = _text(BRAND_JS)
assert 'document.readyState === "loading"' in js
assert 'document.addEventListener("DOMContentLoaded", applyBrand)' in js
def test_brand_js_reskins_title_brand_text_prose_and_attributes() -> None:
"""The four DOM passes (phase 39 task 02): the document title,
every .brand-text (bold split for "Brain of …" names, plain text
otherwise — the name is HTML-escaped before innerHTML), a
TreeWalker over the text nodes (script/style nodes rejected — the
page source is never rewritten), and the aria-label / placeholder /
meta content attributes."""
js = _text(BRAND_JS)
assert "document.title.replaceAll" in js
assert ".brand-text" in js
assert 'name.startsWith("Brain of ")' in js
assert "escapeHTML" in js, "the name must be escaped before innerHTML"
assert "el.textContent = name" in js, "non-'Brain of ' names render plain"
assert "document.createTreeWalker" in js
assert "NodeFilter.SHOW_TEXT" in js
assert 'tag === "SCRIPT" || tag === "STYLE"' in js
assert "replaceAll(BRAND_LITERAL, name)" in js
for marker in ('"aria-label"', '"placeholder"', "meta[content]"):
assert marker in js, f"the attribute pass must cover {marker}"
def test_page_scripts_keep_the_default_literal_exactly_once() -> None:
"""The fallback literal lives in the page scripts' brand() reads —
exactly one copy per file (a second copy could drift out of sync)."""
assert _text(APP_JS).count('"Brain of Reese"') == 1, (
"app.js: the literal must appear only in the brand() fallback"
)
assert _text(DOCUMENT_JS).count('"Brain of Reese"') == 1, (
"document.js: the literal must appear only in the brand() fallback"
)
def test_app_js_labels_resolve_the_name_at_call_time() -> None:
"""Phase 39 task 02: the status labels, the typing label, the
elapsed-seconds hint and the tool labels read window.BOR_BRAND
through brand() — at CALL time, so a label set after /api/config
lands carries the configured name (a module-level const would
freeze the default)."""
js = _text(APP_JS)
assert 'const brand = () => window.BOR_BRAND || "Brain of Reese";' in js
assert "`${brand()} is thinking`" in js
assert "`${brand()} is answering`" in js
assert "`${brand()} is still thinking (${secs}s)`" in js
assert "`${brand()} is reading ${argument}`" in js
assert "`${brand()} is listing documents`" in js
# The frozen module-level literals must be gone (stale-name bug).
assert '"Brain of Reese is thinking"' not in js
assert '"Brain of Reese is answering"' not in js
def test_document_js_titles_resolve_the_name() -> None:
"""The viewer page titles (render + not-found) carry the resolved
name — the module sets them itself, so it must use brand() (the
static document.html title is handled by the brand.js title pass)."""
js = _text(DOCUMENT_JS)
assert 'document.title = `${doc.title} · ${brand()}`' in js
assert 'document.title = `Document not found · ${brand()}`' in js
assert 'window.BOR_BRAND || "Brain of Reese"' in js
def test_every_page_loads_brand_js_before_its_module_script() -> None:
"""All pages load brand.js as a CLASSIC script, before the page's
module script (modules execute after parsing — the synchronous
default must be set first)."""
for page in HTML_PAGES:
html = _text(FRONTEND / page)
brand_idx = html.find('<script src="assets/brand.js"></script>')
module_idx = html.find('<script type="module"')
assert brand_idx != -1, f"{page}: brand.js must load"
assert module_idx != -1, f"{page}: the page module must load"
assert brand_idx < module_idx, (
f"{page}: brand.js must load BEFORE the module script"
)
assert 'type="module"' not in html[brand_idx : brand_idx + 60], (
f"{page}: brand.js must be a classic script"
)
def test_containerfile_builds_brand_js_like_its_classic_sibling() -> None:
"""The image build minifies brand.js (classic script — minify only,
no --bundle, exactly like markdown.js) so the container serves it."""
cf = _text(CONTAINERFILE)
lines = [ln for ln in cf.splitlines() if "brand.js" in ln]
assert len(lines) == 1, "one esbuild line for brand.js"
line = lines[0]
assert "esbuild ./assets/brand.js" in line
assert "--minify" in line
assert "--bundle" not in line, (
"classic script: minify only (the markdown.js pattern)"
)
assert "--outfile=/out/assets/brand.js" in line
def test_no_cdn_in_the_brand_layer() -> None:
"""AGENTS.md rule 6: the brand layer adds no external reference."""
js = _text(BRAND_JS)
assert "http://" not in js and "https://" not in js
for page in HTML_PAGES:
html = _text(FRONTEND / page)
assert 'src="https://' not in html and 'href="https://' not in html