import { describe, expect, test } from "bun:test"; import { existsSync, readdirSync } from "node:fs"; import { join } from "node:path"; import type { MigrationRecord } from "../../../src/server/db/load-migrations"; import { runMigrations } from "../../../src/server/db/migrate"; import { createTestDatabase, openTestDatabase } from "../../helpers"; const MIGRATION_001: MigrationRecord = { checksum: "fake-checksum-001", id: "0001_initial", sql: ` CREATE TABLE test_table (id TEXT PRIMARY KEY, name TEXT NOT NULL); `, }; const MIGRATION_002: MigrationRecord = { checksum: "fake-checksum-002", id: "0002_add_desc", sql: ` ALTER TABLE test_table ADD COLUMN description TEXT DEFAULT ''; `, }; describe("migration 执行器", () => { test("应用待执行 migration 并记录", () => { const handle = createTestDatabase("migration-test"); try { const { db, dir, logger } = handle; runMigrations(db, [MIGRATION_001], dir, logger); const rows = db.query("SELECT id, checksum FROM schema_migrations").all() as Array<{ checksum: string; id: string; }>; expect(rows.length).toBe(1); expect(rows[0]!.id).toBe("0001_initial"); expect(rows[0]!.checksum).toBe("fake-checksum-001"); db.exec("INSERT INTO test_table (id, name) VALUES ('1', 'test')"); handle.close(); } finally { handle.cleanup(); } }); test("跳过已应用的 migration", () => { const handle = createTestDatabase("migration-test"); try { const { db, dir, logger } = handle; runMigrations(db, [MIGRATION_001], dir, logger); runMigrations(db, [MIGRATION_001], dir, logger); const rows = db.query("SELECT id FROM schema_migrations").all() as Array<{ id: string }>; expect(rows.length).toBe(1); handle.close(); } finally { handle.cleanup(); } }); test("按顺序应用多个 migration", () => { const handle = createTestDatabase("migration-test"); try { const { db, dir, logger } = handle; runMigrations(db, [MIGRATION_001, MIGRATION_002], dir, logger); const rows = db.query("SELECT id FROM schema_migrations ORDER BY id").all() as Array<{ id: string }>; expect(rows.length).toBe(2); expect(rows[0]!.id).toBe("0001_initial"); expect(rows[1]!.id).toBe("0002_add_desc"); db.exec("INSERT INTO test_table (id, name, description) VALUES ('1', 'test', 'desc')"); handle.close(); } finally { handle.cleanup(); } }); test("无待执行 migration 时不做变更", () => { const handle = createTestDatabase("migration-test"); try { const { db, dir, logger } = handle; runMigrations(db, [], dir, logger); const tableExists = db .query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'") .get(); expect(tableExists).toBeNull(); handle.close(); } finally { handle.cleanup(); } }); test("执行 migration 前创建备份", () => { const handle = createTestDatabase("migration-test"); try { const { db, dir } = handle; db.exec("CREATE TABLE existing (id TEXT)"); db.exec("INSERT INTO existing (id) VALUES ('x')"); handle.close(); const reopened = openTestDatabase(dir); runMigrations(reopened.db, [MIGRATION_001], dir, reopened.logger); reopened.close(); const backupsDir = join(dir, "backups"); expect(existsSync(backupsDir)).toBe(true); const backupFiles = readdirSync(backupsDir); expect(backupFiles.length).toBe(1); expect(backupFiles[0]!).toMatch(/^alfred-.*\.db$/); } finally { handle.cleanup(); } }); test("失败的 migration 不留下部分记录", () => { const handle = createTestDatabase("migration-test"); const BAD_MIGRATION: MigrationRecord = { checksum: "bad", id: "0003_bad", sql: "INVALID SQL STATEMENT;", }; try { const { db, dir, logger } = handle; expect(() => { runMigrations(db, [MIGRATION_001, BAD_MIGRATION], dir, logger); }).toThrow(); const rows = db.query("SELECT id FROM schema_migrations").all() as Array<{ id: string }>; expect(rows.length).toBe(0); handle.close(); } finally { handle.cleanup(); } }); });