test: cover sqlite transaction guardrails

This commit is contained in:
Peter Steinberger
2026-05-08 12:46:10 +01:00
parent 5f9660fbfc
commit 3a0ff80ee3
2 changed files with 75 additions and 0 deletions

View File

@@ -96,6 +96,7 @@ const legacyStoreMarkers = [
{ label: "Matrix sync store JSON", pattern: /\bbot-storage\.json\b/u },
{ label: "Matrix storage metadata JSON", pattern: /\bstorage-meta\.json\b/u },
{ label: "Matrix inbound dedupe JSON", pattern: /\binbound-dedupe\.json\b/u },
{ label: "Matrix startup verification JSON", pattern: /\bstartup-verification\.json\b/u },
{
label: "Discord model-picker preferences JSON",
pattern: /\bmodel-picker-preferences\.json\b/u,

View File

@@ -0,0 +1,74 @@
import { afterEach, describe, expect, it } from "vitest";
import { requireNodeSqlite } from "./node-sqlite.js";
import { runSqliteImmediateTransactionSync } from "./sqlite-transaction.js";
const openDatabases: Array<import("node:sqlite").DatabaseSync> = [];
function createDatabase(): import("node:sqlite").DatabaseSync {
const { DatabaseSync } = requireNodeSqlite();
const db = new DatabaseSync(":memory:");
db.exec("CREATE TABLE entries (id TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL);");
openDatabases.push(db);
return db;
}
function readEntries(db: import("node:sqlite").DatabaseSync): string[] {
return db
.prepare("SELECT id FROM entries ORDER BY id")
.all()
.map((row) => (row as { id: string }).id);
}
afterEach(() => {
for (const db of openDatabases.splice(0)) {
db.close();
}
});
describe("runSqliteImmediateTransactionSync", () => {
it("keeps outer writes when a nested savepoint rolls back", () => {
const db = createDatabase();
runSqliteImmediateTransactionSync(db, () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("outer", "kept");
expect(() =>
runSqliteImmediateTransactionSync(db, () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("inner", "rolled back");
throw new Error("nested failure");
}),
).toThrow("nested failure");
});
expect(readEntries(db)).toEqual(["outer"]);
});
it("commits nested savepoint writes with the outer transaction", () => {
const db = createDatabase();
runSqliteImmediateTransactionSync(db, () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("outer", "kept");
runSqliteImmediateTransactionSync(db, () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("inner", "kept");
});
});
expect(readEntries(db)).toEqual(["inner", "outer"]);
});
it("rejects Promise-returning operations and rolls back their synchronous writes", () => {
const db = createDatabase();
expect(() =>
runSqliteImmediateTransactionSync(db, async () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("async", "rolled back");
return "done";
}),
).toThrow("must be synchronous");
expect(readEntries(db)).toEqual([]);
runSqliteImmediateTransactionSync(db, () => {
db.prepare("INSERT INTO entries(id, value) VALUES (?, ?)").run("after", "works");
});
expect(readEntries(db)).toEqual(["after"]);
});
});