leistungsbilanz-ts/tests/print-view-migration.test.ts
Julian Appel 7222398628 Typecheck tests against current types
tsx runs tests without type checking and tsconfig.json covers only src,
so 71 type errors had accumulated unnoticed. tsconfig.tests.json now
checks every test and helper, and npm run typecheck includes it.

Fixtures gain the fields the DTOs have grown since they were written
(manualQuantity, circuit apparent power/cos phi, downstream board loads,
section totals, building, cover and board info fields). Repository
lookups that return "row ?? null" now declare the null in their type,
which their callers already handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:17:45 +01:00

73 lines
4.7 KiB
TypeScript

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<string, unknown>;
const oldBoard = context.sqlite.prepare("SELECT * FROM distribution_boards").get() as Record<string, unknown>;
const oldFloor = context.sqlite.prepare("SELECT * FROM floors").get() as Record<string, unknown>;
const oldRoom = context.sqlite.prepare("SELECT * FROM rooms").get() as Record<string, unknown>;
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<string, unknown>;
const board = context.sqlite.prepare("SELECT * FROM distribution_boards").get() as Record<string, unknown>;
const floor = context.sqlite.prepare("SELECT * FROM floors").get() as Record<string, unknown>;
const room = context.sqlite.prepare("SELECT * FROM rooms").get() as Record<string, unknown>;
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<[], { count: number }>("SELECT count(*) AS count FROM buildings").get()!.count, 0);
assert.equal(context.sqlite.prepare<[], { count: number }>("SELECT count(*) AS count FROM document_branding_assets").get()!.count, 0);
assert.equal(context.sqlite.prepare<[], { count: number }>("SELECT count(*) AS count FROM document_branding_settings").get()!.count, 0);
assert.equal(context.sqlite.prepare<[], { count: number }>("SELECT count(*) AS count FROM estimate_balances").get()!.count, 0);
assert.equal(context.sqlite.prepare<[], { count: number }>("SELECT count(*) AS count FROM estimate_groups").get()!.count, 0);
assert.equal(context.sqlite.prepare<[], { count: number }>("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);
}
});