import assert from "node:assert/strict"; import path from "node:path"; import { describe, it } from "node:test"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { createDatabaseContext } from "../src/db/database-context.js"; import { ExternalInitialImportProjectCommandRepository } from "../src/db/repositories/external-initial-import-project-command.repository.js"; import { ExternalModelStateRepository } from "../src/db/repositories/external-model-state.repository.js"; import { ExternalObjectPlanningProjectCommandRepository } from "../src/db/repositories/external-object-planning-project-command.repository.js"; import { externalModelConfigurations } from "../src/db/schema/external-model-configurations.js"; import { projects } from "../src/db/schema/projects.js"; import { createEmptyExternalModelState, createExternalInitialImportProjectCommand } from "../src/domain/models/external-initial-import-project-command.model.js"; import { createExternalObjectPlanningUpdateProjectCommand } from "../src/domain/models/external-object-planning-project-command.model.js"; import { createExternalXlsxInitialImportPlan } from "../src/external-model/application/external-xlsx-initial-import.js"; import { buildExternalXlsxInitialImportTarget } from "../src/external-model/application/external-xlsx-initial-import-target.js"; import { createDefaultExternalModelConfiguration } from "../src/external-model/configuration/external-model-configuration.js"; import { createRevitXlsxFixture, externalXlsxTestConfiguration, firstWorksharingId } from "./fixtures/revit-xlsx-fixtures.js"; const deviceTypeA = "10000000-0000-4000-8000-000000000001"; const deviceTypeB = "10000000-0000-4000-8000-000000000002"; function configured() { return { ...createDefaultExternalModelConfiguration(), ...externalXlsxTestConfiguration, deviceTypes: [ { id: deviceTypeA, name: "Steckdose", category: "single_phase" as const, defaults: { powerPerUnitW: null, simultaneityFactor: null, cosPhi: 0.9, costGroup: "440" } }, { id: deviceTypeB, name: "Leuchte", category: "lighting" as const, defaults: { powerPerUnitW: 40, simultaneityFactor: 0.8, cosPhi: null, costGroup: null } }, ], familyTypeRules: [ { id: "20000000-0000-4000-8000-000000000001", exactFamilyAndType: "Steckdose: Standard", deviceTypeId: deviceTypeA, quantityRule: "socketCount" as const }, { id: "20000000-0000-4000-8000-000000000002", exactFamilyAndType: "Leuchte: Decke", deviceTypeId: deviceTypeB, quantityRule: "one" as const }, ], }; } describe("Linear XLSX initial import", () => { it("plans exact families, skips suspicious rows and defaults missing GZF to one", async () => { const { plan } = await createExternalXlsxInitialImportPlan({ fileName: "linear.xlsx", bytes: await createRevitXlsxFixture(), configuration: configured(), catalogs: { floors: [], rooms: [], distributionBoards: [], projectDevices: [] } }); assert.equal(plan.objectCount, 2); assert.equal(plan.suspectObjectCount, 1); assert.equal(plan.issueCounts.unknownFamilyAndType, 0); assert.equal(plan.objects[0].worksharingId, firstWorksharingId); assert.equal(plan.objects[0].suggestedPlanningValues.effectiveQuantity, 2); assert.equal(plan.objects[0].suggestedPlanningValues.simultaneityFactor, 1); assert.equal(plan.objects[1].suggestedPlanningValues.simultaneityFactor, 0.8); }); it("commits atomically and undo/redo uses stored states", async () => { const context = createDatabaseContext(":memory:"); migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") }); context.db.insert(projects).values({ id: "project-1", name: "Projekt" }).run(); const configuration = configured(); const configurationSnapshot = { id: "config-1", projectId: "project-1", configuration }; context.db.insert(externalModelConfigurations).values(configurationSnapshot).run(); try { const bytes = await createRevitXlsxFixture(); const { plan, document } = await createExternalXlsxInitialImportPlan({ fileName: "linear.xlsx", bytes, configuration, catalogs: { floors: [], rooms: [], distributionBoards: [], projectDevices: [] } }); let sequence = 0; const target = buildExternalXlsxInitialImportTarget({ projectId: "project-1", expectedRevision: 0, sourceName: "Revit", importedAtIso: "2026-09-30T10:00:00.000Z", configuration, originalContentBase64: Buffer.from(bytes).toString("base64"), document, plan, roomDecisions: plan.sourceRooms.map((room) => ({ sourceRoomKey: room.sourceRoomKey, roomId: null, defaultDistributionBoardId: null })), familyProjectDeviceDecisions: plan.familyGroups.map((family) => ({ familyAndType: family.familyAndType, projectDeviceId: null })), objectsWithoutRoomDefaultDistributionBoardId: null, createId: () => `30000000-0000-4000-8000-${String(++sequence).padStart(12, "0")}` }); const repository = new ExternalInitialImportProjectCommandRepository(context.db); const inserted = repository.execute({ projectId: "project-1", expectedRevision: 0, source: "user", command: createExternalInitialImportProjectCommand(configurationSnapshot, configurationSnapshot, createEmptyExternalModelState(), target) }); assert.equal(inserted.revision.revisionNumber, 1); assert.equal(new ExternalModelStateRepository(context.db).getByProject("project-1").state.objects.length, 2); const undone = repository.execute({ projectId: "project-1", expectedRevision: 1, source: "undo", historyTargetChangeSetId: inserted.revision.changeSetId, command: inserted.inverse }); assert.equal(new ExternalModelStateRepository(context.db).getByProject("project-1").state.source, null); repository.execute({ projectId: "project-1", expectedRevision: 2, source: "redo", historyTargetChangeSetId: inserted.revision.changeSetId, command: undone.inverse }); const redone = new ExternalModelStateRepository(context.db).getByProject("project-1").state; assert.deepEqual(redone.objects.map((object) => object.worksharingId), plan.objects.map((object) => object.worksharingId).sort()); const expectedObject = redone.objects[0]; const targetObject = { ...expectedObject, planningValues: { ...expectedObject.planningValues, simultaneityFactor: 0.5 }, overriddenFields: [...expectedObject.overriddenFields, "simultaneityFactor" as const] }; const planningRepository = new ExternalObjectPlanningProjectCommandRepository(context.db); const planned = planningRepository.execute({ projectId: "project-1", expectedRevision: 3, source: "user", command: createExternalObjectPlanningUpdateProjectCommand([{ expected: expectedObject, target: targetObject }]) }); assert.equal(new ExternalModelStateRepository(context.db).getByProject("project-1").state.objects[0].planningValues.simultaneityFactor, 0.5); planningRepository.execute({ projectId: "project-1", expectedRevision: 4, source: "undo", historyTargetChangeSetId: planned.revision.changeSetId, command: planned.inverse }); assert.equal(new ExternalModelStateRepository(context.db).getByProject("project-1").state.objects[0].planningValues.simultaneityFactor, 1); } finally { context.close(); } }); });