import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { deserializeProjectCommand, serializeProjectCommand, type SerializedProjectCommand, } from "../src/domain/models/project-command.model.js"; import { assertCircuitUpdateProjectCommand, createCircuitUpdateProjectCommand, } from "../src/domain/models/circuit-project-command.model.js"; import { assertCircuitDeviceRowUpdateProjectCommand, createCircuitDeviceRowUpdateProjectCommand, } from "../src/domain/models/circuit-device-row-project-command.model.js"; describe("serialized project commands", () => { it("round-trips a versioned command envelope", () => { const command: SerializedProjectCommand = { schemaVersion: 1, type: "circuit.update", payload: { circuitId: "circuit-1", changes: { displayName: "Werkstatt", protectionRatedCurrent: 16, isReserve: false, remark: null, }, }, }; assert.deepEqual( deserializeProjectCommand(serializeProjectCommand(command)), command ); }); it("rejects values that JSON would lose or silently change", () => { const invalidPayloads: unknown[] = [ { value: undefined }, { value: Number.NaN }, { value: Number.POSITIVE_INFINITY }, { value: new Date() }, ]; for (const payload of invalidPayloads) { assert.throws(() => serializeProjectCommand({ schemaVersion: 1, type: "test.invalid", payload: payload as never, }) ); } }); it("rejects cyclic payloads before persistence", () => { const payload: Record = {}; payload.self = payload; assert.throws( () => serializeProjectCommand({ schemaVersion: 1, type: "test.cyclic", payload: payload as never, }), /must not contain cycles/ ); }); it("rejects malformed serialized envelopes", () => { assert.throws( () => deserializeProjectCommand( JSON.stringify({ schemaVersion: 0, type: "", payload: null }) ), /schema version/ ); }); }); describe("circuit update project commands", () => { it("creates a typed command with explicit null clearing semantics", () => { const command = createCircuitUpdateProjectCommand("circuit-1", { displayName: null, cableLength: 12.5, isReserve: false, }); assert.doesNotThrow(() => assertCircuitUpdateProjectCommand(command)); assert.deepEqual(command.payload.changes, [ { field: "displayName", value: null }, { field: "cableLength", value: 12.5 }, { field: "isReserve", value: false }, ]); }); it("rejects empty, duplicate and invalid field changes", () => { assert.throws( () => createCircuitUpdateProjectCommand("circuit-1", {}), /at least one change/ ); assert.throws( () => assertCircuitUpdateProjectCommand({ schemaVersion: 1, type: "circuit.update", payload: { circuitId: "circuit-1", changes: [ { field: "displayName", value: "A" }, { field: "displayName", value: "B" }, ], }, }), /duplicate field/ ); assert.throws( () => createCircuitUpdateProjectCommand("circuit-1", { voltage: 0, }), /outside its allowed range/ ); }); }); describe("circuit device-row update project commands", () => { it("creates typed nullable device-row changes", () => { const command = createCircuitDeviceRowUpdateProjectCommand("row-1", { roomId: null, quantity: 2, cosPhi: 0.9, }); assert.doesNotThrow(() => assertCircuitDeviceRowUpdateProjectCommand(command) ); assert.deepEqual(command.payload.changes, [ { field: "roomId", value: null }, { field: "quantity", value: 2 }, { field: "cosPhi", value: 0.9 }, ]); }); it("rejects invalid device-row values", () => { assert.throws( () => createCircuitDeviceRowUpdateProjectCommand("row-1", {}), /at least one change/ ); assert.throws( () => createCircuitDeviceRowUpdateProjectCommand("row-1", { displayName: "", }), /non-empty string/ ); assert.throws( () => createCircuitDeviceRowUpdateProjectCommand("row-1", { quantity: -1, }), /non-negative/ ); }); });