diff --git a/tests/migrations_test.ts b/tests/migrations_test.ts new file mode 100644 index 0000000..e624cbe --- /dev/null +++ b/tests/migrations_test.ts @@ -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().map((r) => r.name); + +const columnNames = (db: Database, table: string): string[] => + db.prepare(`PRAGMA table_info(${table})`) + .all() + .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().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(); + } + }, +);