Files
leistungsbilanz-ts/tests/project-command.model.test.ts
T
2026-07-23 21:38:20 +02:00

169 lines
4.4 KiB
TypeScript

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<string, unknown> = {};
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/
);
});
});