Stand up a client test suite on Node's built-in runner (no vitest/jsdom — the
targeted modules are plain ESM with no browser/DOM deps) and cover the
meaningful client logic, not presentational components:
- api/client.js: the fetch wrapper — always sends the session cookie, maps a
non-2xx response to a thrown ApiError (body.message → statusText fallback),
resolves an empty body to null, sets Content-Type for JSON but NOT for raw
FormData uploads, and builds/encodes query strings + path params.
- lib/shardEvents.js: describe() across event kinds (payload vs live frame,
actor name→acct→"Someone" fallback, sale pluralization, champ.update
branches) and the categoryOf table-consistency check.
- data/regionBuckets.js: the presence roll-up, incl. the first-match-wins
ordering and the "buckets always reconcile to the total" invariant.
- lib/heroLayout.js: parseLayout's version/shape guard and heroBackground's
default-vs-custom branch.
- lib/format.js: the date/label formatters + relative-time buckets.
Wiring so these actually count:
- client/package.json gains a `test` script (node --test);
- pr-checks.yml runs the client tests as a PR gate;
- sonarqube.yml generates a client LCOV report and sonar-project.properties
feeds it alongside the server report (SF paths resolve to client/src/...).
43 client tests; coverage on the tested modules: format/regionBuckets/
heroLayout 100%, api client 86%, shardEvents 80%.
Co-Authored-By: Claude <noreply@anthropic.com>
Add controller-level unit tests (mock req/res, monkeypatched collaborators)
focused on security boundaries and decision logic the API must not regress:
- auth.controller: honeypot handling, non-enumerating generic-fail for every
credential failure, inactive-account refusal, the TOTP challenge branch that
must NOT issue a session, register-mode gating + dup-username 409, and logout
that always clears the cookie and revokes the session (even on error).
- invite.controller: user created at the invite's PRESET role, and the lost
double-accept race rolling back the just-created user.
- passwordReset.controller: identical generic 200 whether or not the email
matched (incl. internal errors), per-account mail-failure isolation, the
single-use consume race, and revoke-everywhere-on-reset with no auto-login.
- public.controller: staff-only draft visibility, token-gated page preview,
wiki search precedence + unknown-filter handling, contact 502.
- shard.controller (public): the PUBLIC_KINDS feed allowlist and the public
house view stripping owner/price — both leak-prevention boundaries.
Lifts: auth.controller 46%→94%, passwordReset 33%→93%,
public.controller 28%→65%, shard.controller 45%→68% line coverage;
server aggregate 63.5%→70.4%.
Co-Authored-By: Claude <noreply@anthropic.com>
Add meaningful unit tests for three server models with untested business
logic, each against an in-memory fake db (no DB required):
- pages.model: slug validation + reserved-name guard, slug immutability,
the protected ON-via-update / OFF-only-via-unprotect asymmetry, publish
stamping, draft invisibility to public reads, dup-slug → 409, block gate.
- shardState.model: partial-refresh field dropping (vitals must not clobber
login fields), is_idoc derivation, economy clamp/ordering/Number coercion,
presence zero-snapshot defaults, payload-fallback shaping, and the
camelCase read-shaping contract the site + Android client depend on.
- moderation.model: five-feed window merge, userSummary count/total
semantics (total sums unknown types too), and graceful degradation when
bot config is missing.
Lifts: pages.model 23%→82%, moderation.model 27%→85%,
shardState.model 33%→62% line coverage.
Co-Authored-By: Claude <noreply@anthropic.com>