leistungsbilanz-ts/tests/circuit-group-subtree-project-command.repository.test.ts

308 lines
9.4 KiB
TypeScript
Raw 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 DatabaseContext,
} from "../src/db/database-context.js";
import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js";
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js";
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.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 { distributionBoardComponentProtectionDevices } from "../src/db/schema/distribution-board-component-protection-devices.js";
import { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
import { createCircuitGroupDeleteSubtreeProjectCommand } from "../src/domain/models/circuit-group-subtree-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" })
.run();
new DistributionBoardFixtureRepository(
context.db
).createWithCircuitListAndDefaultSections("project-1", "UV-01");
const list = context.db.select().from(circuitLists).get()!;
const group = context.db
.select()
.from(circuitSections)
.where(eq(circuitSections.category, "lighting"))
.get()!;
context.db
.insert(distributionBoardComponents)
.values({
id: "subtree-rcd",
circuitListId: list.id,
sectionId: group.id,
equipmentIdentifier: "-1Q1.0",
name: "Gruppen-FI",
role: "group_residual_current_protection",
placement: "group",
sortOrder: 10,
})
.run();
context.db
.insert(distributionBoardComponentProtectionDevices)
.values({
componentId: "subtree-rcd",
type: "FI",
ratedCurrentA: 40,
rcdType: "A",
ratedResidualCurrentMa: 30,
})
.run();
context.db
.insert(circuits)
.values({
id: "subtree-circuit",
circuitListId: list.id,
sectionId: group.id,
equipmentIdentifier: "-1F1.1",
displayName: "Beleuchtung",
sortOrder: 10,
voltage: 230,
isReserve: 0,
})
.run();
context.db
.insert(circuitProtectionDevices)
.values({
circuitId: "subtree-circuit",
type: "LS",
ratedCurrentA: 10,
tripCharacteristic: "B",
})
.run();
context.db
.insert(circuitDeviceRows)
.values({
id: "subtree-row",
circuitId: "subtree-circuit",
linkedProjectDeviceId: null,
sortOrder: 10,
name: "Leuchte",
displayName: "Leuchte",
phaseType: "single_phase",
connectionKind: null,
costGroup: null,
category: null,
level: null,
roomId: null,
roomNumberSnapshot: null,
roomNameSnapshot: null,
quantity: 2,
powerPerUnit: 0.03,
simultaneityFactor: 1,
cosPhi: 0.95,
remark: null,
overriddenFields: "[\"displayName\"]",
})
.run();
return context;
}
describe("circuit-group subtree project command", () => {
it("deletes and restores the exact populated subtree through undo/redo", () => {
const context = createTestDatabase();
try {
const repository =
new CircuitGroupSubtreeProjectCommandRepository(context.db);
const groupId = context.db
.select()
.from(circuits)
.where(eq(circuits.id, "subtree-circuit"))
.get()!.sectionId;
const snapshot = repository.capture("project-1", groupId);
const command =
createCircuitGroupDeleteSubtreeProjectCommand(snapshot);
const deleted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
assert.equal(
context.db
.select()
.from(circuitSections)
.where(eq(circuitSections.id, groupId))
.get(),
undefined
);
assert.equal(
context.db
.select()
.from(circuitDeviceRows)
.where(eq(circuitDeviceRows.id, "subtree-row"))
.get(),
undefined
);
repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: new ProjectHistoryRepository(
context.db
).getNextCommand("project-1", "undo")?.changeSetId,
command: deleted.inverse,
});
assert.deepEqual(
repository.capture("project-1", groupId),
snapshot
);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "redo",
historyTargetChangeSetId: new ProjectHistoryRepository(
context.db
).getNextCommand("project-1", "redo")?.changeSetId,
command,
});
assert.equal(
context.db
.select()
.from(circuits)
.where(eq(circuits.id, "subtree-circuit"))
.get(),
undefined
);
} finally {
context.close();
}
});
it("rejects a subtree changed after confirmation", () => {
const context = createTestDatabase();
try {
const repository =
new CircuitGroupSubtreeProjectCommandRepository(context.db);
const groupId = context.db
.select()
.from(circuits)
.where(eq(circuits.id, "subtree-circuit"))
.get()!.sectionId;
const snapshot = repository.capture("project-1", groupId);
context.db
.update(circuitDeviceRows)
.set({ displayName: "Geändert" })
.where(eq(circuitDeviceRows.id, "subtree-row"))
.run();
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command:
createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
}),
/changed before deletion/
);
assert.ok(
context.db
.select()
.from(circuitSections)
.where(eq(circuitSections.id, groupId))
.get()
);
assert.equal(
context.db.select().from(projectRevisions).all().length,
0
);
} finally {
context.close();
}
});
it("blocks deleting a group containing a feeding circuit", () => {
const context = createTestDatabase();
try {
const list = context.db.select().from(circuitLists).get()!;
const downstream = new DistributionBoardFixtureRepository(
context.db
).createWithCircuitListAndDefaultSections("project-1", "Unterverteiler");
context.db
.update(distributionBoards)
.set({
feedingDistributionBoardId: list.distributionBoardId,
feedingCircuitId: "subtree-circuit",
})
.where(eq(distributionBoards.id, downstream.id))
.run();
const repository =
new CircuitGroupSubtreeProjectCommandRepository(context.db);
const groupId = context.db
.select()
.from(circuits)
.where(eq(circuits.id, "subtree-circuit"))
.get()!.sectionId;
const snapshot = repository.capture("project-1", groupId);
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
}),
/Speist Unterverteiler – erst Einspeisung ändern\./
);
assert.deepEqual(repository.capture("project-1", groupId), snapshot);
} finally {
context.close();
}
});
it("rolls back a cascaded deletion when history persistence fails", () => {
const context = createTestDatabase();
try {
const repository =
new CircuitGroupSubtreeProjectCommandRepository(context.db);
const groupId = context.db
.select()
.from(circuits)
.where(eq(circuits.id, "subtree-circuit"))
.get()!.sectionId;
const snapshot = repository.capture("project-1", groupId);
context.sqlite.exec(`
CREATE TRIGGER fail_group_subtree_history
BEFORE INSERT ON project_history_stack_entries
BEGIN
SELECT RAISE(ABORT, 'forced group subtree history failure');
END;
`);
assert.throws(
() =>
repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command:
createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
}),
/forced group subtree history failure/
);
assert.deepEqual(
repository.capture("project-1", groupId),
snapshot
);
} finally {
context.close();
}
});
});