Undo Redo working

This commit is contained in:
2026-05-05 09:55:08 +02:00
parent 75435475fc
commit 9b9e67bf0c
9 changed files with 938 additions and 213 deletions
+168 -7
View File
@@ -1,6 +1,8 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { CircuitWriteService } from "../src/domain/services/circuit-write.service.js";
import { CircuitRepository } from "../src/db/repositories/circuit.repository.js";
import { db } from "../src/db/client.js";
describe("circuit write service rules", () => {
it("rejects duplicate equipment identifiers in same circuit list", async () => {
@@ -146,8 +148,8 @@ describe("circuit write service rules", () => {
assert.equal(reserveFlag, false);
});
it("renumber affects only circuits in selected section and keeps row order untouched", async () => {
const updatedIds: string[] = [];
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() {
@@ -157,24 +159,94 @@ describe("circuit write service rules", () => {
circuitRepository: {
async listBySection() {
return [
{ id: "c1", sectionId: "s1", equipmentIdentifier: "-2F7", sortOrder: 10, isReserve: 0 },
{ id: "c2", sectionId: "s1", equipmentIdentifier: "-2F9", sortOrder: 20, isReserve: 1 },
{ 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 update(circuitId: string) {
updatedIds.push(circuitId);
async updateEquipmentIdentifiersSafely(_listId: string, updates: Array<{ id: string; equipmentIdentifier: string }>) {
safeUpdatePayload = updates;
},
} as never,
});
const result = await service.renumberSection("s1");
assert.deepEqual(updatedIds, ["c1", "c2"]);
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 () => {
const updatedReserve: Array<{ id: string; isReserve: boolean }> = [];
const movedCalls: Array<{ rowId: string; targetCircuitId: string; sortOrder: number }> = [];
@@ -330,4 +402,93 @@ describe("circuit write service rules", () => {
{ id: "c2", sortOrder: 30, equipmentIdentifier: "-2F9" },
]);
});
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;
}
});
});