chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { tryOpenSync } = await import("../../../src/lib/db/adapters/driverFactory.ts");
|
||||
|
||||
describe("betterSqliteAdapter", () => {
|
||||
test("abre DB in-memory e executa CRUD básico", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (!adapter || adapter.driver !== "better-sqlite3") {
|
||||
console.log("SKIP: better-sqlite3 não disponível");
|
||||
return;
|
||||
}
|
||||
|
||||
adapter.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)");
|
||||
const result = adapter.prepare("INSERT INTO test (val) VALUES (?)").run("hello");
|
||||
assert.equal(result.changes, 1);
|
||||
|
||||
const row = adapter
|
||||
.prepare("SELECT val FROM test WHERE id = ?")
|
||||
.get(result.lastInsertRowid) as {
|
||||
val: string;
|
||||
};
|
||||
assert.equal(row.val, "hello");
|
||||
|
||||
const rows = adapter.prepare("SELECT * FROM test").all();
|
||||
assert.equal(rows.length, 1);
|
||||
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("pragma retorna valor simples", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (!adapter || adapter.driver !== "better-sqlite3") return;
|
||||
|
||||
const mode = adapter.pragma("journal_mode", { simple: true });
|
||||
assert.ok(typeof mode === "string");
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("transaction envolve operações em bloco atômico", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (!adapter || adapter.driver !== "better-sqlite3") return;
|
||||
|
||||
adapter.exec("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val TEXT)");
|
||||
const insertFn = adapter.transaction(() => {
|
||||
adapter.prepare("INSERT INTO tx_test (val) VALUES (?)").run("a");
|
||||
adapter.prepare("INSERT INTO tx_test (val) VALUES (?)").run("b");
|
||||
});
|
||||
insertFn();
|
||||
|
||||
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM tx_test").get() as { cnt: number };
|
||||
assert.equal(count.cnt, 2);
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("driver é 'better-sqlite3'", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (!adapter || adapter.driver !== "better-sqlite3") return;
|
||||
assert.equal(adapter.driver, "better-sqlite3");
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("transaction com rollback em erro desfaz operações", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (!adapter || adapter.driver !== "better-sqlite3") return;
|
||||
|
||||
adapter.exec("CREATE TABLE rollback_test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)");
|
||||
const insertWithError = adapter.transaction(() => {
|
||||
adapter.prepare("INSERT INTO rollback_test (val) VALUES (?)").run("before-error");
|
||||
throw new Error("force rollback");
|
||||
});
|
||||
|
||||
assert.throws(() => insertWithError(), /force rollback/);
|
||||
|
||||
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM rollback_test").get() as {
|
||||
cnt: number;
|
||||
};
|
||||
assert.equal(count.cnt, 0, "Rollback deve ter desfeito o insert");
|
||||
adapter.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { tryOpenSync, openDatabaseAsync, preInitSqlJs, getSqlJsAdapter } =
|
||||
await import("../../../src/lib/db/adapters/driverFactory.ts");
|
||||
|
||||
describe("driverFactory", () => {
|
||||
test("tryOpenSync retorna adapter síncrono ou null", () => {
|
||||
const adapter = tryOpenSync(":memory:");
|
||||
if (adapter) {
|
||||
assert.ok(["better-sqlite3", "node:sqlite"].includes(adapter.driver));
|
||||
adapter.exec("CREATE TABLE t (v TEXT)");
|
||||
adapter.prepare("INSERT INTO t VALUES (?)").run("ok");
|
||||
const row = adapter.prepare("SELECT v FROM t").get() as { v: string };
|
||||
assert.equal(row.v, "ok");
|
||||
adapter.close();
|
||||
} else {
|
||||
assert.equal(adapter, null);
|
||||
}
|
||||
});
|
||||
|
||||
test("openDatabaseAsync sempre retorna um adapter válido", async () => {
|
||||
const adapter = await openDatabaseAsync(":memory:");
|
||||
assert.ok(["better-sqlite3", "node:sqlite", "sql.js"].includes(adapter.driver));
|
||||
|
||||
adapter.exec("CREATE TABLE t (v TEXT)");
|
||||
adapter.prepare("INSERT INTO t VALUES (?)").run("ok");
|
||||
const row = adapter.prepare("SELECT v FROM t").get() as { v: string };
|
||||
assert.equal(row.v, "ok");
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("preInitSqlJs cacheia o adapter por filePath", async () => {
|
||||
const path = `sqljs_cache_test_${Date.now()}`;
|
||||
const adapter1 = await preInitSqlJs(path);
|
||||
const adapter2 = await preInitSqlJs(path);
|
||||
assert.equal(adapter1, adapter2, "Deve retornar o mesmo adapter cacheado");
|
||||
adapter1.close();
|
||||
});
|
||||
|
||||
test("getSqlJsAdapter retorna null para path não inicializado", () => {
|
||||
const unique = `not_initialized_${Date.now()}`;
|
||||
assert.equal(getSqlJsAdapter(unique), null);
|
||||
});
|
||||
|
||||
test("getSqlJsAdapter retorna adapter após preInitSqlJs", async () => {
|
||||
const path = `sqljs_get_test_${Date.now()}`;
|
||||
await preInitSqlJs(path);
|
||||
const adapter = getSqlJsAdapter(path);
|
||||
assert.ok(adapter !== null);
|
||||
assert.equal(adapter!.driver, "sql.js");
|
||||
adapter!.close();
|
||||
});
|
||||
|
||||
test("openDatabaseAsync suporta operações CRUD completas", async (t) => {
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
const fs = await import("node:fs");
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), `driver_crud_${Date.now()}.sqlite`);
|
||||
t.after(() => {
|
||||
try {
|
||||
fs.unlinkSync(tmpFile);
|
||||
} catch {}
|
||||
});
|
||||
|
||||
const adapter = await openDatabaseAsync(tmpFile);
|
||||
|
||||
adapter.exec("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, qty INTEGER)");
|
||||
|
||||
const r1 = adapter.prepare("INSERT INTO items (name, qty) VALUES (?, ?)").run("apple", 5);
|
||||
const r2 = adapter.prepare("INSERT INTO items (name, qty) VALUES (?, ?)").run("banana", 3);
|
||||
assert.equal(r1.changes, 1);
|
||||
assert.equal(r2.changes, 1);
|
||||
|
||||
const rows = adapter.prepare("SELECT * FROM items ORDER BY id").all() as Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
qty: number;
|
||||
}>;
|
||||
assert.equal(rows.length, 2);
|
||||
assert.equal(rows[0].name, "apple");
|
||||
assert.equal(rows[1].name, "banana");
|
||||
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("cross-driver: escreve com adapter sync, relê com sql.js", async (t) => {
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
const fs = await import("node:fs");
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), `cross_driver_${Date.now()}.sqlite`);
|
||||
t.after(() => {
|
||||
try {
|
||||
fs.unlinkSync(tmpFile);
|
||||
} catch {}
|
||||
});
|
||||
|
||||
const syncAdapter = tryOpenSync(tmpFile);
|
||||
if (!syncAdapter) {
|
||||
console.log("SKIP: nenhum driver síncrono disponível para cross-driver test");
|
||||
return;
|
||||
}
|
||||
syncAdapter.exec("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)");
|
||||
syncAdapter.prepare("INSERT INTO items (name) VALUES (?)").run("cross-test");
|
||||
syncAdapter.close();
|
||||
|
||||
const { createSqlJsAdapter } = await import("../../../src/lib/db/adapters/sqljsAdapter.ts");
|
||||
const reader = await createSqlJsAdapter(tmpFile);
|
||||
const row = reader.prepare("SELECT name FROM items WHERE id = 1").get() as { name: string };
|
||||
assert.equal(row.name, "cross-test");
|
||||
reader.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createNodeSqliteAdapterFromDatabase } from "../../../src/lib/db/adapters/nodeSqliteShared.ts";
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
class FakeStatement {
|
||||
finalized = false;
|
||||
|
||||
constructor(
|
||||
private readonly sql: string,
|
||||
private readonly rows: Row[] = []
|
||||
) {}
|
||||
|
||||
run(..._params: unknown[]) {
|
||||
return { changes: 1n, lastInsertRowid: 7n };
|
||||
}
|
||||
|
||||
get(..._params: unknown[]) {
|
||||
if (this.sql.startsWith("PRAGMA")) {
|
||||
return { journal_mode: "wal" };
|
||||
}
|
||||
return this.rows[0];
|
||||
}
|
||||
|
||||
all(..._params: unknown[]) {
|
||||
if (this.sql.startsWith("PRAGMA")) {
|
||||
return [{ journal_mode: "wal" }];
|
||||
}
|
||||
return this.rows;
|
||||
}
|
||||
|
||||
finalize() {
|
||||
this.finalized = true;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDb {
|
||||
closed = false;
|
||||
execCalls: string[] = [];
|
||||
statements: FakeStatement[] = [];
|
||||
|
||||
prepare(sql: string) {
|
||||
const statement = new FakeStatement(sql, [{ value: "ok" }]);
|
||||
this.statements.push(statement);
|
||||
return statement;
|
||||
}
|
||||
|
||||
exec(sql: string) {
|
||||
this.execCalls.push(sql);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
test("createNodeSqliteAdapterFromDatabase wraps node:sqlite statements", () => {
|
||||
const db = new FakeDb();
|
||||
const adapter = createNodeSqliteAdapterFromDatabase(db, ":memory:");
|
||||
|
||||
const insert = adapter.prepare("INSERT INTO t VALUES (?)").run("ok");
|
||||
assert.deepEqual(insert, { changes: 1, lastInsertRowid: 7 });
|
||||
|
||||
const row = adapter.prepare("SELECT value FROM t").get() as Row;
|
||||
assert.equal(row.value, "ok");
|
||||
|
||||
assert.equal(adapter.pragma("journal_mode", { simple: true }), "wal");
|
||||
assert.deepEqual(adapter.pragma("journal_mode"), [{ journal_mode: "wal" }]);
|
||||
});
|
||||
|
||||
test("createNodeSqliteAdapterFromDatabase uses savepoints for transactions", () => {
|
||||
const db = new FakeDb();
|
||||
const adapter = createNodeSqliteAdapterFromDatabase(db, ":memory:");
|
||||
|
||||
const run = adapter.transaction((value: string) => value.toUpperCase());
|
||||
assert.equal(run("ok"), "OK");
|
||||
assert.equal(db.execCalls[0].startsWith("SAVEPOINT "), true);
|
||||
assert.equal(db.execCalls[1].startsWith("RELEASE "), true);
|
||||
});
|
||||
|
||||
test("createNodeSqliteAdapterFromDatabase finalizes cached statements on close", () => {
|
||||
const db = new FakeDb();
|
||||
let closedHookCalls = 0;
|
||||
const adapter = createNodeSqliteAdapterFromDatabase(db, ":memory:", () => {
|
||||
closedHookCalls++;
|
||||
});
|
||||
|
||||
adapter.prepare("SELECT 1").get();
|
||||
adapter.close();
|
||||
|
||||
assert.equal(closedHookCalls, 1);
|
||||
assert.equal(db.closed, true);
|
||||
assert.equal(adapter.open, false);
|
||||
assert.equal(
|
||||
db.statements.every((statement) => statement.finalized),
|
||||
true
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { createSqlJsAdapter } = await import("../../../src/lib/db/adapters/sqljsAdapter.ts");
|
||||
|
||||
describe("sqljsAdapter", () => {
|
||||
test("abre DB in-memory e executa CRUD básico", async () => {
|
||||
const adapter = await createSqlJsAdapter(":memory:");
|
||||
|
||||
adapter.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)");
|
||||
const result = adapter.prepare("INSERT INTO test (val) VALUES (?)").run("hello");
|
||||
assert.equal(result.changes, 1);
|
||||
|
||||
const row = adapter
|
||||
.prepare("SELECT val FROM test WHERE id = ?")
|
||||
.get(result.lastInsertRowid) as { val: string };
|
||||
assert.equal(row.val, "hello");
|
||||
|
||||
const rows = adapter.prepare("SELECT * FROM test").all();
|
||||
assert.equal(rows.length, 1);
|
||||
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("driver é 'sql.js'", async () => {
|
||||
const adapter = await createSqlJsAdapter(":memory:");
|
||||
assert.equal(adapter.driver, "sql.js");
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("pragma retorna valor", async () => {
|
||||
const adapter = await createSqlJsAdapter(":memory:");
|
||||
const mode = adapter.pragma("journal_mode", { simple: true });
|
||||
assert.ok(mode !== null && mode !== undefined);
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("transaction é atômica — rollback em erro", async () => {
|
||||
const adapter = await createSqlJsAdapter(":memory:");
|
||||
adapter.exec("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)");
|
||||
|
||||
const insert = adapter.transaction(() => {
|
||||
adapter.prepare("INSERT INTO tx_test (val) VALUES (?)").run("ok");
|
||||
throw new Error("rollback!");
|
||||
});
|
||||
|
||||
assert.throws(() => insert(), /rollback/);
|
||||
|
||||
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM tx_test").get() as { cnt: number };
|
||||
assert.equal(count.cnt, 0, "Rollback deve ter desfeito o insert");
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("transaction confirma quando não lança erro", async () => {
|
||||
const adapter = await createSqlJsAdapter(":memory:");
|
||||
adapter.exec("CREATE TABLE commit_test (id INTEGER PRIMARY KEY, val TEXT)");
|
||||
|
||||
const insert = adapter.transaction(() => {
|
||||
adapter.prepare("INSERT INTO commit_test (val) VALUES (?)").run("committed");
|
||||
});
|
||||
insert();
|
||||
|
||||
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM commit_test").get() as {
|
||||
cnt: number;
|
||||
};
|
||||
assert.equal(count.cnt, 1);
|
||||
adapter.close();
|
||||
});
|
||||
|
||||
test("escreve em arquivo e relê corretamente", async (t) => {
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
const fs = await import("node:fs");
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), `sqljs_test_${Date.now()}.sqlite`);
|
||||
t.after(() => {
|
||||
try {
|
||||
fs.unlinkSync(tmpFile);
|
||||
} catch {}
|
||||
});
|
||||
|
||||
const writer = await createSqlJsAdapter(tmpFile);
|
||||
writer.exec("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)");
|
||||
writer.prepare("INSERT INTO items (name) VALUES (?)").run("test-value");
|
||||
writer.close();
|
||||
|
||||
const reader = await createSqlJsAdapter(tmpFile);
|
||||
const row = reader.prepare("SELECT name FROM items WHERE id = 1").get() as { name: string };
|
||||
assert.equal(row.name, "test-value");
|
||||
reader.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user