Persist circuit protection updates

This commit is contained in:
2026-07-31 07:37:38 +02:00
parent 0c92fbd09e
commit 18ca5eb3d4
12 changed files with 501 additions and 2 deletions
+7
View File
@@ -371,6 +371,13 @@ Das Bearbeiten ändert ausschließlich den Anzeigenamen. Nur vollständig leere
Gruppen können über `circuit-group.delete` entfernt werden; für befüllte
Gruppen bleibt die Aktion bis zum gesonderten Warn- und Unterbaumdialog
gesperrt.
`circuit-protection.update` bildet die eigene persistente Schreibgrenze für
die neue 1:1-Stromkreisschutztabelle. Der Command vergleicht den vollständigen
erwarteten Datensatz, validiert Typ und abhängige Felder, prüft die
Projektzugehörigkeit und schreibt Schutzgerät, Revision und Historienübergang
atomar. Benutzer können Schutzdaten anlegen oder ändern, aber nicht entfernen;
Undo einer erstmaligen Anlage darf den zuvor fehlenden Datensatz exakt
wiederherstellen.
`distribution-board.update` versioniert Etage, Netzart und den
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
+2 -1
View File
@@ -36,7 +36,8 @@ requirements and intended sequencing, not proof of implementation.
- [x] Phase E2b: render the structure projection in the editor grid.
- [x] Phase E3a: create, edit and delete mutable group and footer components.
- [x] Phase E3b1: create, rename and safely delete empty circuit groups.
- [ ] Phase E3b2: circuit-protection editing.
- [x] Phase E3b2a: persistent circuit-protection update command.
- [ ] Phase E3b2b: circuit-protection defaults on insert and editor modal.
- [ ] Phase E4: group reorder, renumber, circuit moves and populated-delete warning.
- [ ] Phase E: editor projection and editing.
- [ ] Phase F: documentation and full GUI verification.
@@ -742,7 +742,9 @@ Acceptance:
Status: In progress. The complete tree read model E1, pure structural
projection E2a, grid rendering E2b and mutable component editing E3a are
complete. Basic group management E3b1 is also complete. Circuit-protection
editing and structural drag/renumber/delete workflows remain pending.
editing now has its persistent E3b2a command boundary; insertion defaults and
the editor modal as well as structural drag/renumber/delete workflows remain
pending.
- render fixed header components
- render group components and circuit blocks
@@ -777,6 +779,10 @@ Implemented in E1/E2a/E2b/E3a:
child BMKs remain stable
- an exactly empty group can be removed through the persistent group command;
populated deletion stays disabled until the dedicated warning UI is present
- `circuit-protection.update` inserts or updates one validated 1:1 protection
snapshot, rejects stale state and preserves exact persistent Undo/Redo
- user commands cannot remove circuit protection; a nullable target exists
only as the inverse of adding protection to a retained circuit without it
Acceptance:
@@ -0,0 +1,97 @@
import { and, eq } from "drizzle-orm";
import {
assertCircuitProtectionUpdateProjectCommand,
createCircuitProtectionUpdateProjectCommand,
type CircuitProtectionSnapshot,
} from "../../domain/models/circuit-protection-project-command.model.js";
import type {
CircuitProtectionProjectCommandStore,
ExecuteCircuitProtectionCommandInput,
} from "../../domain/ports/circuit-protection-project-command.store.js";
import type { AppDatabase } from "../database-context.js";
import { circuitLists } from "../schema/circuit-lists.js";
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
import { circuits } from "../schema/circuits.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
export class CircuitProtectionProjectCommandRepository
implements CircuitProtectionProjectCommandStore
{
constructor(private readonly database: AppDatabase) {}
execute(input: ExecuteCircuitProtectionCommandInput) {
return executeProjectCommandTransaction(this.database, input, (tx) => {
assertCircuitProtectionUpdateProjectCommand(input.command);
const { circuitId, expected, target } = input.command.payload;
if (input.source === "user" && target === null) {
throw new Error("User commands cannot remove circuit protection.");
}
this.assertOwnership(tx, input.projectId, circuitId);
const actual = tx
.select()
.from(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.get();
if (!sameNullableSnapshot(expected, actual)) {
throw new Error("Circuit protection changed before update.");
}
if (target === null) {
tx.delete(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.run();
} else if (actual) {
tx.update(circuitProtectionDevices)
.set(target)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.run();
} else {
tx.insert(circuitProtectionDevices).values(target).run();
}
return createCircuitProtectionUpdateProjectCommand(
circuitId,
target,
expected
);
});
}
private assertOwnership(
database: AppDatabase,
projectId: string,
circuitId: string
) {
const circuit = database
.select({ projectId: circuitLists.projectId })
.from(circuits)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuits.circuitListId)
)
.where(
and(
eq(circuits.id, circuitId),
eq(circuitLists.projectId, projectId)
)
)
.get();
if (!circuit) {
throw new Error("Circuit protection does not belong to project.");
}
}
}
function sameNullableSnapshot(
expected: CircuitProtectionSnapshot | null,
actual: typeof circuitProtectionDevices.$inferSelect | undefined
) {
if (expected === null) {
return actual === undefined;
}
if (!actual) {
return false;
}
return Object.entries(expected).every(
([key, value]) =>
(actual as Record<string, unknown>)[key] === value
);
}
@@ -0,0 +1,108 @@
import { protectionDeviceConfigurationSchema } from "../../shared/validation/protection-device.schemas.js";
import type {
BreakerTripCharacteristic,
FuseUtilizationCategory,
ProtectionDeviceType,
RcdType,
} from "../../shared/constants/protection-device.js";
import type { SerializedProjectCommand } from "./project-command.model.js";
export const circuitProtectionUpdateCommandType =
"circuit-protection.update" as const;
export const circuitProtectionUpdateCommandSchemaVersion = 1 as const;
export interface CircuitProtectionSnapshot {
circuitId: string;
type: ProtectionDeviceType;
ratedCurrentA: number;
fuseUtilizationCategory: FuseUtilizationCategory | null;
tripCharacteristic: BreakerTripCharacteristic | null;
rcdType: RcdType | null;
ratedResidualCurrentMa: number | null;
}
interface CircuitProtectionUpdatePayload {
circuitId: string;
expected: CircuitProtectionSnapshot | null;
target: CircuitProtectionSnapshot | null;
}
export interface CircuitProtectionUpdateProjectCommand
extends SerializedProjectCommand<CircuitProtectionUpdatePayload> {
schemaVersion: typeof circuitProtectionUpdateCommandSchemaVersion;
type: typeof circuitProtectionUpdateCommandType;
}
export function createCircuitProtectionUpdateProjectCommand(
circuitId: string,
expected: CircuitProtectionSnapshot | null,
target: CircuitProtectionSnapshot | null
): CircuitProtectionUpdateProjectCommand {
const command: CircuitProtectionUpdateProjectCommand = {
schemaVersion: circuitProtectionUpdateCommandSchemaVersion,
type: circuitProtectionUpdateCommandType,
payload: { circuitId, expected, target },
};
assertCircuitProtectionUpdateProjectCommand(command);
return command;
}
export function assertCircuitProtectionUpdateProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is CircuitProtectionUpdateProjectCommand {
if (
command.schemaVersion !== circuitProtectionUpdateCommandSchemaVersion ||
command.type !== circuitProtectionUpdateCommandType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 3
) {
throw new Error("Unsupported circuit-protection update command.");
}
const { circuitId, expected, target } = command.payload;
if (typeof circuitId !== "string" || !circuitId.trim()) {
throw new Error("Circuit protection requires a circuit id.");
}
if (expected === null && target === null) {
throw new Error("Circuit protection update must change state.");
}
if (expected !== null) {
assertSnapshot(expected, circuitId);
}
if (target !== null) {
assertSnapshot(target, circuitId);
}
if (JSON.stringify(expected) === JSON.stringify(target)) {
throw new Error("Circuit protection update must change state.");
}
}
function assertSnapshot(value: unknown, circuitId: string) {
if (
!isPlainObject(value) ||
Object.keys(value).length !== 7 ||
value.circuitId !== circuitId
) {
throw new Error("Circuit-protection snapshot is invalid.");
}
const result = protectionDeviceConfigurationSchema.safeParse({
type: value.type,
ratedCurrentA: value.ratedCurrentA,
...(value.fuseUtilizationCategory === null
? {}
: { fuseUtilizationCategory: value.fuseUtilizationCategory }),
...(value.tripCharacteristic === null
? {}
: { tripCharacteristic: value.tripCharacteristic }),
...(value.rcdType === null ? {} : { rcdType: value.rcdType }),
...(value.ratedResidualCurrentMa === null
? {}
: { ratedResidualCurrentMa: value.ratedResidualCurrentMa }),
});
if (!result.success) {
throw new Error("Circuit-protection configuration is invalid.");
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
@@ -0,0 +1,24 @@
import type {
CircuitProtectionUpdateProjectCommand,
} from "../models/circuit-protection-project-command.model.js";
import type {
AppendedProjectRevision,
ProjectRevisionSource,
} from "./project-revision.store.js";
export interface ExecuteCircuitProtectionCommandInput {
projectId: string;
expectedRevision: number;
source: ProjectRevisionSource;
description?: string;
actorId?: string;
historyTargetChangeSetId?: string;
command: CircuitProtectionUpdateProjectCommand;
}
export interface CircuitProtectionProjectCommandStore {
execute(input: ExecuteCircuitProtectionCommandInput): {
revision: AppendedProjectRevision;
inverse: CircuitProtectionUpdateProjectCommand;
};
}
@@ -19,6 +19,10 @@ import {
assertCircuitUpdateProjectCommand,
circuitUpdateCommandType,
} from "../models/circuit-project-command.model.js";
import {
assertCircuitProtectionUpdateProjectCommand,
circuitProtectionUpdateCommandType,
} from "../models/circuit-protection-project-command.model.js";
import {
assertCircuitSectionReorderProjectCommand,
circuitSectionReorderCommandType,
@@ -95,6 +99,7 @@ import type { CircuitDeviceRowProjectCommandStore } from "../ports/circuit-devic
import type { CircuitDeviceRowMoveProjectCommandStore } from "../ports/circuit-device-row-move-project-command.store.js";
import type { CircuitDeviceRowStructureProjectCommandStore } from "../ports/circuit-device-row-structure-project-command.store.js";
import type { CircuitProjectCommandStore } from "../ports/circuit-project-command.store.js";
import type { CircuitProtectionProjectCommandStore } from "../ports/circuit-protection-project-command.store.js";
import type { CircuitSectionReorderProjectCommandStore } from "../ports/circuit-section-reorder-project-command.store.js";
import type { CircuitSectionRenumberProjectCommandStore } from "../ports/circuit-section-renumber-project-command.store.js";
import type { CircuitStructureProjectCommandStore } from "../ports/circuit-structure-project-command.store.js";
@@ -177,6 +182,7 @@ export class ProjectCommandService implements ProjectCommandExecutor {
private readonly circuitGroupRenumberStore: CircuitGroupRenumberProjectCommandStore,
private readonly circuitGroupMoveStore: CircuitGroupMoveProjectCommandStore,
private readonly circuitGroupSubtreeStore: CircuitGroupSubtreeProjectCommandStore,
private readonly circuitProtectionStore: CircuitProtectionProjectCommandStore,
private readonly historyStore: ProjectHistoryStore
) {}
@@ -421,6 +427,13 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case circuitProtectionUpdateCommandType: {
assertCircuitProtectionUpdateProjectCommand(input.command);
return this.circuitProtectionStore.execute({
...input,
command: input.command,
}).revision;
}
case projectFloorInsertCommandType: {
assertProjectFloorInsertProjectCommand(input.command);
return this.projectLocationStructureStore.execute({
@@ -6,6 +6,7 @@ import { CircuitProjectCommandRepository } from "../../db/repositories/circuit-p
import { CircuitSectionReorderProjectCommandRepository } from "../../db/repositories/circuit-section-reorder-project-command.repository.js";
import { CircuitSectionRenumberProjectCommandRepository } from "../../db/repositories/circuit-section-renumber-project-command.repository.js";
import { CircuitStructureProjectCommandRepository } from "../../db/repositories/circuit-structure-project-command.repository.js";
import { CircuitProtectionProjectCommandRepository } from "../../db/repositories/circuit-protection-project-command.repository.js";
import { CircuitGroupStructureProjectCommandRepository } from "../../db/repositories/circuit-group-structure-project-command.repository.js";
import { CircuitGroupRenumberProjectCommandRepository } from "../../db/repositories/circuit-group-renumber-project-command.repository.js";
import { CircuitGroupMoveProjectCommandRepository } from "../../db/repositories/circuit-group-move-project-command.repository.js";
@@ -30,6 +31,8 @@ export const circuitDeviceRowMoveProjectCommandStore =
new CircuitDeviceRowMoveProjectCommandRepository(db);
export const circuitStructureProjectCommandStore =
new CircuitStructureProjectCommandRepository(db);
export const circuitProtectionProjectCommandStore =
new CircuitProtectionProjectCommandRepository(db);
export const circuitGroupStructureProjectCommandStore =
new CircuitGroupStructureProjectCommandRepository(db);
export const circuitGroupRenumberProjectCommandStore =
@@ -79,5 +82,6 @@ export const projectCommandService = new ProjectCommandService(
circuitGroupRenumberProjectCommandStore,
circuitGroupMoveProjectCommandStore,
circuitGroupSubtreeProjectCommandStore,
circuitProtectionProjectCommandStore,
projectHistoryStore
);
@@ -0,0 +1,191 @@
import path from "node:path";
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { eq } from "drizzle-orm";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import {
createDatabaseContext,
type DatabaseContext,
} from "../src/db/database-context.js";
import { CircuitProtectionProjectCommandRepository } from "../src/db/repositories/circuit-protection-project-command.repository.js";
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js";
import { circuitLists } from "../src/db/schema/circuit-lists.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
import { projects } from "../src/db/schema/projects.js";
import {
createCircuitProtectionUpdateProjectCommand,
type CircuitProtectionSnapshot,
} from "../src/domain/models/circuit-protection-project-command.model.js";
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
function createTestDatabase(): DatabaseContext {
const context = createDatabaseContext(":memory:");
migrate(context.db, {
migrationsFolder: path.resolve("src", "db", "migrations"),
});
context.db
.insert(projects)
.values([
{ id: "project-1", name: "Projekt" },
{ id: "project-2", name: "Fremdprojekt" },
])
.run();
new DistributionBoardFixtureRepository(
context.db
).createWithCircuitListAndDefaultSections("project-1", "UV-01");
const list = context.db.select().from(circuitLists).get();
assert.ok(list);
const section = context.db
.select()
.from(circuitSections)
.where(eq(circuitSections.circuitListId, list.id))
.get();
assert.ok(section);
context.db
.insert(circuits)
.values({
id: "circuit-1",
circuitListId: list.id,
sectionId: section.id,
equipmentIdentifier: "-1F1.1",
sortOrder: 10,
})
.run();
return context;
}
const protection: CircuitProtectionSnapshot = {
circuitId: "circuit-1",
type: "LS",
ratedCurrentA: 10,
fuseUtilizationCategory: null,
tripCharacteristic: "B",
rcdType: null,
ratedResidualCurrentMa: null,
};
describe("circuit-protection project command", () => {
it("inserts, updates and restores protection through exact inverse commands", () => {
const context = createTestDatabase();
try {
const repository = new CircuitProtectionProjectCommandRepository(
context.db
);
const history = new ProjectHistoryRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
null,
protection
),
});
assert.deepEqual(
context.db.select().from(circuitProtectionDevices).get(),
protection
);
const target = { ...protection, ratedCurrentA: 16 };
const updated = repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
protection,
target
),
});
assert.equal(
context.db.select().from(circuitProtectionDevices).get()
?.ratedCurrentA,
16
);
const updateUndo = history.getNextCommand("project-1", "undo");
assert.ok(updateUndo);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "undo",
historyTargetChangeSetId: updateUndo.changeSetId,
command: updated.inverse,
});
assert.deepEqual(
context.db.select().from(circuitProtectionDevices).get(),
protection
);
const insertUndo = history.getNextCommand("project-1", "undo");
assert.ok(insertUndo);
repository.execute({
projectId: "project-1",
expectedRevision: 3,
source: "undo",
historyTargetChangeSetId: insertUndo.changeSetId,
command: inserted.inverse,
});
assert.equal(
context.db.select().from(circuitProtectionDevices).get(),
undefined
);
} finally {
context.close();
}
});
it("rejects stale state, foreign ownership and user removal", () => {
const context = createTestDatabase();
try {
const repository = new CircuitProtectionProjectCommandRepository(
context.db
);
assert.throws(
() =>
repository.execute({
projectId: "project-2",
expectedRevision: 0,
source: "user",
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
null,
protection
),
}),
/does not belong/
);
context.db.insert(circuitProtectionDevices).values(protection).run();
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
null,
protection
),
}),
/changed before/
);
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
protection,
null
),
}),
/cannot remove/
);
} finally {
context.close();
}
});
});
+45
View File
@@ -14,6 +14,7 @@ import { CircuitProjectCommandRepository } from "../src/db/repositories/circuit-
import { CircuitSectionReorderProjectCommandRepository } from "../src/db/repositories/circuit-section-reorder-project-command.repository.js";
import { CircuitSectionRenumberProjectCommandRepository } from "../src/db/repositories/circuit-section-renumber-project-command.repository.js";
import { CircuitStructureProjectCommandRepository } from "../src/db/repositories/circuit-structure-project-command.repository.js";
import { CircuitProtectionProjectCommandRepository } from "../src/db/repositories/circuit-protection-project-command.repository.js";
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
@@ -29,6 +30,7 @@ import { ProjectDeviceStructureProjectCommandRepository } from "../src/db/reposi
import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js";
import { ProjectSettingsProjectCommandRepository } from "../src/db/repositories/project-settings-project-command.repository.js";
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
import { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
@@ -38,6 +40,7 @@ import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
import { floors } from "../src/db/schema/floors.js";
import { rooms } from "../src/db/schema/rooms.js";
import { createCircuitProtectionUpdateProjectCommand } from "../src/domain/models/circuit-protection-project-command.model.js";
import { ProjectHistoryOperationUnavailableError } from "../src/domain/errors/project-history-operation-unavailable.error.js";
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
import { createCircuitDeviceRowUpdateProjectCommand } from "../src/domain/models/circuit-device-row-project-command.model.js";
@@ -149,6 +152,7 @@ function createService(context: DatabaseContext) {
new CircuitGroupRenumberProjectCommandRepository(context.db),
new CircuitGroupMoveProjectCommandRepository(context.db),
new CircuitGroupSubtreeProjectCommandRepository(context.db),
new CircuitProtectionProjectCommandRepository(context.db),
new ProjectHistoryRepository(context.db)
);
}
@@ -170,6 +174,47 @@ function getRowQuantity(context: DatabaseContext) {
}
describe("project command service", () => {
it("dispatches circuit-protection updates through project history", () => {
const context = createTestDatabase();
try {
const service = createService(context);
const protection = {
circuitId: "circuit-1",
type: "LS" as const,
ratedCurrentA: 10,
fuseUtilizationCategory: null,
tripCharacteristic: "B" as const,
rcdType: null,
ratedResidualCurrentMa: null,
};
service.executeUser({
projectId: "project-1",
expectedRevision: 0,
command: createCircuitProtectionUpdateProjectCommand(
"circuit-1",
null,
protection
),
});
assert.deepEqual(
context.db.select().from(circuitProtectionDevices).get(),
protection
);
service.undo({ projectId: "project-1", expectedRevision: 1 });
assert.equal(
context.db.select().from(circuitProtectionDevices).get(),
undefined
);
service.redo({ projectId: "project-1", expectedRevision: 2 });
assert.deepEqual(
context.db.select().from(circuitProtectionDevices).get(),
protection
);
} finally {
context.close();
}
});
it("dispatches supported commands and persists undo/redo across service instances", () => {
const context = createTestDatabase();
try {
@@ -14,6 +14,7 @@ import { CircuitProjectCommandRepository } from "../src/db/repositories/circuit-
import { CircuitSectionRenumberProjectCommandRepository } from "../src/db/repositories/circuit-section-renumber-project-command.repository.js";
import { CircuitSectionReorderProjectCommandRepository } from "../src/db/repositories/circuit-section-reorder-project-command.repository.js";
import { CircuitStructureProjectCommandRepository } from "../src/db/repositories/circuit-structure-project-command.repository.js";
import { CircuitProtectionProjectCommandRepository } from "../src/db/repositories/circuit-protection-project-command.repository.js";
import { CircuitGroupStructureProjectCommandRepository } from "../src/db/repositories/circuit-group-structure-project-command.repository.js";
import { CircuitGroupRenumberProjectCommandRepository } from "../src/db/repositories/circuit-group-renumber-project-command.repository.js";
import { CircuitGroupMoveProjectCommandRepository } from "../src/db/repositories/circuit-group-move-project-command.repository.js";
@@ -203,6 +204,7 @@ function createService(context: DatabaseContext) {
new CircuitGroupRenumberProjectCommandRepository(context.db),
new CircuitGroupMoveProjectCommandRepository(context.db),
new CircuitGroupSubtreeProjectCommandRepository(context.db),
new CircuitProtectionProjectCommandRepository(context.db),
new ProjectHistoryRepository(context.db)
);
}
+1
View File
@@ -13,6 +13,7 @@ import {
createDefaultGroupRcd,
} from "../src/domain/services/protection-device-defaults.js";
import { protectionDeviceConfigurationSchema } from "../src/shared/validation/protection-device.schemas.js";
import "./circuit-protection-project-command.repository.test.js";
describe("protection device catalog", () => {
it("contains exactly the agreed protection device types", () => {