forked from jappel/leistungsbilanz-ts
115 lines
6 KiB
TypeScript
115 lines
6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { it } from "node:test";
|
|
import { spawnSync } from "node:child_process";
|
|
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
import { createDatabaseContext } from "../src/db/database-context.js";
|
|
|
|
it("replaces legacy CSV state and resets linked row quantities to their manual share", () => {
|
|
const source = path.resolve("src", "db", "migrations");
|
|
const staging = mkdtempSync(path.join(tmpdir(), "leistungsbilanz-linear-xlsx-migration-"));
|
|
const journal = JSON.parse(readFileSync(path.join(source, "meta", "_journal.json"), "utf8"));
|
|
journal.entries = journal.entries.filter((entry: { idx: number }) => entry.idx <= 13);
|
|
mkdirSync(path.join(staging, "meta"));
|
|
writeFileSync(path.join(staging, "meta", "_journal.json"), JSON.stringify(journal));
|
|
for (const entry of journal.entries) {
|
|
copyFileSync(path.join(source, `${entry.tag}.sql`), path.join(staging, `${entry.tag}.sql`));
|
|
}
|
|
|
|
const context = createDatabaseContext(":memory:");
|
|
try {
|
|
migrate(context.db, { migrationsFolder: staging });
|
|
context.sqlite.exec(`
|
|
INSERT INTO projects (id, name) VALUES ('p', 'Projekt');
|
|
INSERT INTO distribution_boards (id, project_id, name) VALUES ('b', 'p', 'UV');
|
|
INSERT INTO circuit_lists (id, project_id, distribution_board_id, name) VALUES ('l', 'p', 'b', 'Liste');
|
|
INSERT INTO circuit_sections (id, circuit_list_id, key, display_name, prefix) VALUES ('s', 'l', 'single_phase', 'Einphasig', '-2F1.');
|
|
INSERT INTO circuits (id, circuit_list_id, section_id, equipment_identifier) VALUES ('c', 'l', 's', '-2F1.1');
|
|
INSERT INTO circuit_device_rows
|
|
(id, circuit_id, name, display_name, quantity, manual_quantity, power_per_unit, simultaneity_factor)
|
|
VALUES ('r', 'c', 'Steckdose', 'Steckdose', 5, 2, 0.2, 1);
|
|
INSERT INTO external_csv_configurations
|
|
(id, project_id, configuration_version, configuration)
|
|
VALUES ('cfg', 'p', 3, '{}');
|
|
INSERT INTO external_model_sources (id, project_id, name, source_type)
|
|
VALUES ('src', 'p', 'Revit', 'revit_csv');
|
|
INSERT INTO external_import_batches
|
|
(id, project_id, source_id, import_kind, imported_at_iso, file_name, sha256,
|
|
applied_project_revision, configuration_snapshot, original_bytes, document, configuration_version)
|
|
VALUES ('batch', 'p', 'src', 'initial', '2026-01-01T00:00:00.000Z', 'legacy.csv',
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 0, '{}', X'00', '{}', 3);
|
|
INSERT INTO external_model_objects
|
|
(id, project_id, source_id, ifc_guid, last_seen_import_batch_id,
|
|
last_accepted_import_batch_id, accepted_source_values, planning_values,
|
|
overridden_fields, circuit_device_row_id)
|
|
VALUES ('object', 'p', 'src', 'Ifc-GUID', 'batch', 'batch', '{}', '{}', '[]', 'r');
|
|
`);
|
|
|
|
migrate(context.db, { migrationsFolder: source });
|
|
|
|
assert.equal(
|
|
(context.sqlite.prepare("SELECT quantity FROM circuit_device_rows WHERE id = 'r'").get() as { quantity: number }).quantity,
|
|
2
|
|
);
|
|
assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM external_model_objects").get()!.count, 0);
|
|
assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM external_model_configurations").get()!.count, 0);
|
|
assert.throws(() => context.sqlite.prepare("SELECT * FROM external_csv_configurations").all(), /no such table/);
|
|
|
|
const columns = context.sqlite.prepare("PRAGMA table_info(external_model_objects)").all() as Array<{ name: string; notnull: number }>;
|
|
assert.equal(columns.find((column) => column.name === "worksharing_id")?.notnull, 1);
|
|
assert.equal(columns.find((column) => column.name === "ifc_guid")?.notnull, 0);
|
|
assert.deepEqual(context.sqlite.pragma("foreign_key_check"), []);
|
|
assert.equal(context.sqlite.pragma("integrity_check", { simple: true }), "ok");
|
|
} finally {
|
|
context.close();
|
|
rmSync(staging, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("creates and verifies a backup before the Linear-XLSX migration", () => {
|
|
const source = path.resolve("src", "db", "migrations");
|
|
const root = mkdtempSync(path.join(tmpdir(), "leistungsbilanz-linear-xlsx-backup-"));
|
|
const staging = path.join(root, "migrations");
|
|
const dataDirectory = path.join(root, "data");
|
|
mkdirSync(path.join(staging, "meta"), { recursive: true });
|
|
mkdirSync(dataDirectory, { recursive: true });
|
|
const journal = JSON.parse(readFileSync(path.join(source, "meta", "_journal.json"), "utf8"));
|
|
journal.entries = journal.entries.filter((entry: { idx: number }) => entry.idx <= 13);
|
|
writeFileSync(path.join(staging, "meta", "_journal.json"), JSON.stringify(journal));
|
|
for (const entry of journal.entries) {
|
|
copyFileSync(path.join(source, `${entry.tag}.sql`), path.join(staging, `${entry.tag}.sql`));
|
|
}
|
|
|
|
const databasePath = path.join(dataDirectory, "leistungsbilanz.db");
|
|
const context = createDatabaseContext(databasePath);
|
|
try {
|
|
migrate(context.db, { migrationsFolder: staging });
|
|
} finally {
|
|
context.close();
|
|
}
|
|
|
|
try {
|
|
const result = spawnSync(process.execPath, ["scripts/run-migrations.js"], {
|
|
cwd: path.resolve("."),
|
|
env: { ...process.env, LEISTUNGSBILANZ_DATA_DIR: dataDirectory },
|
|
encoding: "utf8",
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.match(result.stdout, /Verified pre-migration backup created/);
|
|
const backups = readdirSync(path.join(dataDirectory, "backups"));
|
|
assert.equal(backups.length, 1);
|
|
|
|
const migrated = createDatabaseContext(databasePath);
|
|
try {
|
|
assert.ok(migrated.sqlite.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'external_model_configurations'"
|
|
).get());
|
|
} finally {
|
|
migrated.close();
|
|
}
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|