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

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>
This commit is contained in:
2026-06-30 11:33:32 +00:00
co-authored by Claude
parent 3b9a6bc85c
commit da4e597f73
24 changed files with 2212 additions and 3 deletions
+181
View File
@@ -0,0 +1,181 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
createuser,
updateuser,
clientuser,
loginuser,
createabode,
updateabode,
createresident,
createapikey,
} from "../../src/schema/validators.js";
describe("createuser validator", () => {
it("accepts a valid CreateUser object", () => {
const result = createuser({
email: "user@example.com",
name: "Alice",
password: "#unset",
flags: {},
});
assert.equal(result, true);
});
it("rejects missing name", () => {
const result = createuser({
email: "user@example.com",
password: "#unset",
flags: {},
});
assert.equal(result, false);
assert.ok(createuser.errors && createuser.errors.length > 0);
});
it("rejects missing email", () => {
const result = createuser({
name: "Alice",
password: "#unset",
flags: {},
});
assert.equal(result, false);
});
it("rejects missing password", () => {
const result = createuser({
email: "user@example.com",
name: "Alice",
flags: {},
});
assert.equal(result, false);
});
});
describe("updateuser validator", () => {
it("accepts update with just uid", () => {
const result = updateuser({ uid: "11111111-1111-1111-1111-111111111111" });
assert.equal(result, true);
});
it("accepts update with uid and name", () => {
const result = updateuser({
uid: "11111111-1111-1111-1111-111111111111",
name: "New Name",
});
assert.equal(result, true);
});
it("rejects missing uid", () => {
const result = updateuser({ name: "Alice" });
assert.equal(result, false);
});
});
describe("clientuser validator", () => {
it("accepts a valid ClientUser (no password)", () => {
const result = clientuser({
uid: "11111111-1111-1111-1111-111111111111",
email: "user@example.com",
name: "Alice",
flags: {},
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-01-01T00:00:00Z",
});
assert.equal(result, true);
});
it("rejects object with password field", () => {
const result = clientuser({
uid: "11111111-1111-1111-1111-111111111111",
email: "user@example.com",
name: "Alice",
password: "#unset",
flags: {},
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-01-01T00:00:00Z",
});
assert.equal(result, false);
});
});
describe("loginuser validator", () => {
it("accepts valid LoginUser", () => {
const result = loginuser({ email: "user@example.com", password: "secret" });
assert.equal(result, true);
});
it("rejects missing password", () => {
const result = loginuser({ email: "user@example.com" });
assert.equal(result, false);
});
it("rejects missing email", () => {
const result = loginuser({ password: "secret" });
assert.equal(result, false);
});
});
describe("createabode validator", () => {
it("accepts valid CreateAbode", () => {
const result = createabode({ name: "My Abode" });
assert.equal(result, true);
});
it("rejects missing name", () => {
const result = createabode({});
assert.equal(result, false);
});
});
describe("updateabode validator", () => {
it("accepts update with just aid", () => {
const result = updateabode({ aid: "22222222-2222-2222-2222-222222222222" });
assert.equal(result, true);
});
it("accepts update with aid and name", () => {
const result = updateabode({
aid: "22222222-2222-2222-2222-222222222222",
name: "New Name",
});
assert.equal(result, true);
});
});
describe("createresident validator", () => {
it("accepts valid CreateResident", () => {
const result = createresident({
uid: "11111111-1111-1111-1111-111111111111",
aid: "22222222-2222-2222-2222-222222222222",
flags: {},
});
assert.equal(result, true);
});
it("rejects missing uid", () => {
const result = createresident({
aid: "22222222-2222-2222-2222-222222222222",
flags: {},
});
assert.equal(result, false);
});
});
describe("createapikey validator", () => {
it("accepts valid CreateApikey", () => {
const result = createapikey({
uid: "11111111-1111-1111-1111-111111111111",
name: "My Key",
permissions: {},
});
assert.equal(result, true);
});
it("rejects missing name", () => {
const result = createapikey({
uid: "11111111-1111-1111-1111-111111111111",
permissions: {},
});
assert.equal(result, false);
});
});