import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { deserializeProjectStateSnapshot, parseProjectStateSnapshot, projectStateSnapshotSchemaVersion, serializeProjectStateSnapshot, } from "../src/domain/models/project-state-snapshot.model.js"; import { createNamedProjectSnapshotSchema } from "../src/shared/validation/project-snapshot.schemas.js"; function minimalSnapshot() { return { schemaVersion: projectStateSnapshotSchemaVersion, project: { id: "project-1", name: "Projekt", internalProjectNumber: "INT-1", externalProjectNumber: null, buildingOwner: null, description: null, singlePhaseVoltageV: 230, threePhaseVoltageV: 400, enabledDistributionBoardSupplyTypes: [ "AV", "SV", "EV", "USV", "MSR", "SiBe", ], }, distributionBoards: [], circuitLists: [], circuitSections: [], circuits: [], projectDevices: [], floors: [], rooms: [], }; } describe("project state snapshot model", () => { it("round-trips a versioned logical project state", () => { const snapshot = parseProjectStateSnapshot(minimalSnapshot()); assert.deepEqual( deserializeProjectStateSnapshot( serializeProjectStateSnapshot(snapshot) ), snapshot ); }); it("upgrades version-one snapshots with empty project metadata", () => { const legacy = { ...minimalSnapshot(), schemaVersion: 1 as const, project: { id: "project-1", name: "Altprojekt", singlePhaseVoltageV: 230, threePhaseVoltageV: 400, }, }; const snapshot = parseProjectStateSnapshot(legacy); assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion); assert.deepEqual(snapshot.project, { ...legacy.project, internalProjectNumber: null, externalProjectNumber: null, buildingOwner: null, description: null, enabledDistributionBoardSupplyTypes: [ "AV", "SV", "EV", "USV", "MSR", "SiBe", ], }); }); it("upgrades version-two distribution boards without inferred assignments", () => { const previous = { ...minimalSnapshot(), schemaVersion: 2 as const, project: { ...minimalSnapshot().project, enabledDistributionBoardSupplyTypes: undefined, }, distributionBoards: [ { id: "board-1", projectId: "project-1", name: "UV 1", }, ], }; delete previous.project.enabledDistributionBoardSupplyTypes; const snapshot = parseProjectStateSnapshot(previous); assert.deepEqual(snapshot.distributionBoards, [ { ...previous.distributionBoards[0], floorId: null, supplyType: null, simultaneityFactor: 1, }, ]); }); it("upgrades version-three snapshots with all project supply types", () => { const previous = { ...minimalSnapshot(), schemaVersion: 3 as const, project: { ...minimalSnapshot().project, enabledDistributionBoardSupplyTypes: undefined, }, }; delete previous.project.enabledDistributionBoardSupplyTypes; const snapshot = parseProjectStateSnapshot(previous); assert.deepEqual(snapshot.project.enabledDistributionBoardSupplyTypes, [ "AV", "SV", "EV", "USV", "MSR", "SiBe", ]); }); it("normalizes version-four device voltages to project settings", () => { const previous = { ...minimalSnapshot(), schemaVersion: 4 as const, projectDevices: [ { id: "device-1", projectId: "project-1", name: "Pumpe", displayName: "Pumpe", phaseType: "three_phase" as const, connectionKind: null, costGroup: null, category: null, quantity: 1, powerPerUnit: 2, simultaneityFactor: 1, cosPhi: null, remark: null, voltageV: 500, }, ], }; const snapshot = parseProjectStateSnapshot(previous); assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion); assert.equal(snapshot.projectDevices[0]?.voltageV, 400); }); it("upgrades version-five distribution boards with a neutral factor", () => { const previous = { ...minimalSnapshot(), schemaVersion: 5 as const, distributionBoards: [ { id: "board-1", projectId: "project-1", name: "UV 1", floorId: null, supplyType: "AV" as const, }, ], }; const snapshot = parseProjectStateSnapshot(previous); assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion); assert.equal( snapshot.distributionBoards[0]?.simultaneityFactor, 1 ); }); it("rejects duplicate ids and cross-project ownership", () => { assert.throws( () => parseProjectStateSnapshot({ ...minimalSnapshot(), distributionBoards: [ { id: "board-1", projectId: "project-1", name: "UV 1", floorId: null, supplyType: null, simultaneityFactor: 1, }, { id: "board-1", projectId: "project-1", name: "UV 2", floorId: null, supplyType: null, simultaneityFactor: 1, }, ], }), /duplicate distribution board ids/ ); assert.throws( () => parseProjectStateSnapshot({ ...minimalSnapshot(), floors: [ { id: "floor-1", projectId: "project-2", name: "EG", sortOrder: 10, }, ], }), /floor belongs to a different project/ ); }); it("rejects invalid references and inconsistent reserve circuits", () => { assert.throws( () => parseProjectStateSnapshot({ ...minimalSnapshot(), distributionBoards: [ { id: "board-1", projectId: "project-1", name: "UV 1", floorId: "missing", supplyType: "AV", simultaneityFactor: 1, }, ], }), /distribution board floor reference is invalid/ ); assert.throws( () => parseProjectStateSnapshot({ ...minimalSnapshot(), rooms: [ { id: "room-1", projectId: "project-1", floorId: "missing", roomNumber: "001", roomName: "Technik", }, ], }), /room floor reference is invalid/ ); assert.throws( () => parseProjectStateSnapshot({ ...minimalSnapshot(), circuits: [ { id: "circuit-1", circuitListId: "missing", sectionId: "missing", equipmentIdentifier: "-1F1", displayName: null, sortOrder: 10, 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: [], }, ], }), /circuit list reference is invalid/ ); }); }); describe("named project snapshot request", () => { it("normalizes bounded snapshot metadata", () => { assert.deepEqual( createNamedProjectSnapshotSchema.parse({ expectedRevision: 12, name: " Freigabe ", description: " Stand vor Ausschreibung ", }), { expectedRevision: 12, name: "Freigabe", description: "Stand vor Ausschreibung", } ); }); it("rejects stale-shaped and excessive input", () => { for (const input of [ { expectedRevision: -1, name: "Stand" }, { expectedRevision: 0, name: " " }, { expectedRevision: 0, name: "x".repeat(101) }, { expectedRevision: 0, name: "Stand", description: "x".repeat(501), }, { expectedRevision: 0, name: "Stand", unexpected: true, }, ]) { assert.equal( createNamedProjectSnapshotSchema.safeParse(input).success, false ); } }); });