Files
leistungsbilanz-ts/tests/circuit-write.rules.test.ts
T

1179 lines
36 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { CircuitWriteService } from "../src/domain/services/circuit-write.service.js";
import { CircuitDeviceRowRepository } from "../src/db/repositories/circuit-device-row.repository.js";
import { CircuitRepository } from "../src/db/repositories/circuit.repository.js";
import { db } from "../src/db/client.js";
import { createCircuitSchema } from "../src/shared/validation/circuit.schemas.js";
describe("circuit write service rules", () => {
it("accepts future sizing inputs without calculating sizing suggestions", () => {
const parsed = createCircuitSchema.safeParse({
sectionId: "s1",
equipmentIdentifier: "-1F1",
sortOrder: 10,
voltage: 230,
controlRequirement: "DALI",
});
assert.equal(parsed.success, true);
});
it("passes voltage and control requirements through circuit updates", async () => {
let updatePayload: Record<string, unknown> = {};
const circuit = {
id: "c1",
circuitListId: "l1",
sectionId: "s1",
equipmentIdentifier: "-1F1",
sortOrder: 10,
isReserve: 0,
voltage: 230,
controlRequirement: "none",
};
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1" } as never;
},
} as never,
circuitRepository: {
async findById() {
return circuit as never;
},
async existsByEquipmentIdentifier() {
return false;
},
async update(_id: string, payload: Record<string, unknown>) {
updatePayload = payload;
},
} as never,
});
await service.updateCircuit("c1", { voltage: 400, controlRequirement: "DALI" });
assert.equal(updatePayload.voltage, 400);
assert.equal(updatePayload.controlRequirement, "DALI");
});
it("rejects duplicate equipment identifiers in same circuit list", async () => {
const service = new CircuitWriteService({
circuitListRepository: {
async findById() {
return { id: "list1", projectId: "p1" } as never;
},
} as never,
circuitSectionRepository: {
async findById() {
return { id: "sec1", circuitListId: "list1" } as never;
},
} as never,
circuitRepository: {
async existsByEquipmentIdentifier() {
return true;
},
} as never,
});
await assert.rejects(
() =>
service.createCircuit("p1", "list1", {
sectionId: "sec1",
equipmentIdentifier: "-2F1",
sortOrder: 10,
}),
/Duplicate equipmentIdentifier/
);
});
it("rejects section and list mismatch", async () => {
const service = new CircuitWriteService({
circuitListRepository: {
async findById() {
return { id: "list1", projectId: "p1" } as never;
},
} as never,
circuitSectionRepository: {
async findById() {
return { id: "sec1", circuitListId: "other" } as never;
},
} as never,
circuitRepository: {
async existsByEquipmentIdentifier() {
return false;
},
} as never,
});
await assert.rejects(
() =>
service.createCircuit("p1", "list1", {
sectionId: "sec1",
equipmentIdentifier: "-2F1",
sortOrder: 10,
}),
/Section does not belong to circuit list/
);
});
it("deleting last device row keeps circuit and sets reserve", async () => {
let transactionalDelete: { rowId: string; circuitId: string } | undefined;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
deleteFromCircuitTransactional(rowId: string, circuitId: string) {
transactionalDelete = { rowId, circuitId };
},
} as never,
circuitRepository: {
async findById() {
return {
id: "c1",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-2F1",
sortOrder: 10,
isReserve: 0,
} as never;
},
} as never,
});
await service.deleteDeviceRow("r1");
assert.deepEqual(transactionalDelete, { rowId: "r1", circuitId: "c1" });
});
it("creating device row in reserve circuit clears reserve status", async () => {
let transactionalCreate:
| {
circuitId: string;
sortOrder?: number;
name: string;
displayName: string;
}
| undefined;
const service = new CircuitWriteService({
circuitRepository: {
async findById() {
return {
id: "c1",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-2F1",
sortOrder: 10,
isReserve: 1,
} as never;
},
} as never,
deviceRowRepository: {
createInCircuitTransactional(input: typeof transactionalCreate) {
transactionalCreate = input;
return "row1";
},
async findById() {
return { id: "row1" } as never;
},
} as never,
circuitListRepository: {} as never,
circuitSectionRepository: {} as never,
projectDeviceRepository: {
async findById() {
return { id: "pd1" } as never;
},
} as never,
});
await service.createDeviceRow("c1", {
name: "Load",
displayName: "Load",
quantity: 1,
powerPerUnit: 1,
simultaneityFactor: 1,
});
assert.deepEqual(transactionalCreate, {
circuitId: "c1",
linkedProjectDeviceId: undefined,
sortOrder: undefined,
name: "Load",
displayName: "Load",
phaseType: undefined,
connectionKind: undefined,
costGroup: undefined,
category: undefined,
level: undefined,
roomId: undefined,
roomNumberSnapshot: undefined,
roomNameSnapshot: undefined,
quantity: 1,
powerPerUnit: 1,
simultaneityFactor: 1,
cosPhi: undefined,
remark: undefined,
overriddenFields: undefined,
});
});
it("creates a circuit and all first device rows through one repository command", async () => {
let transactionalCreate: {
circuit: { circuitListId: string; sectionId: string; equipmentIdentifier: string };
deviceRows: Array<{ linkedProjectDeviceId?: string; name: string }>;
} | undefined;
const service = new CircuitWriteService({
circuitListRepository: {
async findById() {
return { id: "l1", projectId: "p1" } as never;
},
} as never,
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1" } as never;
},
} as never,
circuitRepository: {
async existsByEquipmentIdentifier() {
return false;
},
async findById() {
return { id: "c-new", circuitListId: "l1", sectionId: "s1" } as never;
},
} as never,
projectDeviceRepository: {
async findById(projectId: string, deviceId: string) {
return projectId === "p1" && deviceId === "pd1" ? ({ id: "pd1" } as never) : null;
},
} as never,
deviceRowRepository: {
createCircuitWithDeviceRowsTransactional(input: typeof transactionalCreate) {
transactionalCreate = input;
return { circuitId: "c-new", rowIds: ["r1", "r2"] };
},
async findById(rowId: string) {
return { id: rowId, circuitId: "c-new" } as never;
},
} as never,
});
const created = await service.createCircuitWithDeviceRows("p1", "l1", {
circuit: {
sectionId: "s1",
equipmentIdentifier: "-2F1",
displayName: "Steckdosen",
sortOrder: 10,
},
deviceRows: [
{
linkedProjectDeviceId: "pd1",
name: "Socket",
displayName: "Steckdose",
quantity: 1,
powerPerUnit: 0.2,
simultaneityFactor: 1,
},
{
name: "Manual",
displayName: "Manuell",
quantity: 2,
powerPerUnit: 0.1,
simultaneityFactor: 0.8,
},
],
});
assert.equal(transactionalCreate?.circuit.circuitListId, "l1");
assert.equal(transactionalCreate?.circuit.sectionId, "s1");
assert.equal(transactionalCreate?.circuit.equipmentIdentifier, "-2F1");
assert.deepEqual(
transactionalCreate?.deviceRows.map((row) => row.name),
["Socket", "Manual"]
);
assert.deepEqual(
created.deviceRows.map((row) => row?.id),
["r1", "r2"]
);
});
it("renumber uses safe bulk identifier update for swapped identifiers", async () => {
let safeUpdatePayload: Array<{ id: string; equipmentIdentifier: string }> = [];
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1", prefix: "-2F" } as never;
},
} as never,
circuitRepository: {
async listBySection() {
return [
{ id: "cB", sectionId: "s1", equipmentIdentifier: "-2F2", sortOrder: 10, isReserve: 0 },
{ id: "cA", sectionId: "s1", equipmentIdentifier: "-2F1", sortOrder: 20, isReserve: 1 },
] as never[];
},
async listByCircuitList() {
return [{ id: "x1", sectionId: "s2", equipmentIdentifier: "-3F1" }] as never[];
},
async updateEquipmentIdentifiersSafely(_listId: string, updates: Array<{ id: string; equipmentIdentifier: string }>) {
safeUpdatePayload = updates;
},
} as never,
});
const result = await service.renumberSection("s1");
assert.deepEqual(safeUpdatePayload, [
{ id: "cB", equipmentIdentifier: "-2F1" },
{ id: "cA", equipmentIdentifier: "-2F2" },
]);
assert.equal(result.length, 2);
});
it("renumber shifts forward/backward and respects other sections", async () => {
let safeUpdatePayload: Array<{ id: string; equipmentIdentifier: string }> = [];
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1", prefix: "-1F" } as never;
},
} as never,
circuitRepository: {
async listBySection() {
return [
{ id: "c2", sectionId: "s1", equipmentIdentifier: "-1F5", sortOrder: 10, isReserve: 0 },
{ id: "c1", sectionId: "s1", equipmentIdentifier: "-1F1", sortOrder: 20, isReserve: 0 },
{ id: "c3", sectionId: "s1", equipmentIdentifier: "-1F9", sortOrder: 30, isReserve: 0 },
] as never[];
},
async listByCircuitList() {
return [{ id: "o1", sectionId: "s2", equipmentIdentifier: "-1F2" }] as never[];
},
async updateEquipmentIdentifiersSafely(_listId: string, updates: Array<{ id: string; equipmentIdentifier: string }>) {
safeUpdatePayload = updates;
},
} as never,
});
await service.renumberSection("s1");
assert.deepEqual(safeUpdatePayload, [
{ id: "c2", equipmentIdentifier: "-1F1" },
{ id: "c1", equipmentIdentifier: "-1F3" },
{ id: "c3", equipmentIdentifier: "-1F4" },
]);
});
it("renumber handles gaps and keeps device rows untouched by identifier-only update path", async () => {
let safeCalled = 0;
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1", prefix: "-1F" } as never;
},
} as never,
circuitRepository: {
async listBySection() {
return [
{ id: "c1", sectionId: "s1", equipmentIdentifier: "-1F1", sortOrder: 10, isReserve: 0 },
{ id: "c2", sectionId: "s1", equipmentIdentifier: "-1F5", sortOrder: 20, isReserve: 0 },
{ id: "c3", sectionId: "s1", equipmentIdentifier: "-1F9", sortOrder: 30, isReserve: 0 },
] as never[];
},
async listByCircuitList() {
return [] as never[];
},
async updateEquipmentIdentifiersSafely() {
safeCalled += 1;
},
} as never,
deviceRowRepository: {
async update() {
throw new Error("device rows must not be touched");
},
} as never,
});
await service.renumberSection("s1");
assert.equal(safeCalled, 1);
});
it("moving a device row to another circuit preserves row and toggles reserve flags", async () => {
let transactionalMove:
| {
rows: Array<{ id: string; expectedCircuitId: string }>;
targetCircuitId?: string;
createTargetCircuit?: unknown;
}
| undefined;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional(input: typeof transactionalMove) {
transactionalMove = input;
return {
movedRowIds: input!.rows.map((row) => row.id),
targetCircuitId: input!.targetCircuitId!,
};
},
} as never,
circuitRepository: {
async findById(circuitId: string) {
if (circuitId === "c1") {
return {
id: "c1",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-1F1",
sortOrder: 10,
isReserve: 0,
} as never;
}
return {
id: "c2",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-1F2",
sortOrder: 20,
isReserve: 1,
} as never;
},
} as never,
});
await service.moveDeviceRow("r1", { targetCircuitId: "c2" });
assert.deepEqual(transactionalMove, {
rows: [{ id: "r1", expectedCircuitId: "c1" }],
targetCircuitId: "c2",
createTargetCircuit: undefined,
});
});
it("moving a device row to placeholder creates a new circuit in target section", async () => {
let preparedTarget:
| {
circuitListId: string;
sectionId: string;
equipmentIdentifier: string;
displayName: string;
sortOrder: number;
}
| undefined;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
preparedTarget = input.createTargetCircuit;
return {
movedRowIds: input.rows.map((row) => row.id),
targetCircuitId: "c-new",
createdCircuitId: "c-new",
};
},
} as never,
circuitRepository: {
async findById(circuitId: string) {
if (circuitId === "c1") {
return {
id: "c1",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-1F1",
sortOrder: 10,
isReserve: 0,
} as never;
}
return null as never;
},
async listBySection() {
return [{ sortOrder: 40 }] as never[];
},
} as never,
circuitSectionRepository: {
async findById() {
return { id: "s2", circuitListId: "l1", prefix: "-2F" } as never;
},
} as never,
numberingService: {
async getNextIdentifier() {
return "-2F8";
},
} as never,
});
await service.moveDeviceRow("r1", { targetSectionId: "s2", createNewCircuit: true });
assert.deepEqual(preparedTarget, {
circuitListId: "l1",
sectionId: "s2",
equipmentIdentifier: "-2F8",
displayName: "Neuer Stromkreis",
sortOrder: 50,
});
});
it("moving a device row to its current circuit remains a no-op", async () => {
let transactionalMoveCount = 0;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional() {
transactionalMoveCount += 1;
throw new Error("same-circuit move must not write");
},
} as never,
circuitRepository: {
async findById() {
return {
id: "c1",
sectionId: "s1",
circuitListId: "l1",
equipmentIdentifier: "-1F1",
sortOrder: 10,
isReserve: 0,
} as never;
},
} as never,
});
const result = await service.moveDeviceRow("r1", { targetCircuitId: "c1" });
assert.equal(transactionalMoveCount, 0);
assert.equal(result?.id, "r1");
});
it("moving multiple device rows to a circuit preserves input order and toggles reserve", async () => {
let transactionalMove:
| {
rows: Array<{ id: string; expectedCircuitId: string }>;
targetCircuitId?: string;
createTargetCircuit?: unknown;
}
| undefined;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById(rowId: string) {
if (rowId === "r1") {
return { id: "r1", circuitId: "c1" } as never;
}
return { id: "r2", circuitId: "c2" } as never;
},
moveRowsTransactional(input: typeof transactionalMove) {
transactionalMove = input;
return {
movedRowIds: input!.rows.map((row) => row.id),
targetCircuitId: input!.targetCircuitId!,
};
},
} as never,
circuitRepository: {
async findById(circuitId: string) {
if (circuitId === "c1") {
return { id: "c1", sectionId: "s1", circuitListId: "l1", equipmentIdentifier: "-1F1", sortOrder: 10, isReserve: 0 } as never;
}
if (circuitId === "c2") {
return { id: "c2", sectionId: "s1", circuitListId: "l1", equipmentIdentifier: "-1F2", sortOrder: 20, isReserve: 0 } as never;
}
return { id: "c3", sectionId: "s1", circuitListId: "l1", equipmentIdentifier: "-1F3", sortOrder: 30, isReserve: 1 } as never;
},
} as never,
});
const result = await service.moveDeviceRowsBulk({ rowIds: ["r1", "r2"], targetCircuitId: "c3" });
assert.deepEqual(transactionalMove, {
rows: [
{ id: "r1", expectedCircuitId: "c1" },
{ id: "r2", expectedCircuitId: "c2" },
],
targetCircuitId: "c3",
createTargetCircuit: undefined,
});
assert.deepEqual(result, {
movedRowIds: ["r1", "r2"],
targetCircuitId: "c3",
});
});
it("moving multiple device rows to placeholder creates exactly one new circuit", async () => {
let transactionCount = 0;
let preparedTarget:
| {
circuitListId: string;
sectionId: string;
equipmentIdentifier: string;
displayName: string;
sortOrder: number;
}
| undefined;
const service = new CircuitWriteService({
deviceRowRepository: {
async findById(rowId: string) {
return { id: rowId, circuitId: rowId === "r1" ? "c1" : "c2" } as never;
},
moveRowsTransactional(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
transactionCount += 1;
preparedTarget = input.createTargetCircuit;
return {
movedRowIds: input.rows.map((row) => row.id),
targetCircuitId: "c-new",
createdCircuitId: "c-new",
};
},
} as never,
circuitRepository: {
async findById(circuitId: string) {
if (circuitId === "c1" || circuitId === "c2") {
return { id: circuitId, sectionId: "s1", circuitListId: "l1", equipmentIdentifier: "-1F1", sortOrder: 10, isReserve: 0 } as never;
}
return null as never;
},
async listBySection() {
return [{ sortOrder: 30 }] as never[];
},
} as never,
circuitSectionRepository: {
async findById() {
return { id: "s2", circuitListId: "l1", prefix: "-2F" } as never;
},
} as never,
numberingService: {
async getNextIdentifier() {
return "-2F8";
},
} as never,
});
const result = await service.moveDeviceRowsBulk({
rowIds: ["r1", "r2"],
targetSectionId: "s2",
createNewCircuit: true,
});
assert.equal(transactionCount, 1);
assert.deepEqual(preparedTarget, {
circuitListId: "l1",
sectionId: "s2",
equipmentIdentifier: "-2F8",
displayName: "Neuer Stromkreis",
sortOrder: 40,
});
assert.equal(result.createdCircuitId, "c-new");
});
it("reorders circuits inside one section without renumbering identifiers", async () => {
let safeReorder: { sectionId: string; circuitIds: string[] } | undefined;
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1" } as never;
},
} as never,
circuitRepository: {
async listBySection() {
return [
{ id: "c1", sectionId: "s1", equipmentIdentifier: "-2F7", sortOrder: 10, isReserve: 0 },
{ id: "c2", sectionId: "s1", equipmentIdentifier: "-2F9", sortOrder: 20, isReserve: 0 },
{ id: "c3", sectionId: "s1", equipmentIdentifier: "-2F5", sortOrder: 30, isReserve: 1 },
] as never[];
},
updateSortOrdersSafely(sectionId: string, circuitIds: string[]) {
safeReorder = { sectionId, circuitIds };
},
} as never,
});
await service.reorderCircuitsInSection("s1", { orderedCircuitIds: ["c3", "c1", "c2"] });
assert.deepEqual(safeReorder, {
sectionId: "s1",
circuitIds: ["c3", "c1", "c2"],
});
});
it("safe section identifier bulk update validates full section set (undo safety)", async () => {
let safeCalled = false;
const service = new CircuitWriteService({
circuitSectionRepository: {
async findById() {
return { id: "s1", circuitListId: "l1", prefix: "-1F" } as never;
},
} as never,
circuitRepository: {
async listBySection() {
return [
{ id: "c1", sectionId: "s1", equipmentIdentifier: "-1F1", sortOrder: 10, isReserve: 0 },
{ id: "c2", sectionId: "s1", equipmentIdentifier: "-1F2", sortOrder: 20, isReserve: 0 },
] as never[];
},
async updateEquipmentIdentifiersSafely() {
safeCalled = true;
},
} as never,
});
await service.updateSectionEquipmentIdentifiers("s1", {
identifiers: [
{ circuitId: "c1", equipmentIdentifier: "-1F2" },
{ circuitId: "c2", equipmentIdentifier: "-1F1" },
],
});
assert.equal(safeCalled, true);
});
it("safe identifier bulk update uses synchronous transaction callback", async () => {
const repository = new CircuitRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
return {
from() {
return {
where() {
return {
all() {
return [{ id: "c1" }, { id: "c2" }];
},
};
},
};
},
};
},
update() {
return {
set() {
return {
where() {
return {
run() {
return;
},
};
},
};
},
};
},
};
const result = cb(fakeTx);
callbackReturnedPromise = Boolean(result && typeof (result as Promise<unknown>).then === "function");
if (callbackReturnedPromise) {
throw new Error("Transaction function cannot return a promise");
}
};
try {
await repository.updateEquipmentIdentifiersSafely(
"l1",
[
{ id: "c1", equipmentIdentifier: "-1F1" },
{ id: "c2", equipmentIdentifier: "-1F2" },
],
"s1"
);
assert.equal(callbackReturnedPromise, false);
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("safe circuit reorder uses one synchronous transaction callback", () => {
const repository = new CircuitRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
const sortOrders: number[] = [];
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
return {
from() {
return {
where() {
return {
all() {
return [{ id: "c1" }, { id: "c2" }, { id: "c3" }];
},
};
},
};
},
};
},
update() {
return {
set(values: { sortOrder: number }) {
sortOrders.push(values.sortOrder);
return {
where() {
return {
run() {
return { changes: 1 };
},
};
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
};
try {
repository.updateSortOrdersSafely("s1", ["c3", "c1", "c2"]);
assert.equal(callbackReturnedPromise, false);
assert.deepEqual(sortOrders, [10, 20, 30]);
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("bulk device row move uses one synchronous transaction callback", () => {
const repository = new CircuitDeviceRowRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
let selectCall = 0;
let rowUpdateCount = 0;
const selectedRows = [
[
{ id: "r1", circuitId: "c1" },
{ id: "r2", circuitId: "c2" },
],
[{ id: "c3", circuitListId: "l1" }],
[
{ id: "c1", circuitListId: "l1" },
{ id: "c2", circuitListId: "l1" },
],
[{ id: "existing-target-row", sortOrder: 10 }],
[],
[],
];
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
const rows = selectedRows[selectCall++] ?? [];
const query = {
from() {
return query;
},
where() {
return query;
},
limit() {
return query;
},
all() {
return rows;
},
};
return query;
},
update() {
return {
set() {
return {
where() {
return {
run() {
rowUpdateCount += 1;
return { changes: 1 };
},
};
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
if (callbackReturnedPromise) {
throw new Error("Transaction function cannot return a promise");
}
};
try {
const result = repository.moveRowsTransactional({
rows: [
{ id: "r1", expectedCircuitId: "c1" },
{ id: "r2", expectedCircuitId: "c2" },
],
targetCircuitId: "c3",
});
assert.equal(callbackReturnedPromise, false);
assert.equal(rowUpdateCount, 5);
assert.deepEqual(result, {
movedRowIds: ["r1", "r2"],
targetCircuitId: "c3",
createdCircuitId: undefined,
});
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("device row creation and reserve activation share one synchronous transaction", () => {
const repository = new CircuitDeviceRowRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
let insertedValues: { id?: string; circuitId?: string; sortOrder?: number } | undefined;
let reserveValue: number | undefined;
let selectCall = 0;
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
const rows = selectCall++ === 0 ? [{ id: "c1" }] : [{ sortOrder: 20 }];
const query = {
from() {
return query;
},
where() {
return query;
},
limit() {
return query;
},
all() {
return rows;
},
};
return query;
},
insert() {
return {
values(values: typeof insertedValues) {
insertedValues = values;
return {
run() {
return { changes: 1 };
},
};
},
};
},
update() {
return {
set(values: { isReserve: number }) {
reserveValue = values.isReserve;
return {
where() {
return {
run() {
return { changes: 1 };
},
};
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
};
try {
const rowId = repository.createInCircuitTransactional({
circuitId: "c1",
name: "Load",
displayName: "Load",
quantity: 1,
powerPerUnit: 1,
simultaneityFactor: 1,
});
assert.equal(callbackReturnedPromise, false);
assert.equal(insertedValues?.id, rowId);
assert.equal(insertedValues?.circuitId, "c1");
assert.equal(insertedValues?.sortOrder, 30);
assert.equal(reserveValue, 0);
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("device row deletion and reserve update share one synchronous transaction", () => {
const repository = new CircuitDeviceRowRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
let deleteCount = 0;
let reserveValue: number | undefined;
let selectCall = 0;
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
const rows = selectCall++ === 0 ? [{ id: "r1", circuitId: "c1" }] : [];
const query = {
from() {
return query;
},
where() {
return query;
},
limit() {
return query;
},
all() {
return rows;
},
};
return query;
},
delete() {
return {
where() {
return {
run() {
deleteCount += 1;
return { changes: 1 };
},
};
},
};
},
update() {
return {
set(values: { isReserve: number }) {
reserveValue = values.isReserve;
return {
where() {
return {
run() {
return { changes: 1 };
},
};
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
};
try {
repository.deleteFromCircuitTransactional("r1", "c1");
assert.equal(callbackReturnedPromise, false);
assert.equal(deleteCount, 1);
assert.equal(reserveValue, 1);
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("new circuit and initial device rows share one synchronous transaction", () => {
const repository = new CircuitDeviceRowRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
const insertedValues: Array<Record<string, unknown>> = [];
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
insert() {
return {
values(values: Record<string, unknown>) {
insertedValues.push(values);
return {
run() {
return { changes: 1 };
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
};
try {
const created = repository.createCircuitWithDeviceRowsTransactional({
circuit: {
circuitListId: "l1",
sectionId: "s1",
equipmentIdentifier: "-2F1",
displayName: "Steckdosen",
sortOrder: 10,
},
deviceRows: [
{
name: "Socket",
displayName: "Steckdose",
quantity: 1,
powerPerUnit: 0.2,
simultaneityFactor: 1,
sortOrder: 15,
},
{
name: "Manual",
displayName: "Manuell",
quantity: 2,
powerPerUnit: 0.1,
simultaneityFactor: 0.8,
},
],
});
assert.equal(callbackReturnedPromise, false);
assert.equal(insertedValues.length, 3);
assert.equal(insertedValues[0].id, created.circuitId);
assert.equal(insertedValues[0].isReserve, 0);
assert.equal(insertedValues[1].id, created.rowIds[0]);
assert.equal(insertedValues[1].circuitId, created.circuitId);
assert.equal(insertedValues[1].sortOrder, 15);
assert.equal(insertedValues[2].id, created.rowIds[1]);
assert.equal(insertedValues[2].circuitId, created.circuitId);
assert.equal(insertedValues[2].sortOrder, 20);
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("tracks local edits on linked device rows as overridden fields", async () => {
let savedOverrides: string | undefined;
const current = {
id: "r1",
circuitId: "c1",
linkedProjectDeviceId: "pd1",
name: "Luminaire",
displayName: "Local name",
quantity: 1,
powerPerUnit: 0.1,
simultaneityFactor: 1,
sortOrder: 10,
overriddenFields: null,
};
const service = new CircuitWriteService({
deviceRowRepository: {
async findById() {
return current as never;
},
async update(_rowId: string, input: { overriddenFields?: string }) {
savedOverrides = input.overriddenFields;
},
} as never,
});
await service.updateDeviceRow("r1", { quantity: 2 });
assert.deepEqual(JSON.parse(savedOverrides ?? "[]"), ["quantity"]);
});
});