227 lines
8.7 KiB
TypeScript
227 lines
8.7 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 { ExternalCsvConfigurationProjectCommandRepository } from "../src/db/repositories/external-csv-configuration-project-command.repository.js";
|
|
import { ProjectTransferRepository } from "../src/db/repositories/project-transfer.repository.js";
|
|
import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js";
|
|
import { readProjectStateSnapshot } from "../src/db/repositories/project-state-snapshot.persistence.js";
|
|
import { externalCsvConfigurations } from "../src/db/schema/external-csv-configurations.js";
|
|
import { projects } from "../src/db/schema/projects.js";
|
|
import { createExternalCsvConfigurationUpdateProjectCommand } from "../src/domain/models/external-csv-configuration-project-command.model.js";
|
|
import { createProjectStateRestoreCommand } from "../src/domain/models/project-state-restore-command.model.js";
|
|
import { createDefaultExternalCsvConfiguration } from "../src/external-model/csv/external-csv-contracts.js";
|
|
import { externalCsvTestColumns } from "./fixtures/revit-csv-fixtures.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();
|
|
return context;
|
|
}
|
|
|
|
function snapshot(version = 1) {
|
|
const configuration = createDefaultExternalCsvConfiguration(externalCsvTestColumns);
|
|
configuration.familyTypeRules.push({
|
|
exactFamilyAndType: "Steckdose: Standard",
|
|
internalDeviceType: "Steckdose",
|
|
connectionKind: null,
|
|
category: "single_phase",
|
|
quantityRule: { kind: "fixed", quantity: 1 },
|
|
displayNameSuggestion: { kind: "selection-marker" },
|
|
});
|
|
return {
|
|
id: "external-config-1",
|
|
projectId: "project-1",
|
|
configurationVersion: version,
|
|
configuration,
|
|
};
|
|
}
|
|
|
|
describe("external CSV configuration project command", () => {
|
|
it("persists insert and update with exact inverse commands", () => {
|
|
const context = createTestDatabase();
|
|
try {
|
|
const repository = new ExternalCsvConfigurationProjectCommandRepository(context.db);
|
|
const initial = snapshot();
|
|
const inserted = repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 0,
|
|
source: "user",
|
|
description: "Revit-CSV-Konfiguration anlegen",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(null, initial),
|
|
});
|
|
assert.equal(inserted.revision.revisionNumber, 1);
|
|
assert.deepEqual(readConfiguration(context), initial);
|
|
assert.deepEqual(inserted.inverse.payload, { expected: initial, target: null });
|
|
|
|
const target = structuredClone(initial);
|
|
target.configurationVersion = 2;
|
|
target.configuration.decimalSeparator = ".";
|
|
const updated = repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(initial, target),
|
|
});
|
|
assert.equal(updated.revision.revisionNumber, 2);
|
|
assert.deepEqual(readConfiguration(context), target);
|
|
assert.deepEqual(updated.inverse.payload, { expected: target, target: initial });
|
|
} finally {
|
|
context.close();
|
|
}
|
|
});
|
|
|
|
it("rejects stale state and cross-project targets without a partial revision", () => {
|
|
const context = createTestDatabase();
|
|
try {
|
|
const repository = new ExternalCsvConfigurationProjectCommandRepository(context.db);
|
|
const initial = snapshot();
|
|
repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 0,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(null, initial),
|
|
});
|
|
|
|
assert.throws(
|
|
() => repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(null, snapshot(2)),
|
|
}),
|
|
/changed before update/
|
|
);
|
|
const foreign = snapshot(2);
|
|
foreign.projectId = "project-2";
|
|
assert.throws(
|
|
() => repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(initial, foreign),
|
|
}),
|
|
/identity must remain stable|different project/
|
|
);
|
|
assert.equal(context.db.select({ revision: projects.currentRevision }).from(projects).get()?.revision, 1);
|
|
|
|
const skippedVersion = snapshot(3);
|
|
assert.throws(
|
|
() => repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(initial, skippedVersion),
|
|
}),
|
|
/version must advance by one/
|
|
);
|
|
assert.throws(
|
|
() => repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(initial, initial),
|
|
}),
|
|
/did not change/
|
|
);
|
|
} finally {
|
|
context.close();
|
|
}
|
|
});
|
|
|
|
it("includes configuration in snapshots and remaps it for project duplicates", () => {
|
|
const context = createTestDatabase();
|
|
try {
|
|
const repository = new ExternalCsvConfigurationProjectCommandRepository(context.db);
|
|
const initial = snapshot();
|
|
repository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 0,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(null, initial),
|
|
});
|
|
|
|
const state = readProjectStateSnapshot(context.db, "project-1")?.state;
|
|
assert.deepEqual(state?.externalCsvConfiguration, initial);
|
|
const transferRepository = new ProjectTransferRepository(context.db);
|
|
const transfer = transferRepository.exportProject("project-1");
|
|
assert.ok(transfer);
|
|
const duplicate = transferRepository.importDuplicate(transfer);
|
|
const duplicatedConfiguration = context.db
|
|
.select()
|
|
.from(externalCsvConfigurations)
|
|
.where(eq(externalCsvConfigurations.projectId, duplicate.projectId))
|
|
.get();
|
|
assert.ok(duplicatedConfiguration);
|
|
assert.notEqual(duplicatedConfiguration.id, initial.id);
|
|
assert.equal(duplicatedConfiguration.projectId, duplicate.projectId);
|
|
assert.deepEqual(duplicatedConfiguration.configuration, initial.configuration);
|
|
} finally {
|
|
context.close();
|
|
}
|
|
});
|
|
|
|
it("restores and undoes the complete configuration through snapshot history", () => {
|
|
const context = createTestDatabase();
|
|
try {
|
|
const configurationRepository = new ExternalCsvConfigurationProjectCommandRepository(context.db);
|
|
const initial = snapshot();
|
|
configurationRepository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 0,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(null, initial),
|
|
});
|
|
const configuredState = readProjectStateSnapshot(context.db, "project-1");
|
|
assert.ok(configuredState);
|
|
configurationRepository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 1,
|
|
source: "user",
|
|
command: createExternalCsvConfigurationUpdateProjectCommand(initial, null),
|
|
});
|
|
const emptyState = readProjectStateSnapshot(context.db, "project-1");
|
|
assert.ok(emptyState);
|
|
|
|
const restoreRepository = new ProjectStateRestoreCommandRepository(context.db);
|
|
const restored = restoreRepository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 2,
|
|
source: "user",
|
|
command: createProjectStateRestoreCommand(
|
|
emptyState.payloadSha256,
|
|
configuredState.state
|
|
),
|
|
});
|
|
assert.deepEqual(readConfiguration(context), initial);
|
|
|
|
restoreRepository.execute({
|
|
projectId: "project-1",
|
|
expectedRevision: 3,
|
|
source: "undo",
|
|
historyTargetChangeSetId: restored.revision.changeSetId,
|
|
command: restored.inverse,
|
|
});
|
|
assert.equal(readConfiguration(context), null);
|
|
} finally {
|
|
context.close();
|
|
}
|
|
});
|
|
});
|
|
|
|
function readConfiguration(context: DatabaseContext) {
|
|
return context.db
|
|
.select()
|
|
.from(externalCsvConfigurations)
|
|
.where(eq(externalCsvConfigurations.projectId, "project-1"))
|
|
.get() ?? null;
|
|
}
|