Files
abode/test/backends/sqlite/wrapped-db.test.ts
T
codingetandClaude da4e597f73 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>
2026-06-30 11:33:32 +00:00

112 lines
3.8 KiB
TypeScript

import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { WrappedNodeSqliteDb } from "../../../src/db/sqlite/impl/node-sqlite.js";
import { sql, unsafeSql } from "../../../src/db/sqlite/sql.js";
import type { WrappedDb } from "../../../src/db/sqlite/impl/types.js";
function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) {
describe(`${name}: WrappedDb`, () => {
let db: WrappedDb;
before(() => {
db = makeDb();
db.run(unsafeSql("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)"));
});
after(() => db.destroy());
it("run INSERT returns changes count", () => {
const { changes } = db.run(sql`INSERT INTO test(val) VALUES(${{ text: "hello" }})`);
assert.equal(changes, 1);
});
it("all SELECT returns all rows", () => {
db.run(unsafeSql("DELETE FROM test"));
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "a" }})`);
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "b" }})`);
const rows = db.all<{ val: string }>(unsafeSql("SELECT val FROM test ORDER BY val"));
assert.equal(rows.length, 2);
assert.equal(rows[0].val, "a");
assert.equal(rows[1].val, "b");
});
it("get returns single row or null", () => {
db.run(unsafeSql("DELETE FROM test"));
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "one" }})`);
const row = db.get<{ val: string }>(unsafeSql("SELECT val FROM test"));
assert.ok(row !== null);
assert.equal(row.val, "one");
const none = db.get<{ val: string }>(
sql`SELECT val FROM test WHERE val = ${{ text: "none" }}`
);
assert.equal(none, null);
});
it("get throws when multiple rows match", () => {
db.run(unsafeSql("DELETE FROM test"));
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "dup1" }})`);
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "dup2" }})`);
assert.throws(
() => db.get<{ val: string }>(unsafeSql("SELECT val FROM test")),
/Multiple results/
);
});
it("multi commits on success", () => {
db.run(unsafeSql("DELETE FROM test"));
db.multi(() => {
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "in-tx" }})`);
});
const rows = db.all<{ val: string }>(unsafeSql("SELECT val FROM test"));
assert.equal(rows.length, 1);
assert.equal(rows[0].val, "in-tx");
});
it("multi rolls back on error", () => {
db.run(unsafeSql("DELETE FROM test"));
assert.throws(() =>
db.multi(() => {
db.run(sql`INSERT INTO test(val) VALUES(${{ text: "rollback" }})`);
throw new Error("abort!");
})
);
const rows = db.all<{ val: string }>(unsafeSql("SELECT val FROM test"));
assert.equal(rows.length, 0);
});
it("rethrow passes through return value", () => {
const result = db.rethrow(() => 42);
assert.equal(result, 42);
});
it("rethrow propagates non-SQLite errors unchanged", () => {
const err = new Error("custom error");
assert.throws(() => db.rethrow(() => { throw err; }), (e) => e === err);
});
});
}
runWrappedDbSuite("node-sqlite", () => new WrappedNodeSqliteDb(":memory:"));
describe("better-sqlite3 WrappedDb", async () => {
let bs3Ctor: (new (path: string) => WrappedDb) | null = null;
try {
const mod = await import(
"../../../src/db/sqlite/impl/better-sqlite3.js"
);
bs3Ctor = mod.WrappedBetterSqlite3Db;
} catch {
// better-sqlite3 not available, skip
}
if (bs3Ctor) {
runWrappedDbSuite("better-sqlite3", () => new bs3Ctor!(":memory:"));
} else {
it("better-sqlite3 is not available - skipped", (t) => {
t.skip("better-sqlite3 optional dependency not found");
});
}
});