feat(tests): add unit and integration test suite (node:test) #2

Merged
codinget merged 3 commits from worktree-bridge-cse_01DnbLssZmKCGrmF36puLCWs into master 2026-07-20 02:32:57 +02:00
Owner

Summary

Adds a comprehensive test suite (215 tests, 0 failures) using Node.js built-ins node:test + node:assert/strict — no new test framework dependencies. TypeScript is run via tsx (already a devDependency).

Test structure

Directory What it covers
test/tools/ Middleware and utilities: token, hash, authenticate, jsonBody, convertError, validators
test/shared/ DbInterface/BackendDbInterface contract suites reusable across backends
test/backends/sqlite/ SQLite internals (sql builder, WrappedDb, migrator) + shared suites
test/backends/api/ ApiInterface unit tests + shared suites via a live Koa server backed by SQLite

npm scripts

npm test               # all 215 tests
npm run test:tools     # fast, no DB
npm run test:backends  # all backend tests
npm run test:shared    # shared contract suites only

Bugs fixed by tests

Four bugs in the production code were uncovered:

  • ApiInterface path params leaked into query stringremaining.delete(part) used the :uid key (with colon) instead of part.slice(1), so path parameters also appeared as query params.
  • ApiInterface called res.json() on 204 responses — DELETE endpoints return 204 No Content; calling .json() threw "Unexpected end of JSON input".
  • SqliteInterface.updateResident SQL syntax error — missing comma between updated_by = ? and joinSql(updates) meant resident flags could never be updated.
  • WrappedBetterSqlite3Db rejected readonly: undefinedbetter-sqlite3 requires readonly to be a boolean; passing undefined threw on construction.
## Summary Adds a comprehensive test suite (215 tests, 0 failures) using Node.js built-ins `node:test` + `node:assert/strict` — no new test framework dependencies. TypeScript is run via `tsx` (already a devDependency). ### Test structure | Directory | What it covers | |---|---| | `test/tools/` | Middleware and utilities: token, hash, authenticate, jsonBody, convertError, validators | | `test/shared/` | `DbInterface`/`BackendDbInterface` contract suites reusable across backends | | `test/backends/sqlite/` | SQLite internals (sql builder, WrappedDb, migrator) + shared suites | | `test/backends/api/` | ApiInterface unit tests + shared suites via a live Koa server backed by SQLite | ### npm scripts ``` npm test # all 215 tests npm run test:tools # fast, no DB npm run test:backends # all backend tests npm run test:shared # shared contract suites only ``` ### Bugs fixed by tests Four bugs in the production code were uncovered: - **`ApiInterface` path params leaked into query string** — `remaining.delete(part)` used the `:uid` key (with colon) instead of `part.slice(1)`, so path parameters also appeared as query params. - **`ApiInterface` called `res.json()` on 204 responses** — DELETE endpoints return 204 No Content; calling `.json()` threw "Unexpected end of JSON input". - **`SqliteInterface.updateResident` SQL syntax error** — missing comma between `updated_by = ?` and `joinSql(updates)` meant resident flags could never be updated. - **`WrappedBetterSqlite3Db` rejected `readonly: undefined`** — `better-sqlite3` requires `readonly` to be a boolean; passing `undefined` threw on construction.
codinget added the Kind/BugKind/TestingAgentic
Agent
Sonnet
labels 2026-06-30 13:34:27 +02:00
codinget added 1 commit 2026-06-30 13:34:28 +02:00
Adds 215 tests across three tiers using node:test + node:assert/strict
(no new test framework dependencies):

- test/tools/ — middleware and utility tests (token, hash, authenticate,
  jsonBody, convertError, validators)
- test/shared/ — DbInterface/BackendDbInterface contract suites reusable
  across backends (users, abodes, residents, apikeys, sessions, auth)
- test/backends/sqlite/ — SQLite-private tests (sql builder, WrappedDb,
  migrator) + shared suites via SqliteInterface
- test/backends/api/ — ApiInterface unit tests + shared suites via a
  live Koa server backed by SQLite

Also fixes four bugs uncovered by the tests:
- ApiInterface: path params leaked into query string (slice(1) fix)
- ApiInterface: calling res.json() on 204 No Content responses
- SqliteInterface.updateResident: missing comma in SET clause
- WrappedBetterSqlite3Db: readonly:undefined rejected by better-sqlite3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Review: approve, minor gaps only

