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>
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { sql, catSql, joinSql, calcUpdates, unsafeSql } from "../../../src/db/sqlite/sql.js";
|
|
|
|
describe("sql template tag", () => {
|
|
it("produces correct sql and empty vars for plain text", () => {
|
|
const result = sql`SELECT 1`;
|
|
assert.equal(result._sql, "SELECT 1");
|
|
assert.deepEqual(result._vars, []);
|
|
});
|
|
|
|
it("binds text args with ?", () => {
|
|
const result = sql`WHERE name = ${{ text: "alice" }}`;
|
|
assert.equal(result._sql, "WHERE name = ?");
|
|
assert.deepEqual(result._vars, ["alice"]);
|
|
});
|
|
|
|
it("binds uuid args with ? and converts to Buffer", () => {
|
|
const uuid = "12345678-1234-1234-1234-123456789abc";
|
|
const result = sql`WHERE uid = ${{ uuid }}`;
|
|
assert.equal(result._sql, "WHERE uid = ?");
|
|
assert.equal(result._vars.length, 1);
|
|
assert.ok(result._vars[0] instanceof Buffer, "uuid is stored as Buffer");
|
|
});
|
|
|
|
it("binds jsonb args with jsonb(?) wrapper", () => {
|
|
const result = sql`SET flags = ${{ jsonb: { admin: true } }}`;
|
|
assert.equal(result._sql, "SET flags = jsonb(?)");
|
|
assert.deepEqual(result._vars, [JSON.stringify({ admin: true })]);
|
|
});
|
|
|
|
it("binds date args with datetime(?, ...) wrapper", () => {
|
|
const date = "2024-01-01T00:00:00.000Z";
|
|
const result = sql`SET ts = ${{ date }}`;
|
|
assert.ok(result._sql.startsWith("SET ts = datetime("), result._sql);
|
|
assert.equal(result._vars.length, 1);
|
|
});
|
|
|
|
it("binds int args with ?", () => {
|
|
const result = sql`LIMIT ${{ int: 10 }}`;
|
|
assert.equal(result._sql, "LIMIT ?");
|
|
assert.deepEqual(result._vars, [10]);
|
|
});
|
|
|
|
it("throws for non-integer int value", () => {
|
|
assert.throws(() => sql`LIMIT ${{ int: 10.5 }}`, /Not an integer/);
|
|
});
|
|
|
|
it("emits NULL for null args", () => {
|
|
const result = sql`= ${{ null: true }}`;
|
|
assert.equal(result._sql, "= NULL");
|
|
assert.deepEqual(result._vars, []);
|
|
});
|
|
|
|
it("splices nested SqlCode", () => {
|
|
const inner = sql`AND x = ${{ text: "foo" }}`;
|
|
const outer = sql`WHERE 1=1 ${inner}`;
|
|
assert.equal(outer._sql, "WHERE 1=1 AND x = ?");
|
|
assert.deepEqual(outer._vars, ["foo"]);
|
|
});
|
|
|
|
it("handles multiple args", () => {
|
|
const result = sql`INSERT INTO t(a,b) VALUES(${{ text: "x" }}, ${{ int: 42 }})`;
|
|
assert.equal(result._sql, "INSERT INTO t(a,b) VALUES(?, ?)");
|
|
assert.deepEqual(result._vars, ["x", 42]);
|
|
});
|
|
});
|
|
|
|
describe("catSql", () => {
|
|
it("concatenates sql and vars", () => {
|
|
const a = sql`SELECT * FROM t`;
|
|
const b = sql` WHERE x = ${{ text: "y" }}`;
|
|
const result = catSql(a, b);
|
|
assert.equal(result._sql, "SELECT * FROM t WHERE x = ?");
|
|
assert.deepEqual(result._vars, ["y"]);
|
|
});
|
|
});
|
|
|
|
describe("joinSql", () => {
|
|
it("joins multiple parts with separator", () => {
|
|
const parts = [
|
|
sql`a = ${{ text: "1" }}`,
|
|
sql`b = ${{ text: "2" }}`,
|
|
sql`c = ${{ text: "3" }}`,
|
|
];
|
|
const result = joinSql(parts, sql`, `);
|
|
assert.equal(result._sql, "a = ?, b = ?, c = ?");
|
|
assert.deepEqual(result._vars, ["1", "2", "3"]);
|
|
});
|
|
|
|
it("returns single part unchanged (no separator)", () => {
|
|
const result = joinSql([sql`x = ${{ int: 1 }}`], sql`, `);
|
|
assert.equal(result._sql, "x = ?");
|
|
assert.deepEqual(result._vars, [1]);
|
|
});
|
|
});
|
|
|
|
describe("calcUpdates", () => {
|
|
it("returns only keys present in the object", () => {
|
|
const calc = calcUpdates({
|
|
name: (v: string) => sql`name = ${{ text: v }}`,
|
|
email: (v: string) => sql`email = ${{ text: v }}`,
|
|
});
|
|
const updates = calc({ name: "alice" });
|
|
assert.equal(updates.length, 1);
|
|
assert.equal(updates[0]._sql, "name = ?");
|
|
assert.deepEqual(updates[0]._vars, ["alice"]);
|
|
});
|
|
|
|
it("returns all keys when all are present", () => {
|
|
const calc = calcUpdates({
|
|
a: (v: string) => sql`a = ${{ text: v }}`,
|
|
b: (v: string) => sql`b = ${{ text: v }}`,
|
|
});
|
|
const updates = calc({ a: "x", b: "y" });
|
|
assert.equal(updates.length, 2);
|
|
});
|
|
|
|
it("returns empty array when no keys match", () => {
|
|
const calc = calcUpdates({
|
|
name: (v: string) => sql`name = ${{ text: v }}`,
|
|
});
|
|
const updates = calc({});
|
|
assert.equal(updates.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("unsafeSql", () => {
|
|
it("wraps a raw sql string with no vars", () => {
|
|
const result = unsafeSql("CREATE TABLE t (id INTEGER)");
|
|
assert.equal(result._sql, "CREATE TABLE t (id INTEGER)");
|
|
assert.deepEqual(result._vars, []);
|
|
});
|
|
});
|