From 6508abc00fb3a5b3e080667270fc34a04105e3e8 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 21 Oct 2026 20:25:26 -0400 Subject: [PATCH] =?UTF-8?q?docs(agents):=20the=20doctrine=20=E2=80=94=20te?= =?UTF-8?q?sts=20never=20run=20against=20a=20real=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codify the standing rule at the top of the Tests section: the dev DB is the test environment, and the invariant every test must uphold is the testuser experience — its settings, scans, and reading data must be exactly as the suite found them. The helpers exist to enforce this; when a test needs protection the helpers don't provide, expand the helpers rather than hand-rolling. New known-gotchas from the 2026-10-21 incident cluster: 7. scanner re-extraction is write-after-success — audio_metadata is guarded on both writers; new scan-owned column writes need the same guard. 8. templ double-escapes style attribute values (sanitizer + EscapeString) — emit quote-free CSS via unquoted url() tokens through SafeCSS. 9. vp test runs the vite config — build-hook plugins fire in the test context too; outDir-mutating plugins need an "am I a build?" guard. --- AGENTS.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b729a8e2..85885f00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,16 @@ were name-only across ALL libraries. Fixed; do not regress. ## Tests +- **THE DOCTRINE — tests are never run against a real/production + server.** The dev DB is the test environment, and the invariant every + test must uphold is: **leave the testuser experience alone** — its + settings, scans, and reading data must be exactly as the suite found + them. The helpers exist to enforce this; if a test needs protection + the helpers don't provide, EXPAND THE HELPERS rather than hand-rolling + (2026-10-21: `setSystemSettingRestored` — snapshot-and-restore for + system_settings — exists because a hardcoded ev restore could clobber + a deliberate dev bump, and the live dev server obeys that setting on + its next poll). - Use `setupTestServer()` from `cmd/server/tests/test_helpers_test.go` (see the Conventions section below for the pool/cleanup rules). - Integration tests run against the SHARED dev database: never write @@ -223,3 +233,25 @@ of a server bug: stop and flag the user. test pools use `max_conns=1`; never create a `pgxpool` per subtest (connection exhaustion); use `t.Cleanup()`-based helpers; table-driven subtests over duplicate functions. +7. **Scanner re-extraction is write-after-success** — a failed/empty + extraction must NEVER clear scan-derived columns. `updateMediaItem` + (generic full-row path) preserves `existing.AudioMetadata` when the + new extraction carries no audio payload, and `updateAudioBookItem` + refuses empty payloads over populated rows (both guarded 2026-10-21 + after an ev-gated re-extract NULLed every multi-file audiobook's + audio_metadata — books displayed as 1 second long and lost their + track manifests). New scan-owned column writes need the same guard. +8. **templ double-escapes style attribute values** — the codegen wraps + style exprs in a sanitizer AND `templ.EscapeString`, so ANY quote + character (even inside `templ.SafeCSS`) reaches the DOM as literal + `'` text and the CSS never parses (2026-10-21: every book-detail + hero backdrop broken + Firefox fetched `/media/&` → 500 spam). + Emit quote-free CSS: unquoted `url(...)` tokens via `SafeCSS` + + `cssURL()` in book_detail.templ. +9. **`vp test` runs the vite config** — plugins with build hooks fire + in the test context too. `cleanStaleChunks` guards on + `produced.size === 0` precisely because a bare sweep deleted every + hashed chunk in `web/static` when run under vitest (2026-10-21: + `npm run test` gutted the build; every page 404'd its imports). + Any future outDir-mutating plugin needs the same "am I a build?" + guard.