Tighten rejection tests to their expected errors

Every assert.throws now names the error it expects, so a broken fixture
or an unrelated failure can no longer satisfy a rejection test. Zod
rejections are matched by issue path and code via tests/support/zod-assertions.ts.

Restore snapshot coverage lost with the release baseline: invalid board
floor, room floor and circuit section references and a reserve state
that contradicts the device rows are rejected again under test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-10-29 12:27:46 +01:00
parent cc4eefcb86
commit a8cc7eca5e
8 changed files with 269 additions and 122 deletions

View file

@ -10,6 +10,7 @@ import { remapProjectState } from "../src/domain/models/project-transfer.model.j
import { createNamedProjectSnapshotSchema } from "../src/shared/validation/project-snapshot.schemas.js";
import { createDefaultExternalCsvConfiguration } from "../src/external-model/csv/external-csv-contracts.js";
import { externalCsvTestColumns } from "./fixtures/revit-csv-fixtures.js";
import { zodIssues } from "./support/zod-assertions.js";
/** Removes the fields introduced by snapshot schema version 9. */
function withoutVersionNineFields<
@ -425,11 +426,20 @@ describe("project state snapshot model", () => {
const mislabeled = protectedSnapshot();
mislabeled.schemaVersion = 5 as typeof mislabeled.schemaVersion;
assert.throws(() => parseProjectStateSnapshot(mislabeled));
assert.throws(
() => parseProjectStateSnapshot(mislabeled),
zodIssues(
["project", "unrecognized_keys", "workStage"],
["distributionBoards.0", "unrecognized_keys", "boardNumber"]
)
);
const missingCover = structuredClone(protectedSnapshot());
delete (missingCover.project as Record<string, unknown>).workStage;
assert.throws(() => parseProjectStateSnapshot(missingCover));
assert.throws(
() => parseProjectStateSnapshot(missingCover),
zodIssues(["project.workStage", "invalid_type"])
);
});
it("upgrades snapshot version 4 with the existing quantity as manual share", () => {
@ -468,12 +478,57 @@ describe("project state snapshot model", () => {
it("rejects malformed baseline and unknown snapshot shapes", () => {
const outdated = structuredClone(minimalSnapshot());
delete (outdated.project as Record<string, unknown>).description;
assert.throws(() => parseProjectStateSnapshot(outdated));
assert.throws(() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
schemaVersion: projectStateSnapshotSchemaVersion + 1,
})
assert.throws(
() => parseProjectStateSnapshot(outdated),
zodIssues(["project.description", "invalid_type"])
);
assert.throws(
() =>
parseProjectStateSnapshot({
...minimalSnapshot(),
schemaVersion: projectStateSnapshotSchemaVersion + 1,
}),
zodIssues(["schemaVersion", "invalid_value"])
);
});
it("rejects invalid structure references", () => {
const boardFloor = protectedSnapshot();
(boardFloor.distributionBoards[0] as { floorId: string | null }).floorId =
"missing";
assert.throws(
() => parseProjectStateSnapshot(boardFloor),
/distribution board floor reference is invalid/
);
const roomFloor = protectedSnapshot();
roomFloor.rooms.push({
id: "room-1",
projectId: "project-1",
floorId: "missing",
roomNumber: "001",
roomName: "Technik",
areaM2: null,
});
assert.throws(
() => parseProjectStateSnapshot(roomFloor),
/room floor reference is invalid/
);
const circuitSection = protectedSnapshot();
circuitSection.circuits[0]!.sectionId = "missing";
assert.throws(
() => parseProjectStateSnapshot(circuitSection),
/circuit section reference is invalid/
);
});
it("rejects circuits whose reserve state contradicts their device rows", () => {
const withoutRows = protectedSnapshot();
withoutRows.circuits[0]!.isReserve = false;
assert.throws(
() => parseProjectStateSnapshot(withoutRows),
/reserve state must match its device rows/
);
});
@ -580,7 +635,10 @@ describe("project state snapshot model", () => {
const floorWithoutBuildingField = structuredClone(snapshot);
delete (floorWithoutBuildingField.floors[1] as Record<string, unknown>)
.buildingId;
assert.throws(() => parseProjectStateSnapshot(floorWithoutBuildingField));
assert.throws(
() => parseProjectStateSnapshot(floorWithoutBuildingField),
zodIssues(["floors.1.buildingId", "invalid_type"])
);
});
it("keeps room areas and upgrades snapshot version 7 with an empty area", () => {
@ -633,7 +691,13 @@ describe("project state snapshot model", () => {
const mislabeled = structuredClone(snapshot);
mislabeled.schemaVersion = 6 as typeof mislabeled.schemaVersion;
assert.throws(() => parseProjectStateSnapshot(mislabeled));
assert.throws(
() => parseProjectStateSnapshot(mislabeled),
zodIssues(
["", "unrecognized_keys", "buildings"],
["floors.0", "unrecognized_keys", "buildingId"]
)
);
});
});
@ -654,18 +718,26 @@ describe("named project snapshot request", () => {
});
it("rejects stale-shaped and excessive input", () => {
assert.throws(() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: -1,
name: "",
unexpected: true,
})
assert.throws(
() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: -1,
name: "",
unexpected: true,
}),
zodIssues(
["expectedRevision", "too_small"],
["name", "too_small"],
["", "unrecognized_keys", "unexpected"]
)
);
assert.throws(() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: 0,
name: "x".repeat(201),
})
assert.throws(
() =>
createNamedProjectSnapshotSchema.parse({
expectedRevision: 0,
name: "x".repeat(201),
}),
zodIssues(["name", "too_big"])
);
});
});