Files
leistungsbilanz-ts/tests/project-device-sync.service.test.ts
T

168 lines
4.5 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { ProjectDeviceSyncService } from "../src/domain/services/project-device-sync.service.js";
function projectDevice() {
return {
id: "pd1",
projectId: "p1",
name: "Luminaire",
displayName: "Office lighting",
phaseType: "single_phase",
connectionKind: "fixed",
costGroup: "440",
category: "Lighting",
quantity: 6,
powerPerUnit: 0.04,
simultaneityFactor: 0.8,
totalPower: 0.192,
cosPhi: 0.95,
remark: "DALI",
voltageV: 230,
};
}
function linkedRow() {
return {
id: "row1",
circuitId: "c1",
linkedProjectDeviceId: "pd1",
legacyConsumerId: null,
sortOrder: 10,
name: "Old luminaire",
displayName: "Local display name",
phaseType: "single_phase",
connectionKind: null,
costGroup: null,
category: "Lighting",
level: null,
roomId: null,
roomNumberSnapshot: "1.01",
roomNameSnapshot: "Office",
quantity: 2,
powerPerUnit: 0.03,
simultaneityFactor: 1,
cosPhi: 1,
remark: null,
overriddenFields: JSON.stringify(["displayName", "quantity"]),
equipmentIdentifier: "-1F1",
circuitDisplayName: "Office",
circuitListId: "list1",
circuitListName: "UV-01 Stromkreisliste",
distributionBoardId: "db1",
distributionBoardName: "UV-01",
};
}
function createService() {
const rows = [linkedRow()];
const service = new ProjectDeviceSyncService({
projectDeviceRepository: {
async findById() {
return projectDevice() as never;
},
},
deviceRowRepository: {
async listLinkedByProjectDevice() {
return rows as never;
},
},
});
return { service, rows };
}
describe("project device synchronization", () => {
it("shows field differences and local overrides without changing rows", async () => {
const { service, rows } = createService();
const before = structuredClone(rows);
const preview = await service.getPreview("p1", "pd1");
assert.deepEqual(rows, before);
assert.equal(preview.rows[0].equipmentIdentifier, "-1F1");
assert.equal(
preview.rows[0].differences.some(
(difference) => difference.field === "quantity"
),
true
);
assert.equal(
preview.rows[0].differences.find(
(difference) => difference.field === "displayName"
)?.isOverridden,
true
);
});
it("creates a deterministic command for selected synchronization fields", async () => {
const { service, rows } = createService();
const before = structuredClone(rows);
const command = await service.createSynchronizeCommand(
"p1",
"pd1",
["row1"],
["quantity", "powerPerUnit"]
);
assert.deepEqual(rows, before);
assert.equal(command.payload.operation, "synchronize");
assert.equal(command.payload.rows[0].expected.quantity, 2);
assert.equal(command.payload.rows[0].target.quantity, 6);
assert.equal(command.payload.rows[0].target.powerPerUnit, 0.04);
assert.equal(
command.payload.rows[0].target.displayName,
"Local display name"
);
assert.deepEqual(
JSON.parse(command.payload.rows[0].target.overriddenFields ?? "[]"),
["displayName"]
);
});
it("rejects rows that are not linked to the selected project device", async () => {
const { service } = createService();
await assert.rejects(
() =>
service.createSynchronizeCommand(
"p1",
"pd1",
["other-row"],
["quantity"]
),
/not linked/
);
});
it("creates a link-only disconnect command", async () => {
const { service } = createService();
const command = await service.createDisconnectCommand(
"p1",
"pd1",
["row1"]
);
const assignment = command.payload.rows[0];
assert.equal(command.payload.operation, "disconnect");
assert.equal(assignment.expected.linkedProjectDeviceId, "pd1");
assert.equal(assignment.target.linkedProjectDeviceId, null);
assert.equal(
assignment.target.displayName,
assignment.expected.displayName
);
assert.equal(assignment.target.quantity, assignment.expected.quantity);
});
it("does not create no-op synchronization commands", async () => {
const { service } = createService();
await assert.rejects(
() =>
service.createSynchronizeCommand(
"p1",
"pd1",
["row1"],
["category"]
),
/at least one row/
);
});
});