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 { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js"; import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js"; import { circuits } from "../src/db/schema/circuits.js"; import { distributionBoards } from "../src/db/schema/distribution-boards.js"; import { projects } from "../src/db/schema/projects.js"; import { assertDistributionBoardFeederCircuitProjectCommand, createDistributionBoardCreateFeederCircuitProjectCommand, } from "../src/domain/models/distribution-board-feeder-circuit-project-command.model.js"; import { createDistributionBoardUpdateProjectCommand } from "../src/domain/models/distribution-board-project-command.model.js"; import { createDistributionBoardInsertProjectCommand, createDistributionBoardStructureSnapshot, } from "../src/domain/models/distribution-board-structure-project-command.model.js"; import { buildCircuitInsertSnapshot } from "../src/frontend/utils/circuit-structure-command.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(); return context; } function setupBoards( context: DatabaseContext, repository: DistributionBoardStructureProjectCommandRepository ) { const source = createDistributionBoardStructureSnapshot( "project-1", "NSHV" ); const downstream = createDistributionBoardStructureSnapshot( "project-1", "UV EG" ); repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: createDistributionBoardInsertProjectCommand(source), }); repository.execute({ projectId: "project-1", expectedRevision: 1, source: "user", command: createDistributionBoardInsertProjectCommand(downstream), }); const section = source.sections.find( (candidate) => candidate.key === "three_phase" )!; const circuit = { ...buildCircuitInsertSnapshot({ id: "feeder-circuit", circuitListId: source.circuitList.id, category: "three_phase", deviceRows: [], values: { sectionId: section.id, equipmentIdentifier: `${section.prefix}1`, displayName: undefined, sortOrder: 10, voltage: 400, isReserve: true, }, }), protectionDevice: null, }; const boardUpdate = createDistributionBoardUpdateProjectCommand( downstream.distributionBoard.id, { feedingDistributionBoardId: source.distributionBoard.id, feedingCircuitId: circuit.id, } ); const command = createDistributionBoardCreateFeederCircuitProjectCommand( circuit, boardUpdate ); return { source, downstream, circuit, command }; } describe("distribution-board feeder-circuit project command", () => { it("creates and links one feeder atomically and supports undo and redo", () => { const context = createTestDatabase(); try { const repository = new DistributionBoardStructureProjectCommandRepository(context.db); const history = new ProjectHistoryRepository(context.db); const { source, downstream, circuit, command } = setupBoards( context, repository ); repository.executeFeederCircuit({ projectId: "project-1", expectedRevision: 2, source: "user", command, }); assert.ok( context.db .select() .from(circuits) .where(eq(circuits.id, circuit.id)) .get() ); assert.deepEqual( context.db .select({ boardId: distributionBoards.feedingDistributionBoardId, circuitId: distributionBoards.feedingCircuitId, }) .from(distributionBoards) .where(eq(distributionBoards.id, downstream.distributionBoard.id)) .get(), { boardId: source.distributionBoard.id, circuitId: circuit.id, } ); const undo = history.getNextCommand("project-1", "undo"); assert.ok(undo); assertDistributionBoardFeederCircuitProjectCommand(undo.command); repository.executeFeederCircuit({ projectId: "project-1", expectedRevision: 3, source: "undo", historyTargetChangeSetId: undo.changeSetId, command: undo.command, }); assert.equal( context.db .select() .from(circuits) .where(eq(circuits.id, circuit.id)) .get(), undefined ); assert.deepEqual( context.db .select({ boardId: distributionBoards.feedingDistributionBoardId, circuitId: distributionBoards.feedingCircuitId, }) .from(distributionBoards) .where(eq(distributionBoards.id, downstream.distributionBoard.id)) .get(), { boardId: null, circuitId: null } ); const redo = history.getNextCommand("project-1", "redo"); assert.ok(redo); assertDistributionBoardFeederCircuitProjectCommand(redo.command); repository.executeFeederCircuit({ projectId: "project-1", expectedRevision: 4, source: "redo", historyTargetChangeSetId: redo.changeSetId, command: redo.command, }); assert.equal( context.db .select({ equipmentIdentifier: circuits.equipmentIdentifier }) .from(circuits) .where(eq(circuits.id, circuit.id)) .get()?.equipmentIdentifier, circuit.equipmentIdentifier ); } finally { context.close(); } }); it("rolls back unlinking when the generated circuit changed before undo", () => { const context = createTestDatabase(); try { const repository = new DistributionBoardStructureProjectCommandRepository(context.db); const history = new ProjectHistoryRepository(context.db); const { downstream, circuit, command } = setupBoards(context, repository); repository.executeFeederCircuit({ projectId: "project-1", expectedRevision: 2, source: "user", command, }); context.db .update(circuits) .set({ displayName: "Außerhalb der Historie geändert" }) .where(eq(circuits.id, circuit.id)) .run(); const undo = history.getNextCommand("project-1", "undo"); assert.ok(undo); assertDistributionBoardFeederCircuitProjectCommand(undo.command); assert.throws( () => repository.executeFeederCircuit({ projectId: "project-1", expectedRevision: 3, source: "undo", historyTargetChangeSetId: undo.changeSetId, command: undo.command, }), /changed before history removal/ ); const board = context.db .select() .from(distributionBoards) .where(eq(distributionBoards.id, downstream.distributionBoard.id)) .get(); assert.equal(board?.feedingCircuitId, circuit.id); assert.ok( context.db .select() .from(circuits) .where(eq(circuits.id, circuit.id)) .get() ); } finally { context.close(); } }); });