Create clean release database baseline

This commit is contained in:
2026-07-31 14:07:26 +02:00
parent 5bee3cb103
commit 734a2bcfc4
129 changed files with 2121 additions and 30607 deletions
+40 -355
View File
@@ -42,17 +42,6 @@ function minimalSnapshot() {
};
}
function legacyMinimalSnapshot() {
const {
distributionBoardComponents: _distributionBoardComponents,
circuitProtectionDevices: _circuitProtectionDevices,
distributionBoardComponentProtectionDevices:
_distributionBoardComponentProtectionDevices,
...snapshot
} = minimalSnapshot();
return snapshot;
}
function protectedSnapshot() {
return {
...minimalSnapshot(),
@@ -94,9 +83,6 @@ function protectedSnapshot() {
equipmentIdentifier: "-1F1.1",
displayName: "Reserve",
sortOrder: 10,
protectionType: null,
protectionRatedCurrent: null,
protectionCharacteristic: null,
cableType: null,
cableCrossSection: null,
cableLength: null,
@@ -148,234 +134,26 @@ function protectedSnapshot() {
}
describe("project state snapshot model", () => {
it("round-trips a versioned logical project state", () => {
it("round-trips the current logical project state", () => {
const snapshot = parseProjectStateSnapshot(minimalSnapshot());
assert.deepEqual(
deserializeProjectStateSnapshot(
serializeProjectStateSnapshot(snapshot)
),
deserializeProjectStateSnapshot(serializeProjectStateSnapshot(snapshot)),
snapshot
);
});
it("upgrades version-one snapshots with empty project metadata", () => {
const legacy = {
...legacyMinimalSnapshot(),
schemaVersion: 1 as const,
project: {
id: "project-1",
name: "Altprojekt",
singlePhaseVoltageV: 230,
threePhaseVoltageV: 400,
},
};
const snapshot = parseProjectStateSnapshot(legacy);
assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion);
assert.deepEqual(snapshot.project, {
...legacy.project,
internalProjectNumber: null,
externalProjectNumber: null,
buildingOwner: null,
description: null,
enabledDistributionBoardSupplyTypes: [
"AV",
"SV",
"EV",
"USV",
"MSR",
"SiBe",
],
});
});
it("upgrades version-two distribution boards without inferred assignments", () => {
const previous = {
...legacyMinimalSnapshot(),
schemaVersion: 2 as const,
project: {
...minimalSnapshot().project,
enabledDistributionBoardSupplyTypes: undefined,
},
distributionBoards: [
{
id: "board-1",
projectId: "project-1",
name: "UV 1",
},
],
};
delete previous.project.enabledDistributionBoardSupplyTypes;
const snapshot = parseProjectStateSnapshot(previous);
assert.deepEqual(snapshot.distributionBoards, [
{
...previous.distributionBoards[0],
floorId: null,
supplyType: null,
simultaneityFactor: 1,
},
]);
});
it("upgrades version-three snapshots with all project supply types", () => {
const previous = {
...legacyMinimalSnapshot(),
schemaVersion: 3 as const,
project: {
...minimalSnapshot().project,
enabledDistributionBoardSupplyTypes: undefined,
},
};
delete previous.project.enabledDistributionBoardSupplyTypes;
const snapshot = parseProjectStateSnapshot(previous);
assert.deepEqual(snapshot.project.enabledDistributionBoardSupplyTypes, [
"AV",
"SV",
"EV",
"USV",
"MSR",
"SiBe",
]);
});
it("normalizes version-four device voltages to project settings", () => {
const previous = {
...legacyMinimalSnapshot(),
schemaVersion: 4 as const,
projectDevices: [
{
id: "device-1",
projectId: "project-1",
name: "Pumpe",
displayName: "Pumpe",
phaseType: "three_phase" as const,
connectionKind: null,
costGroup: null,
category: null,
quantity: 1,
powerPerUnit: 2,
simultaneityFactor: 1,
cosPhi: null,
remark: null,
voltageV: 500,
},
],
};
const snapshot = parseProjectStateSnapshot(previous);
assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion);
assert.equal(snapshot.projectDevices[0]?.voltageV, 400);
});
it("upgrades version-five distribution boards with a neutral factor", () => {
const previous = {
...legacyMinimalSnapshot(),
schemaVersion: 5 as const,
distributionBoards: [
{
id: "board-1",
projectId: "project-1",
name: "UV 1",
floorId: null,
supplyType: "AV" as const,
},
],
};
const snapshot = parseProjectStateSnapshot(previous);
assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion);
assert.equal(
snapshot.distributionBoards[0]?.simultaneityFactor,
1
it("rejects pre-baseline snapshot shapes", () => {
const outdated = structuredClone(minimalSnapshot());
delete (outdated.project as Record<string, unknown>).description;
assert.throws(() => parseProjectStateSnapshot(outdated));
assert.throws(() =>
parseProjectStateSnapshot({ ...minimalSnapshot(), schemaVersion: 7 })
);
});
it("upgrades version-six sections without inventing component data", () => {
const previous = {
...legacyMinimalSnapshot(),
schemaVersion: 6 as const,
circuitSections: [
{
id: "section-lighting",
circuitListId: "list-1",
key: "lighting",
displayName: "Beleuchtung",
prefix: "-1F",
sortOrder: 10,
},
{
id: "section-unassigned",
circuitListId: "list-1",
key: "unassigned",
displayName: "Nicht zugeordnet",
prefix: "-UF",
sortOrder: 90,
},
],
distributionBoards: [
{
id: "board-1",
projectId: "project-1",
name: "UV 1",
floorId: null,
supplyType: null,
simultaneityFactor: 1,
},
],
circuitLists: [
{
id: "list-1",
projectId: "project-1",
distributionBoardId: "board-1",
name: "UV 1 Stromkreisliste",
},
],
};
const snapshot = parseProjectStateSnapshot(previous);
assert.deepEqual(
snapshot.circuitSections.map(
({ key, category, groupNumber }) => ({
key,
category,
groupNumber,
})
),
[
{
key: "lighting",
category: "lighting",
groupNumber: 1,
},
{
key: "unassigned",
category: null,
groupNumber: null,
},
]
);
assert.deepEqual(snapshot.distributionBoardComponents, []);
assert.deepEqual(snapshot.circuitProtectionDevices, []);
assert.deepEqual(
snapshot.distributionBoardComponentProtectionDevices,
[]
);
});
it("validates the complete group, component and protection graph", () => {
it("validates component, protection and equipment relations", () => {
const snapshot = parseProjectStateSnapshot(protectedSnapshot());
assert.equal(snapshot.circuitSections[0]?.groupNumber, 1);
assert.equal(
snapshot.distributionBoardComponents[0]?.sectionId,
"section-1"
);
assert.equal(
snapshot.circuitProtectionDevices[0]?.tripCharacteristic,
"B"
);
assert.equal(snapshot.circuitProtectionDevices[0]?.tripCharacteristic, "B");
assert.equal(
snapshot.distributionBoardComponentProtectionDevices[0]
?.ratedResidualCurrentMa,
@@ -401,113 +179,27 @@ describe("project state snapshot model", () => {
);
});
it("rejects duplicate ids and cross-project ownership", () => {
it("rejects duplicate ids and invalid ownership", () => {
const duplicate = protectedSnapshot();
duplicate.distributionBoards.push({
...duplicate.distributionBoards[0]!,
name: "UV 2",
});
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
distributionBoards: [
{
id: "board-1",
projectId: "project-1",
name: "UV 1",
floorId: null,
supplyType: null,
simultaneityFactor: 1,
},
{
id: "board-1",
projectId: "project-1",
name: "UV 2",
floorId: null,
supplyType: null,
simultaneityFactor: 1,
},
],
}),
() => parseProjectStateSnapshot(duplicate),
/duplicate distribution board ids/
);
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
floors: [
{
id: "floor-1",
projectId: "project-2",
name: "EG",
sortOrder: 10,
},
],
}),
/floor belongs to a different project/
);
});
it("rejects invalid references and inconsistent reserve circuits", () => {
const wrongOwner = minimalSnapshot();
wrongOwner.floors.push({
id: "floor-1",
projectId: "project-2",
name: "EG",
sortOrder: 10,
});
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
distributionBoards: [
{
id: "board-1",
projectId: "project-1",
name: "UV 1",
floorId: "missing",
supplyType: "AV",
simultaneityFactor: 1,
},
],
}),
/distribution board floor reference is invalid/
);
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
rooms: [
{
id: "room-1",
projectId: "project-1",
floorId: "missing",
roomNumber: "001",
roomName: "Technik",
},
],
}),
/room floor reference is invalid/
);
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
circuits: [
{
id: "circuit-1",
circuitListId: "missing",
sectionId: "missing",
equipmentIdentifier: "-1F1",
displayName: null,
sortOrder: 10,
protectionType: null,
protectionRatedCurrent: null,
protectionCharacteristic: null,
cableType: null,
cableCrossSection: null,
cableLength: null,
rcdAssignment: null,
terminalDesignation: null,
voltage: null,
controlRequirement: null,
status: null,
isReserve: true,
remark: null,
deviceRows: [],
},
],
}),
/circuit list reference is invalid/
() => parseProjectStateSnapshot(wrongOwner),
/floor belongs to a different project/
);
});
});
@@ -518,36 +210,29 @@ describe("named project snapshot request", () => {
createNamedProjectSnapshotSchema.parse({
expectedRevision: 12,
name: " Freigabe ",
description: " Stand vor Ausschreibung ",
description: " Stand vor Änderung ",
}),
{
expectedRevision: 12,
name: "Freigabe",
description: "Stand vor Ausschreibung",
description: "Stand vor Änderung",
}
);
});
it("rejects stale-shaped and excessive input", () => {
for (const input of [
{ expectedRevision: -1, name: "Stand" },
{ expectedRevision: 0, name: " " },
{ expectedRevision: 0, name: "x".repeat(101) },
{
expectedRevision: 0,
name: "Stand",
description: "x".repeat(501),
},
{
expectedRevision: 0,
name: "Stand",
assert.throws(() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: -1,
name: "",
unexpected: true,
},
]) {
assert.equal(
createNamedProjectSnapshotSchema.safeParse(input).success,
false
);
}
})
);
assert.throws(() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: 0,
name: "x".repeat(201),
})
);
});
});