alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
109 lines (96 loc) • 3.49 kB
text/typescript
import { Alepha } from "alepha";
import { FileSystemProvider, MemoryFileSystemProvider } from "alepha/system";
import { describe, expect, it } from "vitest";
import { DbCommand } from "../commands/db.ts";
/**
* Exposes the destructive-migration guard so it can be driven without booting
* a real app + drizzle-kit.
*/
class TestDbCommand extends DbCommand {
public testAssertNoDestructiveMigrations =
this.assertNoDestructiveMigrations.bind(this);
}
describe("DbCommand", () => {
const create = () => {
const alepha = Alepha.create().with({
provide: FileSystemProvider,
use: MemoryFileSystemProvider,
});
return {
db: alepha.inject(TestDbCommand),
fs: alepha.inject(MemoryFileSystemProvider),
};
};
/**
* D1 ignores `PRAGMA foreign_keys=OFF`, so a generated migration that drops a
* CASCADE parent silently wipes every child row on deploy. Today the only
* defence is a human remembering to grep the migration before pushing.
*/
describe("assertNoDestructiveMigrations", () => {
it("rejects a newly generated migration containing DROP TABLE", async () => {
const { db, fs } = create();
await fs.writeFile(
"/app/migrations/sqlite/0042_drop_it.sql",
'ALTER TABLE "quests" ADD COLUMN "note" text;\nDROP TABLE "campaigns";',
);
await expect(
db.testAssertNoDestructiveMigrations("/app/migrations/sqlite", [
"0042_drop_it.sql",
]),
).rejects.toThrowError(/DROP TABLE/);
});
it("names the offending file so it can be reviewed", async () => {
const { db, fs } = create();
await fs.writeFile(
"/app/migrations/sqlite/0042_drop_it.sql",
'DROP TABLE IF EXISTS "campaigns";',
);
await expect(
db.testAssertNoDestructiveMigrations("/app/migrations/sqlite", [
"0042_drop_it.sql",
]),
).rejects.toThrowError(/0042_drop_it\.sql/);
});
it("accepts a migration with no table drops", async () => {
const { db, fs } = create();
await fs.writeFile(
"/app/migrations/sqlite/0043_add_col.sql",
'ALTER TABLE "quests" ADD COLUMN "note" text;',
);
await expect(
db.testAssertNoDestructiveMigrations("/app/migrations/sqlite", [
"0043_add_col.sql",
]),
).resolves.toBeUndefined();
});
it("ignores DROP TABLE inside a SQL comment", async () => {
const { db, fs } = create();
await fs.writeFile(
"/app/migrations/sqlite/0044_comment.sql",
'-- DROP TABLE "campaigns"; (kept for reference)\nALTER TABLE "quests" ADD COLUMN "note" text;',
);
await expect(
db.testAssertNoDestructiveMigrations("/app/migrations/sqlite", [
"0044_comment.sql",
]),
).resolves.toBeUndefined();
});
it("does not flag pre-existing migrations, only the newly generated ones", async () => {
const { db, fs } = create();
// An old, already-applied migration that legitimately dropped a table.
await fs.writeFile(
"/app/migrations/sqlite/0001_old.sql",
'DROP TABLE "legacy";',
);
await fs.writeFile(
"/app/migrations/sqlite/0045_new.sql",
'ALTER TABLE "quests" ADD COLUMN "note" text;',
);
await expect(
db.testAssertNoDestructiveMigrations("/app/migrations/sqlite", [
"0045_new.sql",
]),
).resolves.toBeUndefined();
});
});
});