test: meaningful unit tests for server models/controllers + client logic #86

Merged
whitlocktech merged 3 commits from test/coverage-meaningful-gaps into main 2026-07-21 05:55:49 +00:00
Member

What & why

Fill the meaningful test gaps across the repo — behavior a future change could silently break — rather than chasing a coverage number. Three scoped commits, one PR:

  1. test(server): pages, shardState, moderation model logic — security-relevant model rules against an in-memory fake db.
  2. test(server): auth, invite, password-reset, public/shard controllers — controller decision logic + security boundaries (mock req/res, monkeypatched collaborators).
  3. test(client): pure-logic modules + CI/Sonar wiring — the client's lib/, api/, data/ logic on Node's built-in runner, wired into the PR gate and Sonar coverage.

The motivation is the same as the recent server-coverage CI work: make Runic Gateway the kind of project you can safely change months later. The 46% Sonar aggregate was mostly client/bot sitting at 0% (no runner); this establishes a client harness and closes the highest-value server gaps.

Highlights of what's now locked

  • auth/password-reset non-enumeration: every credential failure returns the identical generic message; a reset request answers the same whether or not the email exists (even on internal error).
  • The TOTP gate: a 2FA user gets a challenge, never a session, until the code verifies.
  • Invite integrity: users created at the invite's preset role; a lost double-accept race rolls back the orphaned user.
  • Draft/leak boundaries: staff-only draft visibility, token-gated page preview, the PUBLIC_KINDS feed allowlist, and the public house view stripping owner/price.
  • Model invariants: pages' protected ON-via-update / OFF-only-via-unprotect asymmetry + slug immutability; shardState's partial-refresh field dropping and is_idoc derivation; region buckets always reconciling to the online total.

How it was tested

cd server && DB_HOST=127.0.0.1 DB_PORT=59999 node --test test/*.test.js   # 380 pass
cd client && npm test                                                     # 43 pass

All 423 tests green locally. No production code changed — tests + CI/Sonar config only.

Coverage lifts (line): server aggregate 63.5% → 70.4%; auth.controller 46→94%, passwordReset 33→93%, pages.model 23→82%, moderation.model 27→85%. New client tests: format/regionBuckets/heroLayout 100%, api client 86%, shardEvents 80%.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally.
  • I have added or updated tests/docs where it makes sense.
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code (Opus 4.8). I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a Co-Authored-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later), and I have the right to contribute it.

🤖 Generated with Claude Code

## What & why Fill the **meaningful** test gaps across the repo — behavior a future change could silently break — rather than chasing a coverage number. Three scoped commits, one PR: 1. **`test(server): pages, shardState, moderation model logic`** — security-relevant model rules against an in-memory fake db. 2. **`test(server): auth, invite, password-reset, public/shard controllers`** — controller decision logic + security boundaries (mock req/res, monkeypatched collaborators). 3. **`test(client): pure-logic modules + CI/Sonar wiring`** — the client's `lib/`, `api/`, `data/` logic on Node's built-in runner, wired into the PR gate and Sonar coverage. The motivation is the same as the recent server-coverage CI work: make Runic Gateway the kind of project you can safely change months later. The 46% Sonar aggregate was mostly client/bot sitting at 0% (no runner); this establishes a client harness and closes the highest-value server gaps. ### Highlights of what's now locked - **auth/password-reset non-enumeration:** every credential failure returns the identical generic message; a reset request answers the same whether or not the email exists (even on internal error). - **The TOTP gate:** a 2FA user gets a challenge, never a session, until the code verifies. - **Invite integrity:** users created at the invite's *preset role*; a lost double-accept race rolls back the orphaned user. - **Draft/leak boundaries:** staff-only draft visibility, token-gated page preview, the `PUBLIC_KINDS` feed allowlist, and the public house view stripping owner/price. - **Model invariants:** pages' protected ON-via-update / OFF-only-via-unprotect asymmetry + slug immutability; shardState's partial-refresh field dropping and `is_idoc` derivation; region buckets always reconciling to the online total. ## How it was tested ``` cd server && DB_HOST=127.0.0.1 DB_PORT=59999 node --test test/*.test.js # 380 pass cd client && npm test # 43 pass ``` All 423 tests green locally. No production code changed — tests + CI/Sonar config only. Coverage lifts (line): server aggregate **63.5% → 70.4%**; auth.controller 46→94%, passwordReset 33→93%, pages.model 23→82%, moderation.model 27→85%. New client tests: format/regionBuckets/heroLayout 100%, api client 86%, shardEvents 80%. ## Checklist - [x] I have read CONTRIBUTING.md. - [x] The change builds and existing tests/checks pass locally. - [x] I have added or updated tests/docs where it makes sense. - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code (Opus 4.8)`. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a `Co-Authored-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 3 commits 2026-07-21 05:39:42 +00:00
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>
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>
test(client): unit-test the pure-logic modules + wire coverage into CI/Sonar
All checks were successful
PR Checks / bot-install (pull_request) Successful in 15s
PR Checks / client-build (pull_request) Successful in 44s
PR Checks / server-tests (pull_request) Successful in 9m36s
886a504152
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>
whitlocktech scheduled this pull request to auto merge when all checks succeed 2026-07-21 05:55:32 +00:00
whitlocktech approved these changes 2026-07-21 05:55:44 +00:00
whitlocktech merged commit 68a4e9295b into main 2026-07-21 05:55:49 +00:00
whitlocktech deleted branch test/coverage-meaningful-gaps 2026-07-21 05:55:50 +00:00
Sign in to join this conversation.
No description provided.