import path from "node:path"; import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { asc, eq } from "drizzle-orm"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { createDatabaseContext, type DatabaseContext, } from "../src/db/database-context.js"; import { CircuitSectionTransactionRepository } from "../src/db/repositories/circuit-section-transaction.repository.js"; import { DistributionBoardRepository } from "../src/db/repositories/distribution-board.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 { projects } from "../src/db/schema/projects.js"; function createTestDatabase(): DatabaseContext { 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(); context.db .insert(circuits) .values([ { id: "circuit-1", circuitListId: board.id, sectionId: section.id, equipmentIdentifier: "-1F1", displayName: "Stromkreis 1", sortOrder: 10, isReserve: 0, }, { id: "circuit-2", circuitListId: board.id, sectionId: section.id, equipmentIdentifier: "-1F2", displayName: "Stromkreis 2", sortOrder: 20, isReserve: 1, }, { id: "circuit-3", circuitListId: board.id, sectionId: section.id, equipmentIdentifier: "-1F3", displayName: "Stromkreis 3", sortOrder: 30, isReserve: 1, }, ]) .run(); context.db .insert(circuitDeviceRows) .values({ id: "row-1", circuitId: "circuit-1", sortOrder: 10, name: "Leuchte", displayName: "Leuchte", quantity: 1, powerPerUnit: 0.1, simultaneityFactor: 1, }) .run(); return context; } function listCircuits(context: DatabaseContext) { return context.db .select() .from(circuits) .orderBy(asc(circuits.id)) .all(); } describe("circuit-section transaction repository", () => { it("commits identifier swaps without violating the circuit-list uniqueness constraint", () => { const context = createTestDatabase(); try { const persisted = listCircuits(context); const repository = new CircuitSectionTransactionRepository(context.db); repository.updateEquipmentIdentifiers( persisted[0].circuitListId, [ { id: "circuit-1", equipmentIdentifier: "-1F2" }, { id: "circuit-2", equipmentIdentifier: "-1F1" }, ], persisted[0].sectionId ); assert.deepEqual( listCircuits(context).map((circuit) => circuit.equipmentIdentifier), ["-1F2", "-1F1", "-1F3"] ); assert.equal( context.db.select().from(circuitDeviceRows).all()[0].circuitId, "circuit-1" ); } finally { context.close(); } }); it("rolls back temporary and final identifiers when a later update fails", () => { const context = createTestDatabase(); try { const persisted = listCircuits(context); context.sqlite.exec(` CREATE TRIGGER fail_second_final_identifier BEFORE UPDATE OF equipment_identifier ON circuits WHEN OLD.id = 'circuit-2' AND NEW.equipment_identifier = '-1F1' BEGIN SELECT RAISE(ABORT, 'forced identifier failure'); END; `); const repository = new CircuitSectionTransactionRepository(context.db); assert.throws( () => repository.updateEquipmentIdentifiers( persisted[0].circuitListId, [ { id: "circuit-1", equipmentIdentifier: "-1F2" }, { id: "circuit-2", equipmentIdentifier: "-1F1" }, ], persisted[0].sectionId ), /forced identifier failure/ ); assert.deepEqual( listCircuits(context).map((circuit) => circuit.equipmentIdentifier), ["-1F1", "-1F2", "-1F3"] ); } finally { context.close(); } }); it("commits a complete section reorder without changing identifiers", () => { const context = createTestDatabase(); try { const persisted = listCircuits(context); const repository = new CircuitSectionTransactionRepository(context.db); repository.updateSortOrders(persisted[0].sectionId, [ "circuit-3", "circuit-1", "circuit-2", ]); assert.deepEqual( listCircuits(context).map((circuit) => [ circuit.id, circuit.sortOrder, circuit.equipmentIdentifier, ]), [ ["circuit-1", 20, "-1F1"], ["circuit-2", 30, "-1F2"], ["circuit-3", 10, "-1F3"], ] ); } finally { context.close(); } }); it("rolls back earlier sort orders when a later update fails", () => { const context = createTestDatabase(); try { const persisted = listCircuits(context); context.sqlite.exec(` CREATE TRIGGER fail_last_sort_order BEFORE UPDATE OF sort_order ON circuits WHEN OLD.id = 'circuit-2' AND NEW.sort_order = 30 BEGIN SELECT RAISE(ABORT, 'forced sort failure'); END; `); const repository = new CircuitSectionTransactionRepository(context.db); assert.throws( () => repository.updateSortOrders(persisted[0].sectionId, [ "circuit-3", "circuit-1", "circuit-2", ]), /forced sort failure/ ); assert.deepEqual( listCircuits(context).map((circuit) => circuit.sortOrder), [10, 20, 30] ); } finally { context.close(); } }); });