Ran the suite: 215/215 passing, 48 suites, 0 flaky across repeated runs. npx tsc --noEmit passes. package.json only adds npm scripts (test/test:backends/test:shared/test:tools), no new deps — matches the "no new framework dependencies" claim.

All 4 production bug fixes have real regression coverage:

  • ApiInterface.#url path-param leak → directly asserted in test/backends/api/api-interface.test.ts.
  • 204 handling → exercised indirectly via the delete-* shared suites running against a live Koa server.
  • SqliteInterface.updateResident missing comma → covered by updateResident updates flags in test/shared/residents.ts (fails with a SQL syntax error without the fix).
  • better-sqlite3.ts readonly fix → exercised by every sqlite-backed test (constructor always passes the readonly key).

Issues:

  1. tsconfig.json's include only covers src/**/*.ts, so test/ is never type-checked. Building a parallel tsconfig surfaces real type errors: test/shared/users.tshashedPw typed as plain string instead of the hash template-literal type (TS2322 at several call sites), and found.email accessed on a PartialUser | ClientUser union where PartialUser has no email (TS2339). Harmless at runtime but means the test suite currently ships with an un-type-checked blind spot. Suggest wiring test/**/*.ts into a typecheck script.
  2. test/backends/api/index.test.ts only wires up runUserTests/runAbodeTests/runResidentTests/runApikeyTests against the live Koa server — runSessionTests/runAuthTests never run over HTTP, only against sqlite directly. Given this PR's main value is catching integration bugs like the 204/path-param issues, this is the most likely place a similar bug could hide (login, session cookies, apikey-auth header parsing on the server side).
  3. test/shared/users.ts — the "createUser on readonly db throws" test starts with if (!db.readonly) return;, but no test helper ever constructs a readonly db instance for either backend, so this test body never actually executes for sqlite or api. It's currently a no-op that looks like real coverage.

None of these block merging — the suite as-is already catches real bugs (see the 4 fixes above) and resource cleanup (Koa server close, in-memory sqlite) looks correct. Worth merging first among the three open PRs since #3 depends on the same fixes.

**Review: approve, minor gaps only** Ran the suite: 215/215 passing, 48 suites, 0 flaky across repeated runs. `npx tsc --noEmit` passes. package.json only adds npm scripts (test/test:backends/test:shared/test:tools), no new deps — matches the "no new framework dependencies" claim. All 4 production bug fixes have real regression coverage: - `ApiInterface.#url` path-param leak → directly asserted in `test/backends/api/api-interface.test.ts`. - 204 handling → exercised indirectly via the delete-* shared suites running against a live Koa server. - `SqliteInterface.updateResident` missing comma → covered by `updateResident updates flags` in `test/shared/residents.ts` (fails with a SQL syntax error without the fix). - `better-sqlite3.ts` readonly fix → exercised by every sqlite-backed test (constructor always passes the `readonly` key). **Issues:** 1. `tsconfig.json`'s `include` only covers `src/**/*.ts`, so `test/` is never type-checked. Building a parallel tsconfig surfaces real type errors: `test/shared/users.ts` — `hashedPw` typed as plain `string` instead of the hash template-literal type (TS2322 at several call sites), and `found.email` accessed on a `PartialUser | ClientUser` union where `PartialUser` has no `email` (TS2339). Harmless at runtime but means the test suite currently ships with an un-type-checked blind spot. Suggest wiring `test/**/*.ts` into a typecheck script. 2. `test/backends/api/index.test.ts` only wires up `runUserTests`/`runAbodeTests`/`runResidentTests`/`runApikeyTests` against the live Koa server — `runSessionTests`/`runAuthTests` never run over HTTP, only against sqlite directly. Given this PR's main value is catching integration bugs like the 204/path-param issues, this is the most likely place a similar bug could hide (login, session cookies, apikey-auth header parsing on the server side). 3. `test/shared/users.ts` — the "createUser on readonly db throws" test starts with `if (!db.readonly) return;`, but no test helper ever constructs a readonly db instance for either backend, so this test body never actually executes for sqlite or api. It's currently a no-op that looks like real coverage. None of these block merging — the suite as-is already catches real bugs (see the 4 fixes above) and resource cleanup (Koa server close, in-memory sqlite) looks correct. Worth merging first among the three open PRs since #3 depends on the same fixes.
codinget added 1 commit 2026-07-02 03:38:23 +02:00
- Add tsconfig.test.json + typecheck:test script so test/ is type-checked;
  fixes real type errors it surfaced (hashedPw typing, PartialUser|ClientUser
  narrowing for .email).
