import assert from "node:assert/strict"; import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { it } from "node:test"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { createDatabaseContext } from "../src/db/database-context.js"; it("upgrades a populated pre-print database through the current schema without changing existing records", async () => { const source = path.resolve("src/db/migrations"); const staging = mkdtempSync(path.join(tmpdir(), "leistungsbilanz-print-migration-")); const journal = JSON.parse(readFileSync(path.join(source, "meta/_journal.json"), "utf8")); journal.entries = journal.entries.filter((entry: { idx: number }) => entry.idx <= 6); mkdirSync(path.join(staging, "meta")); const files = [path.join(staging, "meta/_journal.json")]; writeFileSync(files[0], JSON.stringify(journal)); for (const entry of journal.entries) { const file = path.join(staging, `${entry.tag}.sql`); copyFileSync(path.join(source, `${entry.tag}.sql`), file); files.push(file); } const context = createDatabaseContext(":memory:"); try { migrate(context.db, { migrationsFolder: staging }); context.sqlite.exec(` INSERT INTO projects (id, name, current_revision) VALUES ('p', 'Existing project', 17); INSERT INTO distribution_boards (id, project_id, name, simultaneity_factor) VALUES ('b', 'p', 'Existing board', 0.7); INSERT INTO floors (id, project_id, name, sort_order) VALUES ('f', 'p', 'Existing floor', 3); INSERT INTO rooms (id, project_id, floor_id, room_number, room_name) VALUES ('r', 'p', 'f', '101', 'Existing room'); `); const oldProject = context.sqlite.prepare("SELECT * FROM projects").get() as Record; const oldBoard = context.sqlite.prepare("SELECT * FROM distribution_boards").get() as Record; const oldFloor = context.sqlite.prepare("SELECT * FROM floors").get() as Record; const oldRoom = context.sqlite.prepare("SELECT * FROM rooms").get() as Record; const backup = path.join(staging, "before-print.db"); files.push(backup); await context.sqlite.backup(backup); migrate(context.db, { migrationsFolder: source }); const project = context.sqlite.prepare("SELECT * FROM projects").get() as Record; const board = context.sqlite.prepare("SELECT * FROM distribution_boards").get() as Record; const floor = context.sqlite.prepare("SELECT * FROM floors").get() as Record; const room = context.sqlite.prepare("SELECT * FROM rooms").get() as Record; for (const [key, value] of Object.entries(oldProject)) assert.deepEqual(project[key], value); for (const [key, value] of Object.entries(oldBoard)) assert.deepEqual(board[key], value); for (const [key, value] of Object.entries(oldFloor)) assert.deepEqual(floor[key], value); for (const [key, value] of Object.entries(oldRoom)) assert.deepEqual(room[key], value); for (const field of ["site_address", "work_stage", "project_lead", "project_editor"]) assert.equal(project[field], null); for (const field of ["board_number", "location", "fed_from", "remark"]) assert.equal(board[field], null); assert.equal(board.feeding_distribution_board_id, null); assert.equal(board.feeding_circuit_id, null); assert.equal(floor.building_id, null); assert.equal(room.area_m2, null); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM buildings").get()!.count, 0); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM document_branding_assets").get()!.count, 0); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM document_branding_settings").get()!.count, 0); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM estimate_balances").get()!.count, 0); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM estimate_groups").get()!.count, 0); assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM estimate_rows").get()!.count, 0); assert.deepEqual(context.sqlite.pragma("foreign_key_check"), []); assert.equal(context.sqlite.pragma("integrity_check", { simple: true }), "ok"); migrate(context.db, { migrationsFolder: source }); assert.deepEqual(context.sqlite.prepare("SELECT * FROM projects").get(), project); } finally { context.close(); for (const file of files) unlinkSync(file); rmdirSync(path.join(staging, "meta")); rmdirSync(staging); } });