123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
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);
|
|
});
|
|
});
|