leistungsbilanz-ts/tests/external-object-row-assignment-project-command.repository.test.ts

695 lines
27 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 AppDatabase } from "../src/db/database-context.js";
import { ExternalObjectRowAssignmentProjectCommandRepository } from "../src/db/repositories/external-object-row-assignment-project-command.repository.js";
import { ExternalObjectNewRowProjectCommandRepository } from "../src/db/repositories/external-object-new-row-project-command.repository.js";
import { ExternalObjectNewCircuitProjectCommandRepository } from "../src/db/repositories/external-object-new-circuit-project-command.repository.js";
import { CircuitDeviceRowStructureProjectCommandRepository } from "../src/db/repositories/circuit-device-row-structure-project-command.repository.js";
import { CircuitStructureProjectCommandRepository } from "../src/db/repositories/circuit-structure-project-command.repository.js";
import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js";
import { toCircuitDeviceRowSnapshot } from "../src/db/repositories/circuit-device-row-structure.persistence.js";
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
import { circuitLists } from "../src/db/schema/circuit-lists.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { externalImportBatches } from "../src/db/schema/external-import-batches.js";
import { externalModelObjects } from "../src/db/schema/external-model-objects.js";
import { externalModelSources } from "../src/db/schema/external-model-sources.js";
import { externalRoomMappings } from "../src/db/schema/external-room-mappings.js";
import { floors } from "../src/db/schema/floors.js";
import { projects } from "../src/db/schema/projects.js";
import { rooms } from "../src/db/schema/rooms.js";
import { createExternalObjectRowAssignmentProjectCommand } from "../src/domain/models/external-object-row-assignment-project-command.model.js";
import {
createExternalObjectNewRowProjectCommand,
externalObjectAssignToNewRowCommandType,
} from "../src/domain/models/external-object-new-row-project-command.model.js";
import {
createExternalObjectNewCircuitProjectCommand,
externalObjectAssignToNewCircuitCommandType,
} from "../src/domain/models/external-object-new-circuit-project-command.model.js";
import type { ExternalModelObjectSnapshot } from "../src/external-model/domain/external-model-contracts.js";
import { createCircuitDeviceRowDeleteProjectCommand } from "../src/domain/models/circuit-device-row-structure-project-command.model.js";
import { createCircuitDeleteProjectCommand } from "../src/domain/models/circuit-structure-project-command.model.js";
import { createCircuitGroupDeleteSubtreeProjectCommand } from "../src/domain/models/circuit-group-subtree-project-command.model.js";
import { assertDistributionBoardHasNoExternalObjects, assertRoomHasNoExternalObjects } from "../src/db/repositories/external-object-deletion-guard.persistence.js";
function createFixture(initialObjectRowId: string | null = "row-1") {
const context = createDatabaseContext(":memory:");
migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") });
const database = context.db;
database.insert(projects).values({ id: "project-1", name: "Projekt" }).run();
database.insert(floors).values({ id: "floor-1", projectId: "project-1", name: "EG", sortOrder: 10 }).run();
database.insert(rooms).values({ id: "room-1", projectId: "project-1", floorId: "floor-1", roomNumber: "101", roomName: "Büro" }).run();
database.insert(distributionBoards).values({ id: "board-1", projectId: "project-1", name: "UV 1" }).run();
database.insert(circuitLists).values({ id: "list-1", projectId: "project-1", distributionBoardId: "board-1", name: "Liste" }).run();
database.insert(circuitSections).values({ id: "section-1", circuitListId: "list-1", key: "single-1", displayName: "1-phasig", prefix: "-2F1.", sortOrder: 10, category: "single_phase", groupNumber: 1 }).run();
for (const index of [1, 2]) {
database.insert(circuits).values({ id: `circuit-${index}`, circuitListId: "list-1", sectionId: "section-1", equipmentIdentifier: `-2F1.${index}`, sortOrder: index * 10, voltage: 230 }).run();
database.insert(circuitDeviceRows).values({
id: `row-${index}`,
circuitId: `circuit-${index}`,
sortOrder: 10,
name: "socket",
displayName: "Steckdose",
phaseType: "single_phase",
connectionKind: "socket",
category: "single_phase",
roomId: "room-1",
roomNumberSnapshot: "101",
roomNameSnapshot: "Büro",
quantity: index === 1 ? (initialObjectRowId === "row-1" ? 3 : 1) : 0,
manualQuantity: index === 1 ? 1 : 0,
powerPerUnit: 0.12,
simultaneityFactor: 1,
}).run();
}
database.insert(externalModelSources).values({ id: "source-1", projectId: "project-1", name: "Revit", sourceType: "revit_xlsx" }).run();
database.insert(externalImportBatches).values({
id: "batch-1",
projectId: "project-1",
sourceId: "source-1",
importKind: "initial",
importedAtIso: "2026-08-02T16:00:00.000Z",
fileName: "revit.xlsx",
sha256: "a".repeat(64),
objectCount: 1,
}).run();
database.insert(externalRoomMappings).values({
id: "mapping-1",
projectId: "project-1",
sourceId: "source-1",
normalizedSourceRoomKey: "number:101",
sourceFloorName: "EG",
sourceRoomNumber: "101",
sourceRoomName: "Büro",
roomId: "room-1",
defaultDistributionBoardId: "board-1",
}).run();
database.insert(externalModelObjects).values(objectSnapshot(initialObjectRowId)).run();
return context;
}
function objectSnapshot(circuitDeviceRowId: string | null): ExternalModelObjectSnapshot {
return {
id: "object-1",
projectId: "project-1",
sourceId: "source-1",
worksharingId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee-ffffffff",
ifcGuid: "ifc-1",
deviceTypeId: "device-type-1",
lastSeenImportBatchId: "batch-1",
lastAcceptedImportBatchId: "batch-1",
acceptedSourceValues: {
rowNumber: 2,
roomNumber: "101",
roomName: "Büro",
familyAndType: "Steckdose: Standard",
selectionMarker: "Steckdose",
socketCount: "2",
sourceRemark: null,
circuitCombined: "-2F1.1",
boardName: "UV 1",
circuitIdentifier: "-2F1.1",
designation: null,
electricalPowerW: "120",
voltageV: 230,
currentA: null,
},
planningValues: {
displayName: "Steckdose",
category: "single_phase",
connectionKind: "socket",
effectiveQuantity: 2,
powerPerUnitW: 120,
simultaneityFactor: 1,
cosPhi: null,
costGroup: null,
remark: null,
},
overriddenFields: [],
externalRoomMappingId: "mapping-1",
distributionBoardId: "board-1",
linkedProjectDeviceId: null,
circuitDeviceRowId,
presenceStatus: "present",
};
}
function row(database: AppDatabase, id: string) {
return toCircuitDeviceRowSnapshot(
database.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, id)).get()!
);
}
function moveCommand(database: AppDatabase, confirmedConflictObjectIds: string[] = []) {
const row1 = row(database, "row-1");
const row2 = row(database, "row-2");
const object = objectSnapshot("row-1");
return createExternalObjectRowAssignmentProjectCommand({
rows: [
{ expected: row1, target: { ...row1, quantity: 1 } },
{ expected: row2, target: { ...row2, quantity: 2 } },
],
objects: [{ expected: object, target: { ...object, circuitDeviceRowId: "row-2" } }],
confirmedConflictObjectIds,
});
}
describe("external object row assignment project command", () => {
it("moves an object atomically and preserves exact quantities through undo and redo", () => {
const context = createFixture();
try {
const repository = new ExternalObjectRowAssignmentProjectCommandRepository(context.db);
const moved = repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: moveCommand(context.db) });
assert.equal(row(context.db, "row-1").quantity, 1);
assert.equal(row(context.db, "row-2").quantity, 2);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-2");
const undone = repository.execute({ projectId: "project-1", expectedRevision: 1, source: "undo", historyTargetChangeSetId: moved.revision.changeSetId, command: moved.inverse });
assert.equal(row(context.db, "row-1").quantity, 3);
assert.equal(row(context.db, "row-2").quantity, 0);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-1");
repository.execute({ projectId: "project-1", expectedRevision: 2, source: "redo", historyTargetChangeSetId: moved.revision.changeSetId, command: undone.inverse });
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-2");
} finally {
context.close();
}
});
it("detaches the last object but keeps the now-manual row", () => {
const context = createFixture();
try {
const currentRow = row(context.db, "row-1");
const object = objectSnapshot("row-1");
const command = createExternalObjectRowAssignmentProjectCommand({
rows: [{ expected: currentRow, target: { ...currentRow, quantity: 1 } }],
objects: [{ expected: object, target: { ...object, circuitDeviceRowId: null } }],
confirmedConflictObjectIds: [],
});
new ExternalObjectRowAssignmentProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
assert.equal(row(context.db, "row-1").quantity, 1);
assert.equal(row(context.db, "row-1").manualQuantity, 1);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
it("requires explicit confirmation for differing planning values", () => {
const context = createFixture();
try {
context.db.update(circuitDeviceRows).set({ displayName: "Abweichend" }).where(eq(circuitDeviceRows.id, "row-2")).run();
const repository = new ExternalObjectRowAssignmentProjectCommandRepository(context.db);
assert.throws(
() => repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: moveCommand(context.db) }),
/explicit conflict confirmation/
);
repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: moveCommand(context.db, ["object-1"]) });
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-2");
} finally {
context.close();
}
});
it("rolls back row quantities and links when history persistence fails", () => {
const context = createFixture();
try {
context.sqlite.exec(`
CREATE TRIGGER fail_assignment_history
BEFORE INSERT ON project_change_sets
BEGIN SELECT RAISE(ABORT, 'late history failure'); END;
`);
const repository = new ExternalObjectRowAssignmentProjectCommandRepository(context.db);
assert.throws(
() => repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: moveCommand(context.db) }),
/late history failure/
);
assert.equal(row(context.db, "row-1").quantity, 3);
assert.equal(row(context.db, "row-2").quantity, 0);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-1");
} finally {
context.close();
}
});
});
describe("external object new-row project command", () => {
it("creates an external-only row and supports exact persisted undo and redo", () => {
const context = createFixture(null);
try {
const base = row(context.db, "row-2");
const newRow = {
...base,
id: "row-new",
quantity: 2,
manualQuantity: 0,
sortOrder: 20,
};
const object = objectSnapshot(null);
const command = createExternalObjectNewRowProjectCommand(
externalObjectAssignToNewRowCommandType,
{
row: newRow,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
);
const repository = new ExternalObjectNewRowProjectCommandRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
assert.equal(row(context.db, "row-new").manualQuantity, 0);
assert.equal(row(context.db, "row-new").quantity, 2);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-new");
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command: inserted.inverse,
}),
/only be removed through project history/
);
const undone = repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: inserted.inverse,
});
assert.equal(
context.db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, "row-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "redo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: undone.inverse,
});
assert.equal(row(context.db, "row-new").quantity, 2);
} finally {
context.close();
}
});
it("rolls back the row and object link when history persistence fails", () => {
const context = createFixture(null);
try {
context.sqlite.exec(`
CREATE TRIGGER fail_new_row_history
BEFORE INSERT ON project_change_sets
BEGIN SELECT RAISE(ABORT, 'late history failure'); END;
`);
const base = row(context.db, "row-2");
const object = objectSnapshot(null);
const command = createExternalObjectNewRowProjectCommand(
externalObjectAssignToNewRowCommandType,
{
row: { ...base, id: "row-new", quantity: 2, manualQuantity: 0, sortOrder: 20 },
objects: [{ expected: object, target: { ...object, circuitDeviceRowId: "row-new" } }],
confirmedConflictObjectIds: [],
}
);
assert.throws(
() => new ExternalObjectNewRowProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
}),
/late history failure/
);
assert.equal(
context.db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, "row-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
it("keeps the created row when it changed after assignment", () => {
const context = createFixture(null);
try {
const base = row(context.db, "row-2");
const object = objectSnapshot(null);
const repository = new ExternalObjectNewRowProjectCommandRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createExternalObjectNewRowProjectCommand(
externalObjectAssignToNewRowCommandType,
{
row: { ...base, id: "row-new", quantity: 2, manualQuantity: 0, sortOrder: 20 },
objects: [{ expected: object, target: { ...object, circuitDeviceRowId: "row-new" } }],
confirmedConflictObjectIds: [],
}
),
});
context.db.update(circuitDeviceRows).set({ displayName: "Geändert" })
.where(eq(circuitDeviceRows.id, "row-new")).run();
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: inserted.inverse,
}),
/changed before history removal/
);
assert.equal(row(context.db, "row-new").displayName, "Geändert");
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-new");
} finally {
context.close();
}
});
});
function newExternalCircuit(database: AppDatabase) {
const sourceRow = row(database, "row-2");
return {
id: "circuit-new",
circuitListId: "list-1",
sectionId: "section-1",
equipmentIdentifier: "-2F1.3",
displayName: "Steckdose",
sortOrder: 30,
cableType: null,
cableCrossSection: null,
cableLength: null,
rcdAssignment: null,
terminalDesignation: null,
voltage: 230,
controlRequirement: null,
status: null,
isReserve: false,
remark: null,
deviceRows: [{
...sourceRow,
id: "row-new",
circuitId: "circuit-new",
quantity: 2,
manualQuantity: 0,
}],
protectionDevice: {
circuitId: "circuit-new",
type: "FI_LS" as const,
ratedCurrentA: 16,
fuseUtilizationCategory: null,
tripCharacteristic: "B" as const,
rcdType: "A" as const,
ratedResidualCurrentMa: 30,
},
};
}
describe("external object new-circuit project command", () => {
it("creates circuit, protection, row and links as one undoable revision", () => {
const context = createFixture(null);
try {
const object = objectSnapshot(null);
const circuit = newExternalCircuit(context.db);
const command = createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
);
const repository = new ExternalObjectNewCircuitProjectCommandRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get()!
.equipmentIdentifier,
"-2F1.3"
);
assert.equal(row(context.db, "row-new").manualQuantity, 0);
assert.equal(
context.db.select().from(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, "circuit-new")).get()!.type,
"FI_LS"
);
assert.equal(
context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId,
"row-new"
);
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command: inserted.inverse,
}),
/only be removed through project history/
);
const undone = repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: inserted.inverse,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "redo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: undone.inverse,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get()!
.equipmentIdentifier,
"-2F1.3"
);
} finally {
context.close();
}
});
it("rejects a preplanned BMK that does not belong to the target group", () => {
const context = createFixture(null);
try {
const object = objectSnapshot(null);
const circuit = {
...newExternalCircuit(context.db),
equipmentIdentifier: "-1F1.3",
};
assert.throws(
() => new ExternalObjectNewCircuitProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
),
}),
/equipment identifier does not match target group/
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
it("blocks history removal when the generated circuit became a feeder", () => {
const context = createFixture(null);
try {
const object = objectSnapshot(null);
const circuit = newExternalCircuit(context.db);
const command = createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
);
const repository = new ExternalObjectNewCircuitProjectCommandRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
context.db.insert(distributionBoards).values({
id: "board-2",
projectId: "project-1",
name: "Unterverteiler",
boardNumber: "UV-02",
feedingDistributionBoardId: "board-1",
feedingCircuitId: circuit.id,
}).run();
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: inserted.inverse,
}),
/Speist UV-02 – erst Einspeisung ändern\./
);
assert.ok(
context.db.select().from(circuits)
.where(eq(circuits.id, circuit.id)).get()
);
assert.equal(
context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId,
"row-new"
);
} finally {
context.close();
}
});
it("rolls back the complete circuit subtree when history persistence fails", () => {
const context = createFixture(null);
try {
context.sqlite.exec(`
CREATE TRIGGER fail_new_circuit_history
BEFORE INSERT ON project_change_sets
BEGIN SELECT RAISE(ABORT, 'late history failure'); END;
`);
const object = objectSnapshot(null);
assert.throws(
() => new ExternalObjectNewCircuitProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit: newExternalCircuit(context.db),
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
),
}),
/late history failure/
);
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
it("detaches linked objects on row deletion and restores the exact link on undo", () => {
const context = createFixture();
try {
const repository = new CircuitDeviceRowStructureProjectCommandRepository(context.db);
const deleted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createCircuitDeviceRowDeleteProjectCommand("row-1", "circuit-1"),
});
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
assert.equal(context.db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, "row-1")).get(), undefined);
repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: deleted.revision.changeSetId,
command: deleted.inverse,
});
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-1");
assert.ok(context.db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, "row-1")).get());
} finally {
context.close();
}
});
it("preserves external links through circuit and populated-group deletion undo", () => {
for (const kind of ["circuit", "group"] as const) {
const context = createFixture();
try {
const repository = kind === "circuit"
? new CircuitStructureProjectCommandRepository(context.db)
: new CircuitGroupSubtreeProjectCommandRepository(context.db);
const command = kind === "circuit"
? createCircuitDeleteProjectCommand("circuit-1", "list-1")
: createCircuitGroupDeleteSubtreeProjectCommand(
(repository as CircuitGroupSubtreeProjectCommandRepository).capture("project-1", "section-1")
);
const deleted = repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command } as never);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
repository.execute({ projectId: "project-1", expectedRevision: 1, source: "undo", historyTargetChangeSetId: deleted.revision.changeSetId, command: deleted.inverse } as never);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-1");
} finally {
context.close();
}
}
});
it("blocks room and distribution-board deletion while external objects reference them", () => {
const context = createFixture(null);
try {
assert.throws(() => assertDistributionBoardHasNoExternalObjects(context.db, "project-1", "board-1"), /Revit-Objekte/);
assert.throws(() => assertRoomHasNoExternalObjects(context.db, "project-1", "room-1"), /Revit-Objekte/);
} finally {
context.close();
}
});
});