mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-11-12 13:26:57 +00:00
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import fs from "fs";
|
|||
|
|
import os from "os";
|
||
|
|
import path from "path";
|
||
|
|
import { getTableColumns, getTableName, is, Table } from "drizzle-orm";
|
||
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||
|
|
import * as schema from "../../../database/db/schema.js";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* One level up from bootstrap-matches-schema.test.ts, which checks that every
|
||
|
|
* TABLE exists. This checks every COLUMN.
|
||
|
|
*
|
||
|
|
* SQLite never runs the drizzle migrations at all -- runRemoteMigrations
|
||
|
|
* throws for sqlite, and the live schema comes from hand-written DDL in
|
||
|
|
* db/index.ts (a CREATE TABLE plus addColumnIfNotExists calls). So adding a
|
||
|
|
* column to schema.ts and running `schema:migrations` type-checks, produces a
|
||
|
|
* migration file, passes every repository test (their fixtures are built from
|
||
|
|
* the schema), and still fails at runtime on the default dialect with "no such
|
||
|
|
* column" -- breaking every write to that table.
|
||
|
|
*
|
||
|
|
* A grep- or token-based guard cannot catch this: db/index.ts refers to
|
||
|
|
* columns as snake_case string arguments, never by the camelCase names the
|
||
|
|
* schema uses. Only booting a real database and reading PRAGMA table_info
|
||
|
|
* compares the two things that actually have to agree.
|
||
|
|
*/
|
||
|
|
describe("bootstrap creates every column in the drizzle schema", () => {
|
||
|
|
let dataDir: string;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-columns-"));
|
||
|
|
vi.resetModules();
|
||
|
|
process.env.DATA_DIR = dataDir;
|
||
|
|
process.env.DB_FILE_ENCRYPTION = "false";
|
||
|
|
process.env.ALLOW_EMPTY_DATA_DIR = "true";
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
delete process.env.DATA_DIR;
|
||
|
|
delete process.env.DB_FILE_ENCRYPTION;
|
||
|
|
delete process.env.ALLOW_EMPTY_DATA_DIR;
|
||
|
|
fs.rmSync(dataDir, { recursive: true, force: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("leaves no schema column missing from a fresh database", async () => {
|
||
|
|
const db = await import("../../../database/db/index.js");
|
||
|
|
await db.initializeDatabase();
|
||
|
|
const sqlite = db.getSqlite();
|
||
|
|
|
||
|
|
const missing: string[] = [];
|
||
|
|
|
||
|
|
for (const value of Object.values(schema)) {
|
||
|
|
if (!is(value, Table)) continue;
|
||
|
|
const tableName = getTableName(value as Table);
|
||
|
|
|
||
|
|
const present = new Set(
|
||
|
|
(
|
||
|
|
sqlite
|
||
|
|
.prepare(`PRAGMA table_info(${JSON.stringify(tableName)})`)
|
||
|
|
.all() as Array<{ name: string }>
|
||
|
|
).map((row) => row.name),
|
||
|
|
);
|
||
|
|
// A table that does not exist at all is the sibling test's finding, not
|
||
|
|
// this one's -- reporting it here too would just duplicate the failure.
|
||
|
|
if (present.size === 0) continue;
|
||
|
|
|
||
|
|
for (const column of Object.values(getTableColumns(value as Table))) {
|
||
|
|
if (!present.has(column.name)) {
|
||
|
|
missing.push(`${tableName}.${column.name}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(missing.sort()).toEqual([]);
|
||
|
|
});
|
||
|
|
});
|