import path from "node:path"; import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { eq } from "drizzle-orm"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { createDatabaseContext, type DatabaseContext, } from "../src/db/database-context.js"; import { DistributionBoardRepository } from "../src/db/repositories/distribution-board.repository.js"; import { LegacyConsumerMigrationRepository } from "../src/db/repositories/legacy-consumer-migration.repository.js"; import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js"; import { circuitSections } from "../src/db/schema/circuit-sections.js"; import { circuits } from "../src/db/schema/circuits.js"; import { consumers } from "../src/db/schema/consumers.js"; import { legacyConsumerCircuitMigrations } from "../src/db/schema/legacy-consumer-circuit-migrations.js"; import { legacyConsumerMigrationReports } from "../src/db/schema/legacy-consumer-migration-report.js"; import { projects } from "../src/db/schema/projects.js"; function createTestDatabase() { const context = createDatabaseContext(":memory:"); migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations"), }); context.db.insert(projects).values({ id: "project-1", name: "Test project" }).run(); const board = new DistributionBoardRepository( context.db ).createWithCircuitListAndDefaultSections("project-1", "UV-01"); const [section] = context.db .select() .from(circuitSections) .where(eq(circuitSections.circuitListId, board.id)) .limit(1) .all(); return { context, circuitListId: board.id, sectionId: section.id }; } function migrationInput(circuitListId: string, sectionId: string) { return { circuitListId, circuits: [ { circuit: { circuitListId, sectionId, equipmentIdentifier: "-1F1", displayName: "Legacy lighting", sortOrder: 10, }, deviceRows: [ { legacyConsumerId: "consumer-1", sortOrder: 10, name: "Light 1", displayName: "Light 1", quantity: 1, powerPerUnit: 0.1, simultaneityFactor: 1, }, { legacyConsumerId: "consumer-2", sortOrder: 20, name: "Light 2", displayName: "Light 2", quantity: 2, powerPerUnit: 0.2, simultaneityFactor: 0.8, }, ], }, ], report: { legacyConsumerCount: 2, createdCircuitCount: 1, createdDeviceRowCount: 2, duplicateGroupedCount: 1, generatedIdentifierCount: 0, unassignedRowCount: 0, warningsJson: "[]", generatedIdentifiersJson: "[]", duplicateGroupsJson: "[{\"normalizedCircuitNumber\":\"-1F1\",\"count\":2}]", }, }; } function assertNoMigrationWrites(context: DatabaseContext) { assert.equal(context.db.select().from(circuits).all().length, 0); assert.equal(context.db.select().from(circuitDeviceRows).all().length, 0); assert.equal( context.db.select().from(legacyConsumerCircuitMigrations).all().length, 0 ); assert.equal( context.db.select().from(legacyConsumerMigrationReports).all().length, 0 ); } describe("legacy consumer migration repository", () => { it("reports every consumer without a completed migration mapping", async () => { const { context, circuitListId, sectionId } = createTestDatabase(); try { context.db .insert(consumers) .values([ { id: "consumer-1", projectId: "project-1", circuitListId, name: "Light 1", quantity: 1, installedPowerPerUnitKw: 0.1, demandFactor: 1, }, { id: "consumer-2", projectId: "project-1", circuitListId: null, name: "Light 2", quantity: 1, installedPowerPerUnitKw: 0.2, demandFactor: 1, }, ]) .run(); const repository = new LegacyConsumerMigrationRepository(context.db); assert.deepEqual( (await repository.listSourceConsumersByCircuitList(circuitListId)).map( (consumer) => consumer.id ), ["consumer-1"] ); assert.deepEqual( (await repository.listUnmigratedConsumers()).sort((left, right) => left.id.localeCompare(right.id) ), [ { id: "consumer-1", projectId: "project-1", circuitListId, }, { id: "consumer-2", projectId: "project-1", circuitListId: null, }, ] ); repository.persistCircuitListMigration( migrationInput(circuitListId, sectionId) ); assert.deepEqual(await repository.listUnmigratedConsumers(), []); } finally { context.close(); } }); it("commits circuits, rows, mappings and report together", () => { const { context, circuitListId, sectionId } = createTestDatabase(); try { const repository = new LegacyConsumerMigrationRepository(context.db); repository.persistCircuitListMigration( migrationInput(circuitListId, sectionId) ); const persistedCircuits = context.db.select().from(circuits).all(); const persistedRows = context.db .select() .from(circuitDeviceRows) .all() .sort((left, right) => left.sortOrder - right.sortOrder); const persistedMappings = context.db .select() .from(legacyConsumerCircuitMigrations) .all() .sort((left, right) => left.consumerId.localeCompare(right.consumerId)); const persistedReports = context.db .select() .from(legacyConsumerMigrationReports) .all(); assert.equal(persistedCircuits.length, 1); assert.equal(persistedRows.length, 2); assert.equal(persistedMappings.length, 2); assert.equal(persistedReports.length, 1); assert.deepEqual( persistedMappings.map((mapping) => [ mapping.consumerId, mapping.circuitId, mapping.circuitDeviceRowId, mapping.circuitListId, ]), [ [ "consumer-1", persistedCircuits[0].id, persistedRows[0].id, circuitListId, ], [ "consumer-2", persistedCircuits[0].id, persistedRows[1].id, circuitListId, ], ] ); assert.equal(persistedReports[0].createdCircuitCount, 1); assert.equal(persistedReports[0].createdDeviceRowCount, 2); } finally { context.close(); } }); it("rolls back all migrated entities when the final report write fails", () => { const { context, circuitListId, sectionId } = createTestDatabase(); try { context.sqlite.exec(` CREATE TRIGGER fail_migration_report BEFORE INSERT ON legacy_consumer_migration_reports BEGIN SELECT RAISE(ABORT, 'forced migration report failure'); END; `); const repository = new LegacyConsumerMigrationRepository(context.db); assert.throws( () => repository.persistCircuitListMigration( migrationInput(circuitListId, sectionId) ), /forced migration report failure/ ); assertNoMigrationWrites(context); } finally { context.close(); } }); });