import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { describe, expect, it } from "vitest"; const projectRoot = resolve(import.meta.dirname, "../../../.."); const migrationPath = resolve( projectRoot, "drizzle/migrations/0023_housekeeping.sql", ); const schemaPath = resolve(projectRoot, "src/db/schema.ts"); const legacyAuditColumns = [ "action", "target", "target_id", "details", "before", "after", "diff", "ip_address", "created_at", "updated_at", ] as const; function migrationSource(): string { return readFileSync(migrationPath, "utf8"); } function schemaSource(): string { return readFileSync(schemaPath, "utf8"); } describe("housekeeping persistence contract", () => { it("adds only the approved nullable audit columns and correlation index", () => { const migration = migrationSource(); const auditAlter = migration.match( /ALTER TABLE `admin_audit_log`[\s\S]*?;/, )?.[0]; expect(auditAlter).toBeDefined(); expect(auditAlter).toContain( "ADD COLUMN `correlation_id` VARCHAR(64) NULL", ); expect(auditAlter).toContain("ADD COLUMN `outcome` VARCHAR(32) NULL"); expect(auditAlter).toContain("ADD COLUMN `reason` TEXT NULL"); expect(auditAlter).toContain("ADD COLUMN `domain` VARCHAR(32) NULL"); expect(auditAlter).toContain( "ADD INDEX `admin_audit_log_correlation_id_idx` (`correlation_id`)", ); expect(auditAlter).not.toMatch(/\b(?:DROP|RENAME|MODIFY|CHANGE)\b/i); for (const column of legacyAuditColumns) { expect(auditAlter).not.toMatch( new RegExp( `\\b(?:ADD|DROP|RENAME|MODIFY|CHANGE)\\s+COLUMN\\s+\\\`${column}\\\``, "i", ), ); } }); it("creates the exact user-scoped preference table with one primary key", () => { const migration = migrationSource(); const preferenceTable = migration.match( /CREATE TABLE `housekeeping_user_preferences` \([\s\S]*?\);/, )?.[0]; expect(preferenceTable).toBeDefined(); expect(preferenceTable).toContain("`user_id` INT NOT NULL"); expect(preferenceTable).toContain( "`schema_version` INT NOT NULL DEFAULT 1", ); expect(preferenceTable).toContain("`payload` LONGTEXT NOT NULL"); expect(preferenceTable).toContain( "`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP", ); expect(preferenceTable).toContain( "`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", ); expect(preferenceTable?.match(/PRIMARY KEY/g)).toHaveLength(1); expect(preferenceTable).toContain("PRIMARY KEY (`user_id`)"); }); it("declares matching additive Drizzle tables and the published preference row type", () => { const schema = schemaSource(); expect(schema).toContain("export const AdminAuditLog = mysqlTable("); expect(schema).toContain('"admin_audit_log"'); expect(schema).toContain( 'correlationId: varchar("correlation_id", { length: 64 })', ); expect(schema).toContain('outcome: varchar("outcome", { length: 32 })'); expect(schema).toContain('reason: text("reason")'); expect(schema).toContain('domain: varchar("domain", { length: 32 })'); expect(schema).toContain( 'index("admin_audit_log_correlation_id_idx").on(t.correlationId)', ); expect(schema).toContain( "export const HousekeepingUserPreferences = mysqlTable(", ); expect(schema).toContain('"housekeeping_user_preferences"'); expect(schema).toContain('userId: int("user_id").primaryKey().notNull()'); expect(schema).toContain( 'schemaVersion: int("schema_version").notNull().default(1)', ); expect(schema).toContain('payload: longtext("payload").notNull()'); expect(schema).toContain( 'createdAt: datetime("created_at").notNull().default(sql`CURRENT_TIMESTAMP`)', ); expect(schema).toContain( 'updatedAt: datetime("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`)', ); expect(schema).toContain("export type HousekeepingUserPreferenceRow ="); expect(schema).toContain( "typeof HousekeepingUserPreferences.$inferSelect;", ); }); it("contains no destructive DDL", () => { const migration = migrationSource(); expect(migration).not.toMatch(/\b(?:DROP|RENAME)\b/i); }); });