- Add HTTP-level auth-http.test.ts for the api backend covering
  login/session-cookie/logout/clear-sessions/bearer-apikey flows, since
  ApiInterface doesn't implement the session/login methods needed to run the
  shared session/auth suites directly.
- Make the readonly-db test in shared/users.ts actually construct a readonly
  db instance (previously a no-op that never ran) via a new getReadonlyDb
  parameter, wired up for both sqlite and api backends.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
codinget added 1 commit 2026-07-02 03:43:52 +02:00
/auth/logout previously only cleared the client's cookie, leaving the
session token valid in the sessions table — a stolen cookie captured
before logout would still work afterwards. Add BackendDbInterface#deleteSession
(implemented in SqliteInterface) and call it from the logout route using
the session token from the cookie. Caught by the new auth-http.test.ts
integration test, updated to assert the session is actually invalidated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Author
Owner

Addressed all three issues from the review, plus a security-relevant bug that turned up while fixing them:

Review issues (4d9a0cf):

  1. Type-checking blind spot: Added tsconfig.test.json (extends the base config, includes test/**/*.ts) plus a typecheck:test npm script. Fixed the real type errors it surfaced in test/shared/users.tshashedPw now typed via Awaited<ReturnType<typeof hashPassword>> instead of plain string, and found.email access now goes through an "email" in found narrowing check against the PartialUser | ClientUser union.

  2. Session/auth not exercised over HTTP for the api backend: runSessionTests/runAuthTests can't be wired up against ApiInterface directly — it doesn't implement getUserBySession/createSession/getUserByApikey (server-only methods, not part of the client-facing DbInterface surface ApiInterface implements). Instead added test/backends/api/auth-http.test.ts, driving the live Koa server directly via fetch: login sets a session cookie, cookie auth works on protected routes, /auth/self without credentials returns 401, logout clears the cookie, /auth/clear-sessions invalidates it, and Bearer apikey auth (valid + invalid) is exercised.

  3. No-op readonly test: Added createReadonlyTestDb() / getReadonlyApiDb() helpers that construct real readonly db instances, wired into runUserTests via a new optional getReadonlyDb parameter for both backends. The test now explicitly t.skip()s if the helper isn't provided instead of silently short-circuiting.

Bug found by the new HTTP test (73c4b16): /auth/logout only cleared the client's cookie — the session token stayed valid server-side, so a cookie captured before logout (e.g. via XSS or a synced device) would keep working after the user logged out. Added BackendDbInterface#deleteSession (implemented in SqliteInterface) and wired it into the logout route so the session is actually invalidated, not just forgotten client-side. The auth-http test now asserts this directly.

Full suite: 222/222 passing, tsc --noEmit clean on both tsconfig.json and tsconfig.test.json.

Addressed all three issues from the review, plus a security-relevant bug that turned up while fixing them: **Review issues (4d9a0cf):** 1. **Type-checking blind spot**: Added `tsconfig.test.json` (extends the base config, includes `test/**/*.ts`) plus a `typecheck:test` npm script. Fixed the real type errors it surfaced in `test/shared/users.ts` — `hashedPw` now typed via `Awaited<ReturnType<typeof hashPassword>>` instead of plain `string`, and `found.email` access now goes through an `"email" in found` narrowing check against the `PartialUser | ClientUser` union. 2. **Session/auth not exercised over HTTP for the api backend**: `runSessionTests`/`runAuthTests` can't be wired up against `ApiInterface` directly — it doesn't implement `getUserBySession`/`createSession`/`getUserByApikey` (server-only methods, not part of the client-facing `DbInterface` surface `ApiInterface` implements). Instead added `test/backends/api/auth-http.test.ts`, driving the live Koa server directly via `fetch`: login sets a session cookie, cookie auth works on protected routes, `/auth/self` without credentials returns 401, logout clears the cookie, `/auth/clear-sessions` invalidates it, and Bearer apikey auth (valid + invalid) is exercised. 3. **No-op readonly test**: Added `createReadonlyTestDb()` / `getReadonlyApiDb()` helpers that construct real readonly db instances, wired into `runUserTests` via a new optional `getReadonlyDb` parameter for both backends. The test now explicitly `t.skip()`s if the helper isn't provided instead of silently short-circuiting. **Bug found by the new HTTP test (73c4b16):** `/auth/logout` only cleared the client's cookie — the session token stayed valid server-side, so a cookie captured before logout (e.g. via XSS or a synced device) would keep working after the user logged out. Added `BackendDbInterface#deleteSession` (implemented in `SqliteInterface`) and wired it into the logout route so the session is actually invalidated, not just forgotten client-side. The auth-http test now asserts this directly. Full suite: 222/222 passing, `tsc --noEmit` clean on both `tsconfig.json` and `tsconfig.test.json`.
Author
Owner

