test(migrations): cover initial schema, idempotency, and legacy upgrade
Some checks failed
CI / check (push) Failing after 17s
Some checks failed
CI / check (push) Failing after 17s
This commit is contained in:
115
tests/migrations_test.ts
Normal file
115
tests/migrations_test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { Database } from "@db/sqlite";
|
||||
import { applyMigrations } from "../src/migrations/apply.ts";
|
||||
|
||||
type TableInfoRow = { name: string };
|
||||
type ColumnInfoRow = { name: string };
|
||||
|
||||
const tableNames = (db: Database): string[] =>
|
||||
db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
|
||||
).all<TableInfoRow>().map((r) => r.name);
|
||||
|
||||
const columnNames = (db: Database, table: string): string[] =>
|
||||
db.prepare(`PRAGMA table_info(${table})`)
|
||||
.all<ColumnInfoRow>()
|
||||
.map((r) => r.name)
|
||||
.sort();
|
||||
|
||||
const indexNames = (db: Database): string[] =>
|
||||
db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
||||
).all<TableInfoRow>().map((r) => r.name);
|
||||
|
||||
Deno.test("applyMigrations creates expected tables, columns, and indices", () => {
|
||||
const db = new Database(":memory:");
|
||||
try {
|
||||
applyMigrations(db);
|
||||
|
||||
const tables = tableNames(db);
|
||||
assertEquals(
|
||||
tables.includes("note") &&
|
||||
tables.includes("state") &&
|
||||
tables.includes("webhook_subscription"),
|
||||
true,
|
||||
);
|
||||
|
||||
const noteCols = columnNames(db, "note");
|
||||
for (
|
||||
const col of [
|
||||
"title",
|
||||
"publishedAt",
|
||||
"createdAt",
|
||||
"did",
|
||||
"rkey",
|
||||
"discoverable",
|
||||
"language",
|
||||
]
|
||||
) {
|
||||
assertEquals(noteCols.includes(col), true, `note.${col} missing`);
|
||||
}
|
||||
|
||||
const webhookCols = columnNames(db, "webhook_subscription");
|
||||
for (const col of ["id", "did", "method", "url", "token", "verb"]) {
|
||||
assertEquals(
|
||||
webhookCols.includes(col),
|
||||
true,
|
||||
`webhook_subscription.${col} missing`,
|
||||
);
|
||||
}
|
||||
|
||||
const indices = indexNames(db);
|
||||
assertEquals(indices.includes("idx_webhook_subscription_did"), true);
|
||||
assertEquals(indices.includes("idx_webhook_subscription_did_verb"), true);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("applyMigrations is idempotent", () => {
|
||||
const db = new Database(":memory:");
|
||||
try {
|
||||
applyMigrations(db);
|
||||
db.exec(
|
||||
"INSERT INTO webhook_subscription (did, method, url, verb) VALUES ('did:plc:x', 'POST', 'https://e.com', 'create')",
|
||||
);
|
||||
applyMigrations(db);
|
||||
applyMigrations(db);
|
||||
|
||||
const [{ count }] = db.prepare(
|
||||
"SELECT COUNT(*) AS count FROM webhook_subscription WHERE did = 'did:plc:x' AND verb = 'create'",
|
||||
).all<{ count: number }>();
|
||||
assertEquals(count, 1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"applyMigrations upgrades a legacy schema missing language and discoverable",
|
||||
() => {
|
||||
const db = new Database(":memory:");
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE TABLE note (
|
||||
title TEXT NOT NULL,
|
||||
publishedAt DATETIME,
|
||||
createdAt DATETIME NOT NULL,
|
||||
did TEXT NOT NULL,
|
||||
rkey TEXT NOT NULL,
|
||||
listed INTEGER NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (did, rkey)
|
||||
);
|
||||
`);
|
||||
|
||||
applyMigrations(db);
|
||||
|
||||
const cols = columnNames(db, "note");
|
||||
assertEquals(cols.includes("discoverable"), true);
|
||||
assertEquals(cols.includes("language"), true);
|
||||
assertEquals(cols.includes("listed"), false);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user