mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
102 lines
3.9 KiB
TypeScript
102 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { applySqliteMigrations, type SqliteMigration, validateSqliteSchemaPrefix } from "../../src/sqlite/index.ts";
|
|
import { openNodeSqliteDatabase } from "../../src/sqlite/node.ts";
|
|
|
|
describe("SQLite migrations", () => {
|
|
it("tracks independent histories and skips migrations already applied", async () => {
|
|
const database = await openNodeSqliteDatabase(":memory:");
|
|
try {
|
|
const first: readonly SqliteMigration[] = [
|
|
{ version: 1, statements: ["CREATE TABLE notes (body TEXT)", "INSERT INTO notes VALUES ('kept')"] },
|
|
];
|
|
await applySqliteMigrations(database, first, { table: "notes_schema" });
|
|
await applySqliteMigrations(database, first, { table: "notes_schema" });
|
|
await applySqliteMigrations(database, [], { table: "other_schema" });
|
|
await applySqliteMigrations(
|
|
database,
|
|
[...first, { version: 2, statements: ["ALTER TABLE notes ADD COLUMN title TEXT"] }],
|
|
{ table: "notes_schema" },
|
|
);
|
|
expect(await database.all("SELECT body, title FROM notes")).toEqual([{ body: "kept", title: null }]);
|
|
expect(await database.get("SELECT version FROM notes_schema")).toEqual({ version: 2 });
|
|
expect(await database.get("SELECT version FROM other_schema")).toEqual({ version: 0 });
|
|
await expect(applySqliteMigrations(database, first, { table: "notes_schema" })).rejects.toThrow(
|
|
"SQLite schema version 2 is newer than supported version 1",
|
|
);
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
});
|
|
|
|
it("rejects invalid migration versions before touching the database", async () => {
|
|
const database = await openNodeSqliteDatabase(":memory:");
|
|
try {
|
|
for (const versions of [[0], [2], [1, 3], [1, 1]]) {
|
|
await expect(
|
|
applySqliteMigrations(
|
|
database,
|
|
versions.map((version) => ({ version, statements: [] })),
|
|
{ table: "app_schema" },
|
|
),
|
|
).rejects.toThrow("contiguous versions starting at 1");
|
|
}
|
|
expect(await database.all("SELECT name FROM sqlite_schema")).toEqual([]);
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
});
|
|
|
|
it("validates prefixes and migration table identifiers before executing SQL", async () => {
|
|
const database = await openNodeSqliteDatabase(":memory:");
|
|
try {
|
|
for (const prefix of ["", "1bad", "bad-prefix", "bad; DROP TABLE notes", "sqlite_app", "SQLITE_app"]) {
|
|
expect(() => validateSqliteSchemaPrefix(prefix)).toThrow();
|
|
await expect(applySqliteMigrations(database, [], { table: prefix })).rejects.toThrow();
|
|
}
|
|
for (const prefix of ["pi_", "_private", "app2"])
|
|
expect(() => validateSqliteSchemaPrefix(prefix)).not.toThrow();
|
|
expect(await database.all("SELECT name FROM sqlite_schema")).toEqual([]);
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
});
|
|
|
|
it("rolls initial bootstrap and every pending migration back together", async () => {
|
|
const database = await openNodeSqliteDatabase(":memory:");
|
|
try {
|
|
const failed: readonly SqliteMigration[] = [
|
|
{
|
|
version: 1,
|
|
statements: [
|
|
"CREATE TABLE migration_first (value TEXT) STRICT",
|
|
"INSERT INTO migration_first (value) VALUES ('retained')",
|
|
],
|
|
},
|
|
{
|
|
version: 2,
|
|
statements: ["CREATE TABLE migration_second (value TEXT) STRICT", "THIS IS NOT SQL"],
|
|
},
|
|
];
|
|
await expect(applySqliteMigrations(database, failed, { table: "app_schema" })).rejects.toThrow();
|
|
expect(
|
|
await database.get(
|
|
"SELECT count(*) AS count FROM sqlite_schema WHERE name IN ('app_schema', 'migration_first', 'migration_second')",
|
|
),
|
|
).toEqual({ count: 0 });
|
|
|
|
await applySqliteMigrations(
|
|
database,
|
|
[failed[0], { version: 2, statements: ["CREATE TABLE migration_second (value TEXT) STRICT"] }],
|
|
{ table: "app_schema" },
|
|
);
|
|
expect(await database.get("SELECT version FROM app_schema WHERE singleton = 1")).toEqual({
|
|
version: 2,
|
|
});
|
|
expect(await database.get("SELECT value FROM migration_first")).toEqual({
|
|
value: "retained",
|
|
});
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
});
|
|
});
|