Commit Graph
4 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 35b9838238 test(e2e): keep the csp suite free of an npm dependency
33a778891 added the first JavaScript unit test that reaches into a
Playwright helper. csp.js imports `expect` from @playwright/test at module
scope, so requiring it drags an npm package into a suite that is
contractually free of them: Makefile:837 calls the target "node:test, no
npm dependency", scripts/tests/unit/javascript.sh installs only node, and
node_modules/ is gitignored. On a fresh CI checkout the import fails and
the whole JavaScript suite dies, not just the new file. It passes here
only because this machine has node_modules, so neither the local run nor
`make quality-high` can see the break.

The suite now intercepts that single request through Module._load and
hands back a six-line `expect` implementing the one matcher these paths
use, restoring the hook immediately afterwards. Probed: the request never
reaches resolution, `@playwright/test` never enters require.cache.

Also corrects a test name. "an enabled injector without a base URL is not
asserted" claimed to pin `if (!host) continue`, and does not: with no base
URL the host is "" and `includes("")` is true, so the suite stays green
with the guard deleted - verified by removing it. 33a778891's message
overstates this in two places, saying the suite pins every guard in
assertCspInjections and that dropping either toLowerCase trips exactly one
test. Dropping `host.toLowerCase()` trips nothing either, because
`new URL(x).hostname` is already lowercased.

Keeping the unit test rather than dropping it is a deliberate call: the
mirrored-unit-test lint exempts `test-*` roles as circular, so this file is
voluntary. It has already earned its place by catching two live mutants,
the enforced-over-report-only precedence and the csp-side toLowerCase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 04:31:57 +02:00
kevinveenbirkenbachandClaude Opus 5 43ec22ee20 fix(e2e): read the CSP off the response, not a second fetch
readCspString re-fetched the current URL through the Playwright API
request context and, when that failed, waited actionTimeout (30s) for a
<meta http-equiv="Content-Security-Policy"> that no template in this
repo emits. On a healthy runner that path works and costs one extra
request; when name resolution degrades it collapses, because the API
request context resolves DNS separately from the browser: the re-fetch
burns its 10s cap and the meta wait then drains whatever is left of the
caller's test budget.

That is what killed web-svc-logout's guest persona in run 32597248629,
job Pretix#0, whose swarm manager had a single resolver on 127.0.0.1
with dnsmasq no longer answering. Every guest test on that host inflated
to ~40s and the logout one, loading two external assets on top, died on
its 60s timeout having asserted nothing. The same role in Bluesky#0 and
YOURLS#0 of the same run, same role set, finished in 0.85s and 0.82s -
so this is a resilience fix, not a repair of a broken assertion.

gotoOnion now installs a page.on("response") recorder, so the header is
taken from the document the browser already received. That also covers
the surface reached through an SSO round trip, where no navigation
Response is in scope at the assertion - only guest.js holds one, biber,
admin and the suitecrm saml spec discard theirs. The re-fetch stays as
the fallback for a spec that navigates without gotoOnion; the meta
fallback is deleted.

The suite pins the header precedence (enforced over report-only) and
every guard in assertCspInjections: a disabled injector, a gate that
throws, an enabled injector without a base URL, a missing gate, a second
enabled injector, and the case-insensitive host match. Inverting the
precedence or dropping toLowerCase each trips exactly one test.

Verified against a headless Chromium and a local server counting
requests: the header survives a 302, no extra request is issued, a
missing injector host still fails the assertion, and a header-less page
falls back to exactly one re-fetch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 04:31:55 +02:00
kevinveenbirkenbach d391b4cbb8 test(unit): cover the postmarks, mediawiki and baserow shims
Bookwyrm has tests for the same trusted-header SSO bridge and settings
patcher; postmarks and baserow ship theirs untested, and the composer
require-dev stripper is a pure transformation nobody exercised.

Each test was checked by breaking its source: the admin gate failing
open, the group matcher loosened to a prefix, require-dev left in place
and the SSO gate forced open all turn the new tests red.
2026-08-22 22:31:37 +02:00
kevinveenbirkenbachandClaude Opus 5 6fee7ca136 feat(quality): parse every shipped language and run its unit suite
Four parse checks join the `lint-*` family, each using the language's own
parser rather than a reimplementation: `php -l`, `ruby -c`, `sqlfluff parse`,
and stylelint with an empty rule set (it raises CssSyntaxError whatever the
rules say, so the check stays syntax-only and grows no style opinion). Three
unit runners join `test-unit`: node:test, PHPUnit, minitest -- the first and
third ship with their runtime, so only PHPUnit adds a dependency.

Provisioning is best effort and the lint script decides whether it can run. A
machine without PHP must not fail the gate, but it must not look like success
either: the skip names how many files went unchecked. The first version of
these scripts read `git ls-files`, which inside the compose container returns
nothing (git refuses the bind-mounted repo as "dubious ownership") and reported
"No SQL files found." over 52 files -- green, and a lie. They use `find` now,
as the surrounding lint scripts already did.

sqlfluff needed two corrections before it parsed anything real. `%(name)s` is a
psycopg parameter marker, not SQL, so the placeholder templater is configured
rather than the files exempted; and the dialect follows each role's declared
engine (`meta/services.yml`), which is postgres for six roles and mysql for
svc-db-mariadb and pixelfed.

Recipes stay in scripts, not in the Makefile: the targets delegate, matching
every other target in the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:10 +02:00