forked from jappel/leistungsbilanz-ts
930 lines
27 KiB
TypeScript
930 lines
27 KiB
TypeScript
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 { DistributionBoardStructureProjectCommandRepository } from "../src/db/repositories/distribution-board-structure-project-command.repository.js";
|
||
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.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 { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
|
||
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
|
||
import { floors } from "../src/db/schema/floors.js";
|
||
import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
||
import { projects } from "../src/db/schema/projects.js";
|
||
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
||
import {
|
||
createDistributionBoardInsertProjectCommand,
|
||
createDistributionBoardStructureSnapshot,
|
||
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
|
||
import {
|
||
createDistributionBoardUpdateProjectCommand,
|
||
} from "../src/domain/models/distribution-board-project-command.model.js";
|
||
import {
|
||
copyDistributionBoard,
|
||
createDistributionBoard,
|
||
deleteDistributionBoard,
|
||
updateDistributionBoard,
|
||
} from "../src/frontend/utils/api.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: "Test project" },
|
||
{ id: "project-2", name: "Foreign project" },
|
||
])
|
||
.run();
|
||
return context;
|
||
}
|
||
|
||
function getStructureCounts(context: DatabaseContext) {
|
||
return {
|
||
boards: context.db.select().from(distributionBoards).all().length,
|
||
lists: context.db.select().from(circuitLists).all().length,
|
||
sections: context.db.select().from(circuitSections).all().length,
|
||
components: context.db
|
||
.select()
|
||
.from(distributionBoardComponents)
|
||
.all().length,
|
||
revisions: context.db.select().from(projectRevisions).all().length,
|
||
};
|
||
}
|
||
|
||
describe("distribution-board structure project command", () => {
|
||
it("builds the fixed setup and sends the expected frontend revision", async () => {
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
" UV-02 ",
|
||
{ floorId: null, supplyType: "SV" }
|
||
);
|
||
assert.equal(structure.distributionBoard.name, "UV-02");
|
||
assert.equal(structure.distributionBoard.supplyType, "SV");
|
||
assert.equal(
|
||
structure.circuitList.name,
|
||
"UV-02 Stromkreisliste"
|
||
);
|
||
assert.deepEqual(
|
||
structure.sections.map((section) => [
|
||
section.key,
|
||
section.prefix,
|
||
]),
|
||
[
|
||
["lighting", "-1F1."],
|
||
["single_phase", "-2F1."],
|
||
["three_phase", "-3F1."],
|
||
]
|
||
);
|
||
assert.deepEqual(
|
||
structure.components.map((component) => [
|
||
component.equipmentIdentifier,
|
||
component.name,
|
||
]),
|
||
[
|
||
["-Q0", "Hauptschalter"],
|
||
["-FA", "Überspannungsableiter"],
|
||
]
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
createDistributionBoardInsertProjectCommand({
|
||
...structure,
|
||
sections: structure.sections.slice(0, 2),
|
||
}),
|
||
/incomplete/
|
||
);
|
||
|
||
const requests: Array<{ url: string; body: unknown }> = [];
|
||
const originalFetch = globalThis.fetch;
|
||
globalThis.fetch = async (input, init) => {
|
||
requests.push({
|
||
url: String(input),
|
||
body: JSON.parse(String(init?.body)),
|
||
});
|
||
return new Response(JSON.stringify({}), {
|
||
status: 200,
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
};
|
||
try {
|
||
await createDistributionBoard(
|
||
"project-1",
|
||
{
|
||
name: "UV-02",
|
||
floorId: null,
|
||
supplyType: "SV",
|
||
},
|
||
7
|
||
);
|
||
} finally {
|
||
globalThis.fetch = originalFetch;
|
||
}
|
||
assert.deepEqual(requests, [
|
||
{
|
||
url: "/api/projects/project-1/distribution-boards",
|
||
body: {
|
||
name: "UV-02",
|
||
floorId: null,
|
||
supplyType: "SV",
|
||
expectedRevision: 7,
|
||
},
|
||
},
|
||
]);
|
||
});
|
||
|
||
it("creates stable structure and supports persisted undo and redo", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
context.db
|
||
);
|
||
const history = new ProjectHistoryRepository(context.db);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-02"
|
||
);
|
||
const command =
|
||
createDistributionBoardInsertProjectCommand(structure);
|
||
const forward = repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command,
|
||
});
|
||
assert.deepEqual(getStructureCounts(context), {
|
||
boards: 1,
|
||
lists: 1,
|
||
sections: 3,
|
||
components: 2,
|
||
revisions: 1,
|
||
});
|
||
|
||
const undoStep = history.getNextCommand("project-1", "undo");
|
||
assert.ok(undoStep);
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "undo",
|
||
historyTargetChangeSetId: undoStep.changeSetId,
|
||
command: forward.inverse,
|
||
});
|
||
assert.deepEqual(getStructureCounts(context), {
|
||
boards: 0,
|
||
lists: 0,
|
||
sections: 0,
|
||
components: 0,
|
||
revisions: 2,
|
||
});
|
||
|
||
const redoStep = history.getNextCommand("project-1", "redo");
|
||
assert.ok(redoStep);
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 2,
|
||
source: "redo",
|
||
historyTargetChangeSetId: redoStep.changeSetId,
|
||
command,
|
||
});
|
||
assert.deepEqual(getStructureCounts(context), {
|
||
boards: 1,
|
||
lists: 1,
|
||
sections: 3,
|
||
components: 2,
|
||
revisions: 3,
|
||
});
|
||
assert.ok(
|
||
context.db
|
||
.select()
|
||
.from(distributionBoards)
|
||
.where(
|
||
eq(
|
||
distributionBoards.id,
|
||
structure.distributionBoard.id
|
||
)
|
||
)
|
||
.get()
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("blocks undo from removing an empty board used as a feeder", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(context.db);
|
||
const upstream = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"NSHV"
|
||
);
|
||
const inserted = repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(upstream),
|
||
});
|
||
const downstream = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-01"
|
||
);
|
||
context.db.insert(distributionBoards).values({
|
||
...downstream.distributionBoard,
|
||
boardNumber: "UV-01",
|
||
feedingDistributionBoardId: upstream.distributionBoard.id,
|
||
}).run();
|
||
context.db.insert(circuitLists).values(downstream.circuitList).run();
|
||
context.db.insert(circuitSections).values(downstream.sections).run();
|
||
context.db.insert(distributionBoardComponents)
|
||
.values(downstream.components).run();
|
||
|
||
assert.throws(
|
||
() => repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "undo",
|
||
historyTargetChangeSetId: inserted.revision.changeSetId,
|
||
command: inserted.inverse,
|
||
}),
|
||
/Speist UV-01 – erst Einspeisung ändern\./
|
||
);
|
||
assert.ok(
|
||
context.db.select().from(distributionBoards)
|
||
.where(eq(distributionBoards.id, upstream.distributionBoard.id)).get()
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("allows only supply types enabled in the project", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
context.db
|
||
.update(projects)
|
||
.set({
|
||
enabledDistributionBoardSupplyTypes: ["MSR", "SiBe"],
|
||
})
|
||
.where(eq(projects.id, "project-1"))
|
||
.run();
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
context.db
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(
|
||
createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV AV",
|
||
{ floorId: null, supplyType: "AV" }
|
||
)
|
||
),
|
||
}),
|
||
/nicht aktiviert/
|
||
);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV SiBe",
|
||
{ floorId: null, supplyType: "SiBe" }
|
||
);
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(structure),
|
||
});
|
||
assert.equal(
|
||
context.db.select().from(distributionBoards).get()?.supplyType,
|
||
"SiBe"
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("updates floor, supply type and simultaneity factor with persisted undo", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
context.db
|
||
.insert(floors)
|
||
.values([
|
||
{
|
||
id: "floor-1",
|
||
projectId: "project-1",
|
||
name: "EG",
|
||
sortOrder: 10,
|
||
},
|
||
{
|
||
id: "floor-foreign",
|
||
projectId: "project-2",
|
||
name: "Fremd",
|
||
sortOrder: 10,
|
||
},
|
||
])
|
||
.run();
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
context.db
|
||
);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-02",
|
||
{ floorId: null, supplyType: "AV" }
|
||
);
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command:
|
||
createDistributionBoardInsertProjectCommand(structure),
|
||
});
|
||
const forward = repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
structure.distributionBoard.id,
|
||
{
|
||
floorId: "floor-1",
|
||
supplyType: "SV",
|
||
simultaneityFactor: 0.75,
|
||
}
|
||
),
|
||
});
|
||
assert.deepEqual(
|
||
context.db
|
||
.select({
|
||
floorId: distributionBoards.floorId,
|
||
supplyType: distributionBoards.supplyType,
|
||
simultaneityFactor:
|
||
distributionBoards.simultaneityFactor,
|
||
})
|
||
.from(distributionBoards)
|
||
.get(),
|
||
{
|
||
floorId: "floor-1",
|
||
supplyType: "SV",
|
||
simultaneityFactor: 0.75,
|
||
}
|
||
);
|
||
|
||
const undoStep = new ProjectHistoryRepository(
|
||
context.db
|
||
).getNextCommand("project-1", "undo");
|
||
assert.ok(undoStep);
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 2,
|
||
source: "undo",
|
||
historyTargetChangeSetId: undoStep.changeSetId,
|
||
command: forward.inverse,
|
||
});
|
||
assert.deepEqual(
|
||
context.db
|
||
.select({
|
||
floorId: distributionBoards.floorId,
|
||
supplyType: distributionBoards.supplyType,
|
||
simultaneityFactor:
|
||
distributionBoards.simultaneityFactor,
|
||
})
|
||
.from(distributionBoards)
|
||
.get(),
|
||
{
|
||
floorId: null,
|
||
supplyType: "AV",
|
||
simultaneityFactor: 1,
|
||
}
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 3,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
structure.distributionBoard.id,
|
||
{ floorId: "floor-foreign" }
|
||
),
|
||
}),
|
||
/does not belong/
|
||
);
|
||
assert.equal(
|
||
context.db
|
||
.select()
|
||
.from(projectRevisions)
|
||
.all().length,
|
||
3
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("updates the info card fields with persisted undo", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
context.db
|
||
);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-EG",
|
||
{ floorId: null, supplyType: "AV" }
|
||
);
|
||
const insert = repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(structure),
|
||
});
|
||
const readInfo = () =>
|
||
context.db
|
||
.select({
|
||
boardNumber: distributionBoards.boardNumber,
|
||
location: distributionBoards.location,
|
||
fedFrom: distributionBoards.fedFrom,
|
||
remark: distributionBoards.remark,
|
||
})
|
||
.from(distributionBoards)
|
||
.get();
|
||
assert.deepEqual(readInfo(), {
|
||
boardNumber: null,
|
||
location: null,
|
||
fedFrom: null,
|
||
remark: null,
|
||
});
|
||
|
||
const forward = repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
structure.distributionBoard.id,
|
||
{
|
||
boardNumber: "UV 0.1",
|
||
location: "Raum 003 Technik",
|
||
fedFrom: "HAK",
|
||
}
|
||
),
|
||
});
|
||
assert.deepEqual(readInfo(), {
|
||
boardNumber: "UV 0.1",
|
||
location: "Raum 003 Technik",
|
||
fedFrom: "HAK",
|
||
remark: null,
|
||
});
|
||
assert.deepEqual(
|
||
forward.inverse.payload.changes.map((change) => change.field),
|
||
["boardNumber", "location", "fedFrom"]
|
||
);
|
||
|
||
// A board with info values is no longer the unchanged empty setup.
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 2,
|
||
source: "user",
|
||
command: insert.inverse,
|
||
}),
|
||
/changed before deletion/
|
||
);
|
||
|
||
const undoStep = new ProjectHistoryRepository(
|
||
context.db
|
||
).getNextCommand("project-1", "undo");
|
||
assert.ok(undoStep);
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 2,
|
||
source: "undo",
|
||
historyTargetChangeSetId: undoStep.changeSetId,
|
||
command: forward.inverse,
|
||
});
|
||
assert.deepEqual(readInfo(), {
|
||
boardNumber: null,
|
||
location: null,
|
||
fedFrom: null,
|
||
remark: null,
|
||
});
|
||
|
||
for (const value of ["", " UV 1", "x".repeat(51)]) {
|
||
assert.throws(
|
||
() =>
|
||
createDistributionBoardUpdateProjectCommand(
|
||
structure.distributionBoard.id,
|
||
{ boardNumber: value }
|
||
),
|
||
/boardNumber/
|
||
);
|
||
}
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("links a feeding board and circuit while preserving version-1 updates", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(context.db);
|
||
const upstream = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"NSHV",
|
||
{ supplyType: "AV" }
|
||
);
|
||
const downstream = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV EG",
|
||
{ supplyType: "AV" }
|
||
);
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(upstream),
|
||
});
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(downstream),
|
||
});
|
||
context.db.insert(circuits).values([
|
||
{
|
||
id: "feed-circuit",
|
||
circuitListId: upstream.circuitList.id,
|
||
sectionId: upstream.sections[2]!.id,
|
||
equipmentIdentifier: "-3F1.1",
|
||
sortOrder: 10,
|
||
},
|
||
{
|
||
id: "downstream-circuit",
|
||
circuitListId: downstream.circuitList.id,
|
||
sectionId: downstream.sections[2]!.id,
|
||
equipmentIdentifier: "-3F1.1",
|
||
sortOrder: 10,
|
||
},
|
||
]).run();
|
||
|
||
const linked = repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 2,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
downstream.distributionBoard.id,
|
||
{
|
||
feedingDistributionBoardId: upstream.distributionBoard.id,
|
||
feedingCircuitId: "feed-circuit",
|
||
}
|
||
),
|
||
});
|
||
assert.deepEqual(
|
||
context.db
|
||
.select({
|
||
feedingDistributionBoardId:
|
||
distributionBoards.feedingDistributionBoardId,
|
||
feedingCircuitId: distributionBoards.feedingCircuitId,
|
||
})
|
||
.from(distributionBoards)
|
||
.where(eq(distributionBoards.id, downstream.distributionBoard.id))
|
||
.get(),
|
||
{
|
||
feedingDistributionBoardId: upstream.distributionBoard.id,
|
||
feedingCircuitId: "feed-circuit",
|
||
}
|
||
);
|
||
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 3,
|
||
source: "undo",
|
||
historyTargetChangeSetId: new ProjectHistoryRepository(
|
||
context.db
|
||
).getNextCommand("project-1", "undo")?.changeSetId,
|
||
command: linked.inverse,
|
||
});
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 4,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
downstream.distributionBoard.id,
|
||
{
|
||
feedingDistributionBoardId: upstream.distributionBoard.id,
|
||
feedingCircuitId: null,
|
||
}
|
||
),
|
||
});
|
||
|
||
assert.throws(
|
||
() => repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 5,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
upstream.distributionBoard.id,
|
||
{
|
||
feedingDistributionBoardId: downstream.distributionBoard.id,
|
||
feedingCircuitId: null,
|
||
}
|
||
),
|
||
}),
|
||
/Zyklus/
|
||
);
|
||
assert.throws(
|
||
() => repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 5,
|
||
source: "user",
|
||
command: createDistributionBoardUpdateProjectCommand(
|
||
downstream.distributionBoard.id,
|
||
{
|
||
feedingDistributionBoardId: upstream.distributionBoard.id,
|
||
feedingCircuitId: "downstream-circuit",
|
||
}
|
||
),
|
||
}),
|
||
/gehört nicht zur gewählten Verteilung/
|
||
);
|
||
|
||
repository.executeUpdate({
|
||
projectId: "project-1",
|
||
expectedRevision: 5,
|
||
source: "user",
|
||
command: {
|
||
schemaVersion: 1,
|
||
type: "distribution-board.update",
|
||
payload: {
|
||
distributionBoardId: downstream.distributionBoard.id,
|
||
changes: [{ field: "boardNumber", value: "UV-01" }],
|
||
},
|
||
},
|
||
});
|
||
const afterLegacyUpdate = context.db
|
||
.select()
|
||
.from(distributionBoards)
|
||
.where(eq(distributionBoards.id, downstream.distributionBoard.id))
|
||
.get();
|
||
assert.equal(afterLegacyUpdate?.boardNumber, "UV-01");
|
||
assert.equal(
|
||
afterLegacyUpdate?.feedingDistributionBoardId,
|
||
upstream.distributionBoard.id
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("sends distribution-board writes to the revision-safe API", async () => {
|
||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||
const originalFetch = globalThis.fetch;
|
||
globalThis.fetch = async (input, init) => {
|
||
requests.push({
|
||
url: String(input),
|
||
method: init?.method ?? "GET",
|
||
body: JSON.parse(String(init?.body)),
|
||
});
|
||
return new Response(JSON.stringify({}), {
|
||
status: 200,
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
};
|
||
try {
|
||
await updateDistributionBoard(
|
||
"project-1",
|
||
"board-1",
|
||
{
|
||
floorId: "floor-1",
|
||
supplyType: "USV",
|
||
simultaneityFactor: 0.8,
|
||
},
|
||
9
|
||
);
|
||
await copyDistributionBoard(
|
||
"project-1",
|
||
"board-1",
|
||
"UV 1 Kopie",
|
||
10
|
||
);
|
||
await deleteDistributionBoard(
|
||
"project-1",
|
||
"board-1",
|
||
11
|
||
);
|
||
} finally {
|
||
globalThis.fetch = originalFetch;
|
||
}
|
||
assert.deepEqual(requests, [
|
||
{
|
||
url: "/api/projects/project-1/distribution-boards/board-1",
|
||
method: "PUT",
|
||
body: {
|
||
floorId: "floor-1",
|
||
supplyType: "USV",
|
||
simultaneityFactor: 0.8,
|
||
expectedRevision: 9,
|
||
},
|
||
},
|
||
{
|
||
url: "/api/projects/project-1/distribution-boards/board-1/copy",
|
||
method: "POST",
|
||
body: {
|
||
name: "UV 1 Kopie",
|
||
expectedRevision: 10,
|
||
},
|
||
},
|
||
{
|
||
url: "/api/projects/project-1/distribution-boards/board-1",
|
||
method: "DELETE",
|
||
body: {
|
||
expectedRevision: 11,
|
||
},
|
||
},
|
||
]);
|
||
});
|
||
|
||
it("rejects foreign, stale and changed structures without partial writes", () => {
|
||
const context = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
context.db
|
||
);
|
||
const foreign = createDistributionBoardInsertProjectCommand(
|
||
createDistributionBoardStructureSnapshot(
|
||
"project-2",
|
||
"UV fremd"
|
||
)
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: foreign,
|
||
}),
|
||
/does not belong/
|
||
);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-02"
|
||
);
|
||
const command =
|
||
createDistributionBoardInsertProjectCommand(structure);
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "user",
|
||
command,
|
||
}),
|
||
ProjectRevisionConflictError
|
||
);
|
||
assert.deepEqual(getStructureCounts(context), {
|
||
boards: 0,
|
||
lists: 0,
|
||
sections: 0,
|
||
components: 0,
|
||
revisions: 0,
|
||
});
|
||
|
||
const inserted = repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command,
|
||
});
|
||
context.db
|
||
.update(distributionBoards)
|
||
.set({ name: "Direkt geändert" })
|
||
.where(eq(distributionBoards.id, structure.distributionBoard.id))
|
||
.run();
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "undo",
|
||
historyTargetChangeSetId:
|
||
new ProjectHistoryRepository(
|
||
context.db
|
||
).getNextCommand("project-1", "undo")?.changeSetId,
|
||
command: inserted.inverse,
|
||
}),
|
||
/changed before deletion/
|
||
);
|
||
assert.equal(
|
||
context.db.select().from(projectRevisions).all().length,
|
||
1
|
||
);
|
||
} finally {
|
||
context.close();
|
||
}
|
||
});
|
||
|
||
it("refuses populated deletion and rolls back late history failures", () => {
|
||
const populatedContext = createTestDatabase();
|
||
try {
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
populatedContext.db
|
||
);
|
||
const structure = createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV-02"
|
||
);
|
||
const inserted = repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command:
|
||
createDistributionBoardInsertProjectCommand(structure),
|
||
});
|
||
populatedContext.db
|
||
.insert(circuits)
|
||
.values({
|
||
id: "circuit-1",
|
||
circuitListId: structure.circuitList.id,
|
||
sectionId: structure.sections[0].id,
|
||
equipmentIdentifier: "-1F1",
|
||
sortOrder: 10,
|
||
})
|
||
.run();
|
||
const undoStep = new ProjectHistoryRepository(
|
||
populatedContext.db
|
||
).getNextCommand("project-1", "undo");
|
||
assert.ok(undoStep);
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 1,
|
||
source: "undo",
|
||
historyTargetChangeSetId: undoStep.changeSetId,
|
||
command: inserted.inverse,
|
||
}),
|
||
/populated/
|
||
);
|
||
} finally {
|
||
populatedContext.close();
|
||
}
|
||
|
||
const rollbackContext = createTestDatabase();
|
||
try {
|
||
rollbackContext.sqlite.exec(`
|
||
CREATE TRIGGER fail_distribution_board_history
|
||
BEFORE INSERT ON project_history_stack_entries
|
||
BEGIN
|
||
SELECT RAISE(ABORT, 'forced distribution-board history failure');
|
||
END;
|
||
`);
|
||
const repository =
|
||
new DistributionBoardStructureProjectCommandRepository(
|
||
rollbackContext.db
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
repository.execute({
|
||
projectId: "project-1",
|
||
expectedRevision: 0,
|
||
source: "user",
|
||
command: createDistributionBoardInsertProjectCommand(
|
||
createDistributionBoardStructureSnapshot(
|
||
"project-1",
|
||
"UV rollback"
|
||
)
|
||
),
|
||
}),
|
||
/forced distribution-board history failure/
|
||
);
|
||
assert.deepEqual(getStructureCounts(rollbackContext), {
|
||
boards: 0,
|
||
lists: 0,
|
||
sections: 0,
|
||
components: 0,
|
||
revisions: 0,
|
||
});
|
||
} finally {
|
||
rollbackContext.close();
|
||
}
|
||
});
|
||
});
|