Add component update commands

This commit is contained in:
2026-07-30 19:42:09 +02:00
parent 604604f056
commit 69b020339e
11 changed files with 547 additions and 21 deletions
@@ -17,6 +17,7 @@ import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
import {
createDistributionBoardComponentInsertProjectCommand,
createDistributionBoardComponentUpdateProjectCommand,
type DistributionBoardComponentSnapshot,
} from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
@@ -198,6 +199,272 @@ describe("distribution-board component structure project command", () => {
}
});
it("updates an auxiliary component and restores its name, BMK and order", () => {
const context = createTestDatabase();
try {
const { list } = getListAndSection(context);
const expected: DistributionBoardComponentSnapshot = {
component: {
id: "component-update-aux",
circuitListId: list.id,
sectionId: null,
equipmentIdentifier: "-K20",
name: "KNX Aktor",
role: "auxiliary",
placement: "footer",
sortOrder: 10,
},
protectionDevice: null,
};
const target: DistributionBoardComponentSnapshot = {
component: {
...expected.component,
equipmentIdentifier: "-K21",
name: "KNX Jalousieaktor",
sortOrder: 30,
},
protectionDevice: null,
};
const repository =
new DistributionBoardComponentStructureProjectCommandRepository(
context.db
);
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command:
createDistributionBoardComponentInsertProjectCommand(
expected
),
});
const updated = repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command:
createDistributionBoardComponentUpdateProjectCommand(
expected,
target
),
});
assert.deepEqual(
context.db
.select()
.from(distributionBoardComponents)
.where(eq(distributionBoardComponents.id, expected.component.id))
.get(),
target.component
);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "undo",
historyTargetChangeSetId: new ProjectHistoryRepository(
context.db
).getNextCommand("project-1", "undo")?.changeSetId,
command: updated.inverse,
});
assert.deepEqual(
context.db
.select()
.from(distributionBoardComponents)
.where(eq(distributionBoardComponents.id, expected.component.id))
.get(),
expected.component
);
} finally {
context.close();
}
});
it("updates group protection data atomically and rejects stale state", () => {
const context = createTestDatabase();
try {
const { list, section } = getListAndSection(context);
const expected: DistributionBoardComponentSnapshot = {
component: {
id: "component-update-protection",
circuitListId: list.id,
sectionId: section.id,
equipmentIdentifier: "-1Q1.0",
name: "Gruppen-FI",
role: "group_residual_current_protection",
placement: "group",
sortOrder: 10,
},
protectionDevice: {
componentId: "component-update-protection",
type: "FI",
ratedCurrentA: 40,
fuseUtilizationCategory: null,
tripCharacteristic: null,
rcdType: "A",
ratedResidualCurrentMa: 30,
},
};
const target: DistributionBoardComponentSnapshot = {
component: {
...expected.component,
name: "Gruppen-FI 63 A",
},
protectionDevice: {
...expected.protectionDevice!,
ratedCurrentA: 63,
},
};
const repository =
new DistributionBoardComponentStructureProjectCommandRepository(
context.db
);
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command:
createDistributionBoardComponentInsertProjectCommand(
expected
),
});
repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command:
createDistributionBoardComponentUpdateProjectCommand(
expected,
target
),
});
assert.deepEqual(
context.db
.select()
.from(distributionBoardComponentProtectionDevices)
.where(
eq(
distributionBoardComponentProtectionDevices.componentId,
expected.component.id
)
)
.get(),
target.protectionDevice
);
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "user",
command:
createDistributionBoardComponentUpdateProjectCommand(
expected,
target
),
}),
/changed before update/
);
assert.equal(
context.db.select().from(projectRevisions).all().length,
2
);
assert.throws(
() =>
createDistributionBoardComponentUpdateProjectCommand(
target,
{
...target,
component: {
...target.component,
sectionId: "another-section",
},
}
),
/cannot change ownership or role/
);
} finally {
context.close();
}
});
it("rolls back a component update when history persistence fails", () => {
const context = createTestDatabase();
try {
const { list } = getListAndSection(context);
const expected: DistributionBoardComponentSnapshot = {
component: {
id: "component-update-rollback",
circuitListId: list.id,
sectionId: null,
equipmentIdentifier: "-K30",
name: "Aktor",
role: "auxiliary",
placement: "footer",
sortOrder: 10,
},
protectionDevice: null,
};
const target: DistributionBoardComponentSnapshot = {
component: {
...expected.component,
name: "Geänderter Aktor",
sortOrder: 20,
},
protectionDevice: null,
};
const repository =
new DistributionBoardComponentStructureProjectCommandRepository(
context.db
);
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command:
createDistributionBoardComponentInsertProjectCommand(
expected
),
});
context.sqlite.exec(`
CREATE TRIGGER fail_component_update_history
BEFORE INSERT ON project_history_stack_entries
BEGIN
SELECT RAISE(ABORT, 'forced component update history failure');
END;
`);
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command:
createDistributionBoardComponentUpdateProjectCommand(
expected,
target
),
}),
/forced component update history failure/
);
assert.deepEqual(
context.db
.select()
.from(distributionBoardComponents)
.where(eq(distributionBoardComponents.id, expected.component.id))
.get(),
expected.component
);
assert.equal(
context.db.select().from(projectRevisions).all().length,
1
);
} finally {
context.close();
}
});
it("rejects fixed roles, foreign sections, duplicate BMKs and stale revisions", () => {
const context = createTestDatabase();
try {
+87 -1
View File
@@ -51,7 +51,10 @@ import {
createDistributionBoardInsertProjectCommand,
createDistributionBoardStructureSnapshot,
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
import { createDistributionBoardComponentInsertProjectCommand } from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
import {
createDistributionBoardComponentInsertProjectCommand,
createDistributionBoardComponentUpdateProjectCommand,
} from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
import {
createProjectFloorInsertProjectCommand,
createProjectFloorSnapshot,
@@ -1121,6 +1124,89 @@ describe("project command service", () => {
}
});
it("dispatches distribution-board component updates", () => {
const context = createTestDatabase();
try {
const service = createService(context);
const circuitListId = context.db
.select()
.from(circuitSections)
.get()!.circuitListId;
const expected = {
component: {
id: "component-service-update",
circuitListId,
sectionId: null,
equipmentIdentifier: "-K11",
name: "KNX Aktor",
role: "auxiliary" as const,
placement: "footer" as const,
sortOrder: 10,
},
protectionDevice: null,
};
const target = {
component: {
...expected.component,
name: "KNX Jalousieaktor",
sortOrder: 20,
},
protectionDevice: null,
};
service.executeUser({
projectId: "project-1",
expectedRevision: 0,
command:
createDistributionBoardComponentInsertProjectCommand(
expected
),
});
const updated = service.executeUser({
projectId: "project-1",
expectedRevision: 1,
command:
createDistributionBoardComponentUpdateProjectCommand(
expected,
target
),
});
assert.equal(updated.history.currentRevision, 2);
assert.equal(
context.db
.select()
.from(distributionBoardComponents)
.where(
eq(
distributionBoardComponents.id,
expected.component.id
)
)
.get()?.name,
target.component.name
);
createService(context).undo({
projectId: "project-1",
expectedRevision: 2,
});
assert.equal(
context.db
.select()
.from(distributionBoardComponents)
.where(
eq(
distributionBoardComponents.id,
expected.component.id
)
)
.get()?.name,
expected.component.name
);
} finally {
context.close();
}
});
it("dispatches floor and room setup with their persisted inverses", () => {
const context = createTestDatabase();
try {
+8
View File
@@ -131,6 +131,14 @@ describe("project version history presentation", () => {
),
"Verteilergerät entfernt"
);
assert.equal(
getProjectRevisionDescription(
revision(7, {
commandType: "distribution-board-component.update",
})
),
"Verteilergerät bearbeitet"
);
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
assert.equal(
getProjectSnapshotKindLabel("automatic"),