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 { CircuitDeviceRowProjectCommandRepository } from "../src/db/repositories/circuit-device-row-project-command.repository.js"; import { CircuitDeviceRowMoveProjectCommandRepository } from "../src/db/repositories/circuit-device-row-move-project-command.repository.js"; import { CircuitDeviceRowStructureProjectCommandRepository } from "../src/db/repositories/circuit-device-row-structure-project-command.repository.js"; import { CircuitProjectCommandRepository } from "../src/db/repositories/circuit-project-command.repository.js"; import { CircuitSectionReorderProjectCommandRepository } from "../src/db/repositories/circuit-section-reorder-project-command.repository.js"; import { CircuitSectionRenumberProjectCommandRepository } from "../src/db/repositories/circuit-section-renumber-project-command.repository.js"; import { CircuitStructureProjectCommandRepository } from "../src/db/repositories/circuit-structure-project-command.repository.js"; import { DistributionBoardRepository } from "../src/db/repositories/distribution-board.repository.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 { ProjectDeviceProjectCommandRepository } from "../src/db/repositories/project-device-project-command.repository.js"; import { ProjectDeviceRowSyncProjectCommandRepository } from "../src/db/repositories/project-device-row-sync-project-command.repository.js"; import { ProjectDeviceStructureProjectCommandRepository } from "../src/db/repositories/project-device-structure-project-command.repository.js"; import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js"; import { ProjectSettingsProjectCommandRepository } from "../src/db/repositories/project-settings-project-command.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 { projectChangeSets } from "../src/db/schema/project-change-sets.js"; import { projectDevices } from "../src/db/schema/project-devices.js"; import { projectRevisions } from "../src/db/schema/project-revisions.js"; import { projects } from "../src/db/schema/projects.js"; import { ProjectHistoryOperationUnavailableError } from "../src/domain/errors/project-history-operation-unavailable.error.js"; import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js"; import { createCircuitDeviceRowUpdateProjectCommand } from "../src/domain/models/circuit-device-row-project-command.model.js"; import { createCircuitDeviceRowMoveProjectCommand, createCircuitDeviceRowMoveWithNewCircuitProjectCommand, } from "../src/domain/models/circuit-device-row-move-project-command.model.js"; import { createCircuitDeviceRowInsertProjectCommand } from "../src/domain/models/circuit-device-row-structure-project-command.model.js"; import { createCircuitUpdateProjectCommand } from "../src/domain/models/circuit-project-command.model.js"; import { createCircuitSectionReorderProjectCommand } from "../src/domain/models/circuit-section-reorder-project-command.model.js"; import { createCircuitSectionsReorderProjectCommand } from "../src/domain/models/circuit-sections-reorder-project-command.model.js"; import { createCircuitSectionRenumberProjectCommand } from "../src/domain/models/circuit-section-renumber-project-command.model.js"; import { createCircuitInsertProjectCommand } from "../src/domain/models/circuit-structure-project-command.model.js"; import { createDistributionBoardInsertProjectCommand, createDistributionBoardStructureSnapshot, } from "../src/domain/models/distribution-board-structure-project-command.model.js"; import { createProjectDeviceRowSyncProjectCommand } from "../src/domain/models/project-device-row-sync-project-command.model.js"; import { createProjectDeviceUpdateProjectCommand } from "../src/domain/models/project-device-project-command.model.js"; import { createProjectDeviceInsertProjectCommand } from "../src/domain/models/project-device-structure-project-command.model.js"; import { createProjectSettingsUpdateProjectCommand } from "../src/domain/models/project-settings-project-command.model.js"; import { ProjectCommandService } from "../src/domain/services/project-command.service.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)) .get(); assert.ok(section); context.db .insert(circuits) .values({ id: "circuit-1", circuitListId: board.id, sectionId: section.id, equipmentIdentifier: "-1F1", displayName: "Bestand", sortOrder: 10, }) .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 createService(context: DatabaseContext) { return new ProjectCommandService( new CircuitProjectCommandRepository(context.db), new CircuitDeviceRowProjectCommandRepository(context.db), new CircuitDeviceRowStructureProjectCommandRepository(context.db), new CircuitDeviceRowMoveProjectCommandRepository(context.db), new CircuitStructureProjectCommandRepository(context.db), new DistributionBoardStructureProjectCommandRepository(context.db), new CircuitSectionReorderProjectCommandRepository(context.db), new CircuitSectionRenumberProjectCommandRepository(context.db), new ProjectDeviceStructureProjectCommandRepository(context.db), new ProjectDeviceProjectCommandRepository(context.db), new ProjectDeviceRowSyncProjectCommandRepository(context.db), new ProjectSettingsProjectCommandRepository(context.db), new ProjectStateRestoreCommandRepository(context.db), new ProjectHistoryRepository(context.db) ); } function getCircuitName(context: DatabaseContext) { return context.db .select({ displayName: circuits.displayName }) .from(circuits) .where(eq(circuits.id, "circuit-1")) .get()?.displayName; } function getRowQuantity(context: DatabaseContext) { return context.db .select({ quantity: circuitDeviceRows.quantity }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.quantity; } describe("project command service", () => { it("dispatches supported commands and persists undo/redo across service instances", () => { const context = createTestDatabase(); try { const service = createService(context); const circuitResult = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitUpdateProjectCommand("circuit-1", { displayName: "Neu", }), }); assert.equal(circuitResult.revision.revisionNumber, 1); assert.equal(circuitResult.history.undoDepth, 1); assert.equal(getCircuitName(context), "Neu"); const rowResult = service.executeUser({ projectId: "project-1", expectedRevision: 1, command: createCircuitDeviceRowUpdateProjectCommand("row-1", { quantity: 2, }), }); assert.equal(rowResult.revision.revisionNumber, 2); assert.equal(rowResult.history.undoDepth, 2); assert.equal(getRowQuantity(context), 2); const reloadedService = createService(context); const firstUndo = reloadedService.undo({ projectId: "project-1", expectedRevision: 2, }); assert.equal(firstUndo.revision.revisionNumber, 3); assert.equal(firstUndo.history.undoDepth, 1); assert.equal(firstUndo.history.redoDepth, 1); assert.equal(getRowQuantity(context), 1); const secondUndo = reloadedService.undo({ projectId: "project-1", expectedRevision: 3, }); assert.equal(secondUndo.revision.revisionNumber, 4); assert.equal(secondUndo.history.undoDepth, 0); assert.equal(secondUndo.history.redoDepth, 2); assert.equal(getCircuitName(context), "Bestand"); const redo = reloadedService.redo({ projectId: "project-1", expectedRevision: 4, }); assert.equal(redo.revision.revisionNumber, 5); assert.equal(redo.history.undoDepth, 1); assert.equal(redo.history.redoDepth, 1); assert.equal(getCircuitName(context), "Neu"); assert.deepEqual( context.db .select({ source: projectRevisions.source }) .from(projectRevisions) .all() .map((row) => row.source), ["user", "user", "undo", "undo", "redo"] ); } finally { context.close(); } }); it("dispatches project-device row synchronization and its inverse", () => { const context = createTestDatabase(); try { context.db .insert(projectDevices) .values({ id: "project-device-1", projectId: "project-1", name: "Luminaire", displayName: "Office lighting", phaseType: "single_phase", quantity: 4, powerPerUnit: 0.04, simultaneityFactor: 0.8, }) .run(); context.db .update(circuitDeviceRows) .set({ linkedProjectDeviceId: "project-device-1", overriddenFields: '["displayName"]', }) .where(eq(circuitDeviceRows.id, "row-1")) .run(); const expected = { linkedProjectDeviceId: "project-device-1", name: "Leuchte", displayName: "Leuchte", phaseType: null, connectionKind: null, costGroup: null, category: null, quantity: 1, powerPerUnit: 0.1, simultaneityFactor: 1, cosPhi: null, remark: null, overriddenFields: '["displayName"]', }; const service = createService(context); const synchronized = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createProjectDeviceRowSyncProjectCommand( "project-device-1", "synchronize", [ { rowId: "row-1", expected, target: { ...expected, name: "Luminaire", displayName: "Office lighting", quantity: 4, powerPerUnit: 0.04, simultaneityFactor: 0.8, overriddenFields: null, }, }, ] ), }); assert.equal(synchronized.history.undoDepth, 1); assert.equal(getRowQuantity(context), 4); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.redoDepth, 1); assert.equal(getRowQuantity(context), 1); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.overriddenFields, '["displayName"]' ); } finally { context.close(); } }); it("dispatches project-device updates and their persisted inverse", () => { const context = createTestDatabase(); try { context.db .insert(projectDevices) .values({ id: "project-device-1", projectId: "project-1", name: "Luminaire", displayName: "Office lighting", phaseType: "single_phase", quantity: 4, powerPerUnit: 0.04, simultaneityFactor: 0.8, }) .run(); const service = createService(context); const updated = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createProjectDeviceUpdateProjectCommand( "project-device-1", { displayName: "Flurbeleuchtung", powerPerUnit: 0.05, } ), }); assert.equal(updated.history.undoDepth, 1); assert.equal( context.db .select() .from(projectDevices) .where(eq(projectDevices.id, "project-device-1")) .get()?.displayName, "Flurbeleuchtung" ); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.redoDepth, 1); assert.equal( context.db .select() .from(projectDevices) .where(eq(projectDevices.id, "project-device-1")) .get()?.displayName, "Office lighting" ); } finally { context.close(); } }); it("dispatches project-device insertion and deletion", () => { const context = createTestDatabase(); try { const service = createService(context); const inserted = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createProjectDeviceInsertProjectCommand({ id: "project-device-new", projectId: "project-1", name: "Socket", displayName: "Office socket", phaseType: "single_phase", connectionKind: null, costGroup: "440", category: "socket", quantity: 2, powerPerUnit: 0.2, simultaneityFactor: 0.5, cosPhi: null, remark: null, voltageV: 230, }), }); assert.equal(inserted.history.undoDepth, 1); assert.equal( context.db .select() .from(projectDevices) .where(eq(projectDevices.id, "project-device-new")) .get()?.displayName, "Office socket" ); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.redoDepth, 1); assert.equal( context.db .select() .from(projectDevices) .where(eq(projectDevices.id, "project-device-new")) .get(), undefined ); } finally { context.close(); } }); it("rejects stale undo and preserves the domain value and stack", () => { const context = createTestDatabase(); try { const service = createService(context); const changed = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitUpdateProjectCommand("circuit-1", { displayName: "Neu", }), }); assert.throws( () => service.undo({ projectId: "project-1", expectedRevision: 0, }), (error) => error instanceof ProjectRevisionConflictError && error.actualRevision === 1 ); assert.equal(getCircuitName(context), "Neu"); assert.deepEqual( new ProjectHistoryRepository(context.db).getState("project-1"), changed.history ); assert.equal(context.db.select().from(projectRevisions).all().length, 1); assert.equal(context.db.select().from(projectChangeSets).all().length, 1); } finally { context.close(); } }); it("dispatches device-row insertion and its persisted inverse", () => { const context = createTestDatabase(); try { const service = createService(context); const inserted = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitDeviceRowInsertProjectCommand({ id: "row-2", circuitId: "circuit-1", linkedProjectDeviceId: null, legacyConsumerId: null, sortOrder: 20, name: "Steckdose", displayName: "Steckdose", phaseType: "single_phase", connectionKind: null, costGroup: null, category: null, level: null, roomId: null, roomNumberSnapshot: null, roomNameSnapshot: null, quantity: 1, powerPerUnit: 0.2, simultaneityFactor: 1, cosPhi: null, remark: null, overriddenFields: null, }), }); assert.equal(inserted.history.undoDepth, 1); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-2")) .get()?.displayName, "Steckdose" ); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.redoDepth, 1); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-2")) .get(), undefined ); } finally { context.close(); } }); it("dispatches complete circuit insertion and its persisted inverse", () => { const context = createTestDatabase(); try { const existingCircuit = context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-1")) .get(); assert.ok(existingCircuit); const service = createService(context); const inserted = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitInsertProjectCommand({ id: "circuit-new", circuitListId: existingCircuit.circuitListId, sectionId: existingCircuit.sectionId, equipmentIdentifier: "-1F2", displayName: "Reserve", sortOrder: 20, protectionType: null, protectionRatedCurrent: null, protectionCharacteristic: null, cableType: null, cableCrossSection: null, cableLength: null, rcdAssignment: null, terminalDesignation: null, voltage: 230, controlRequirement: null, status: null, isReserve: true, remark: null, deviceRows: [], }), }); assert.equal(inserted.history.undoDepth, 1); assert.equal( context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-new")) .get()?.equipmentIdentifier, "-1F2" ); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.redoDepth, 1); assert.equal( context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-new")) .get(), undefined ); } finally { context.close(); } }); it("dispatches deterministic device-row moves and their inverse", () => { const context = createTestDatabase(); try { const sourceCircuit = context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-1")) .get(); assert.ok(sourceCircuit); context.db .insert(circuits) .values({ id: "circuit-2", circuitListId: sourceCircuit.circuitListId, sectionId: sourceCircuit.sectionId, equipmentIdentifier: "-1F2", sortOrder: 20, isReserve: 1, }) .run(); const service = createService(context); const moved = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitDeviceRowMoveProjectCommand([ { rowId: "row-1", expectedCircuitId: "circuit-1", expectedSortOrder: 10, targetCircuitId: "circuit-2", targetSortOrder: 10, }, ]), }); assert.equal(moved.history.undoDepth, 1); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.circuitId, "circuit-2" ); createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.circuitId, "circuit-1" ); } finally { context.close(); } }); it("dispatches placeholder moves that create and remove a circuit", () => { const context = createTestDatabase(); try { const sourceCircuit = context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-1")) .get(); assert.ok(sourceCircuit); const targetCircuit = { id: "circuit-placeholder-target", circuitListId: sourceCircuit.circuitListId, sectionId: sourceCircuit.sectionId, equipmentIdentifier: "-1F2", displayName: "Neuer Stromkreis", sortOrder: 20, protectionType: null, protectionRatedCurrent: null, protectionCharacteristic: null, cableType: null, cableCrossSection: null, cableLength: null, rcdAssignment: null, terminalDesignation: null, voltage: null, controlRequirement: null, status: null, isReserve: true, remark: null, deviceRows: [], }; const moved = createService(context).executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitDeviceRowMoveWithNewCircuitProjectCommand( "create", targetCircuit, [ { rowId: "row-1", expectedCircuitId: "circuit-1", expectedSortOrder: 10, targetCircuitId: targetCircuit.id, targetSortOrder: 10, }, ] ), }); assert.equal(moved.history.undoDepth, 1); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.circuitId, targetCircuit.id ); createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal( context.db .select({ id: circuits.id }) .from(circuits) .where(eq(circuits.id, targetCircuit.id)) .get(), undefined ); assert.equal( context.db .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, "row-1")) .get()?.circuitId, "circuit-1" ); } finally { context.close(); } }); it("dispatches complete section reorders without changing identifiers", () => { const context = createTestDatabase(); try { const firstCircuit = context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-1")) .get(); assert.ok(firstCircuit); context.db .insert(circuits) .values({ id: "circuit-2", circuitListId: firstCircuit.circuitListId, sectionId: firstCircuit.sectionId, equipmentIdentifier: "-1F2", sortOrder: 20, isReserve: 1, }) .run(); const reordered = createService(context).executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitSectionReorderProjectCommand( firstCircuit.sectionId, [ { circuitId: "circuit-1", expectedSortOrder: 10, targetSortOrder: 20, }, { circuitId: "circuit-2", expectedSortOrder: 20, targetSortOrder: 10, }, ] ), }); assert.equal(reordered.history.undoDepth, 1); assert.deepEqual( context.db .select({ id: circuits.id, equipmentIdentifier: circuits.equipmentIdentifier, sortOrder: circuits.sortOrder, }) .from(circuits) .all() .sort((left, right) => left.id.localeCompare(right.id)), [ { id: "circuit-1", equipmentIdentifier: "-1F1", sortOrder: 20, }, { id: "circuit-2", equipmentIdentifier: "-1F2", sortOrder: 10, }, ] ); createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.deepEqual( context.db .select({ id: circuits.id, sortOrder: circuits.sortOrder }) .from(circuits) .all() .sort((left, right) => left.id.localeCompare(right.id)), [ { id: "circuit-1", sortOrder: 10 }, { id: "circuit-2", sortOrder: 20 }, ] ); const multiSection = createService(context).executeUser({ projectId: "project-1", expectedRevision: 2, command: createCircuitSectionsReorderProjectCommand([ { sectionId: firstCircuit.sectionId, assignments: createCircuitSectionReorderProjectCommand( firstCircuit.sectionId, [ { circuitId: "circuit-1", expectedSortOrder: 10, targetSortOrder: 20, }, { circuitId: "circuit-2", expectedSortOrder: 20, targetSortOrder: 10, }, ] ).payload.assignments, }, ]), }); assert.equal(multiSection.history.currentRevision, 3); assert.equal(multiSection.history.redoDepth, 0); } finally { context.close(); } }); it("dispatches explicit section renumbering and its inverse", () => { const context = createTestDatabase(); try { const firstCircuit = context.db .select() .from(circuits) .where(eq(circuits.id, "circuit-1")) .get(); assert.ok(firstCircuit); context.db .insert(circuits) .values({ id: "circuit-2", circuitListId: firstCircuit.circuitListId, sectionId: firstCircuit.sectionId, equipmentIdentifier: "-1F2", sortOrder: 20, isReserve: 1, }) .run(); const renumbered = createService(context).executeUser({ projectId: "project-1", expectedRevision: 0, command: createCircuitSectionRenumberProjectCommand( firstCircuit.sectionId, [ { circuitId: "circuit-1", expectedEquipmentIdentifier: "-1F1", targetEquipmentIdentifier: "-1F2", }, { circuitId: "circuit-2", expectedEquipmentIdentifier: "-1F2", targetEquipmentIdentifier: "-1F1", }, ] ), }); assert.equal(renumbered.history.undoDepth, 1); assert.deepEqual( context.db .select({ id: circuits.id, equipmentIdentifier: circuits.equipmentIdentifier, sortOrder: circuits.sortOrder, }) .from(circuits) .all() .sort((left, right) => left.id.localeCompare(right.id)), [ { id: "circuit-1", equipmentIdentifier: "-1F2", sortOrder: 10, }, { id: "circuit-2", equipmentIdentifier: "-1F1", sortOrder: 20, }, ] ); createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.deepEqual( context.db .select({ id: circuits.id, equipmentIdentifier: circuits.equipmentIdentifier, }) .from(circuits) .all() .sort((left, right) => left.id.localeCompare(right.id)), [ { id: "circuit-1", equipmentIdentifier: "-1F1", }, { id: "circuit-2", equipmentIdentifier: "-1F2", }, ] ); } finally { context.close(); } }); it("rejects unavailable history directions without writing a revision", () => { const context = createTestDatabase(); try { const service = createService(context); assert.throws( () => service.undo({ projectId: "project-1", expectedRevision: 0, }), (error) => error instanceof ProjectHistoryOperationUnavailableError && error.direction === "undo" ); assert.throws( () => service.redo({ projectId: "project-1", expectedRevision: 0, }), (error) => error instanceof ProjectHistoryOperationUnavailableError && error.direction === "redo" ); assert.equal(context.db.select().from(projectRevisions).all().length, 0); } finally { context.close(); } }); it("dispatches project settings updates through persistent history", () => { const context = createTestDatabase(); try { const service = createService(context); const updated = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createProjectSettingsUpdateProjectCommand({ singlePhaseVoltageV: 240, threePhaseVoltageV: 415, }), }); assert.equal(updated.history.currentRevision, 1); assert.deepEqual( context.db .select({ singlePhaseVoltageV: projects.singlePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV, }) .from(projects) .where(eq(projects.id, "project-1")) .get(), { singlePhaseVoltageV: 240, threePhaseVoltageV: 415, } ); const undone = service.undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.currentRevision, 2); assert.deepEqual( context.db .select({ singlePhaseVoltageV: projects.singlePhaseVoltageV, threePhaseVoltageV: projects.threePhaseVoltageV, }) .from(projects) .where(eq(projects.id, "project-1")) .get(), { singlePhaseVoltageV: 230, threePhaseVoltageV: 400, } ); } finally { context.close(); } }); it("dispatches distribution-board setup and its persisted inverse", () => { const context = createTestDatabase(); try { const service = createService(context); const structure = createDistributionBoardStructureSnapshot( "project-1", "UV-02" ); const created = service.executeUser({ projectId: "project-1", expectedRevision: 0, command: createDistributionBoardInsertProjectCommand(structure), }); assert.equal(created.history.currentRevision, 1); assert.ok( context.db .select() .from(circuitSections) .where( eq( circuitSections.circuitListId, structure.circuitList.id ) ) .get() ); const undone = createService(context).undo({ projectId: "project-1", expectedRevision: 1, }); assert.equal(undone.history.currentRevision, 2); assert.equal( context.db .select() .from(circuitSections) .where( eq( circuitSections.circuitListId, structure.circuitList.id ) ) .all().length, 0 ); } finally { context.close(); } }); it("rejects unsupported and malformed commands before domain writes", () => { const context = createTestDatabase(); try { const service = createService(context); assert.throws( () => service.executeUser({ projectId: "project-1", expectedRevision: 0, command: { schemaVersion: 1, type: "unknown.command", payload: {}, }, }), /Unsupported project command type/ ); assert.throws( () => service.executeUser({ projectId: "project-1", expectedRevision: 0, command: { schemaVersion: 1, type: "circuit.update", payload: { circuitId: "circuit-1", changes: [] }, }, }), /at least one change/ ); assert.equal(getCircuitName(context), "Bestand"); assert.equal(context.db.select().from(projectRevisions).all().length, 0); } finally { context.close(); } }); });