Follow-up: all three gaps addressed, plus a real security fix surfaced along the way

Re-checked the branch — 222/222 tests pass, npm run typecheck and the new npm run typecheck:test are both clean.

All three issues from my earlier review are resolved:

  1. tsconfig.test.json + typecheck:test now type-checks test/, and the real type errors it surfaced (hashedPw typing, PartialUser | ClientUser narrowing on .email) are fixed.
  2. test/backends/api/auth-http.test.ts now covers login/session-cookie/logout/clear-sessions/bearer-apikey over real HTTP against the api backend (previously only sqlite exercised these).
  3. The readonly-db test in test/shared/users.ts now actually constructs a readonly instance via getReadonlyDb for both backends, so it's no longer a silent no-op.

And that new HTTP-level logout test caught a genuine security bug (commit 73c4b16): POST /auth/logout only cleared the client's cookie — the session token stayed valid in the sessions table. A cookie captured before logout (XSS, log leakage, shared machine, proxy capture, etc.) would keep working indefinitely after the user "logged out." Fixed by adding BackendDbInterface#deleteSession and calling it from the logout route with the token from the cookie before clearing it. Verified the fix: auth-http.test.ts now asserts GET /auth/self returns 401 with the old cookie post-logout, and I confirmed #checkReadonly() / cookie name (abode_session) / read-before-clear ordering are all correct in apirouter.ts.

This is a good example of exactly the kind of bug this test suite is meant to catch (real bug, live HTTP layer, not visible from a sqlite-only unit test) — nice find. Approving.

**Follow-up: all three gaps addressed, plus a real security fix surfaced along the way** Re-checked the branch — 222/222 tests pass, `npm run typecheck` and the new `npm run typecheck:test` are both clean. All three issues from my earlier review are resolved: 1. `tsconfig.test.json` + `typecheck:test` now type-checks `test/`, and the real type errors it surfaced (`hashedPw` typing, `PartialUser | ClientUser` narrowing on `.email`) are fixed. 2. `test/backends/api/auth-http.test.ts` now covers login/session-cookie/logout/clear-sessions/bearer-apikey over real HTTP against the api backend (previously only sqlite exercised these). 3. The readonly-db test in `test/shared/users.ts` now actually constructs a readonly instance via `getReadonlyDb` for both backends, so it's no longer a silent no-op. **And that new HTTP-level logout test caught a genuine security bug (commit 73c4b16):** `POST /auth/logout` only cleared the client's cookie — the session token stayed valid in the `sessions` table. A cookie captured before logout (XSS, log leakage, shared machine, proxy capture, etc.) would keep working indefinitely after the user "logged out." Fixed by adding `BackendDbInterface#deleteSession` and calling it from the logout route with the token from the cookie before clearing it. Verified the fix: `auth-http.test.ts` now asserts `GET /auth/self` returns 401 with the old cookie post-logout, and I confirmed `#checkReadonly()` / cookie name (`abode_session`) / read-before-clear ordering are all correct in `apirouter.ts`. This is a good example of exactly the kind of bug this test suite is meant to catch (real bug, live HTTP layer, not visible from a sqlite-only unit test) — nice find. Approving.
codinget merged commit 73c4b169c5 into master 2026-07-20 02:32:57 +02:00
codinget deleted branch worktree-bridge-cse_01DnbLssZmKCGrmF36puLCWs 2026-07-20 02:32:57 +02:00
Sign in to join this conversation.