import assert from "node:assert/strict"; import path from "node:path"; import { performance } from "node:perf_hooks"; 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 { ExternalFollowUpImportProjectCommandRepository } from "../src/db/repositories/external-follow-up-import-project-command.repository.js"; import { ExternalModelStateRepository } from "../src/db/repositories/external-model-state.repository.js"; import { ProjectSnapshotRepository } from "../src/db/repositories/project-snapshot.repository.js"; import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js"; import { ProjectTransferRepository } from "../src/db/repositories/project-transfer.repository.js"; import { readProjectStateSnapshot } from "../src/db/repositories/project-state-snapshot.persistence.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 { createExternalFollowUpImportProjectCommand } from "../src/domain/models/external-follow-up-import-project-command.model.js"; import { createExternalXlsxFollowUpImportPlan } from "../src/external-model/application/external-xlsx-follow-up-import.js"; import { buildExternalXlsxFollowUpTarget } from "../src/external-model/application/external-xlsx-follow-up-target.js"; import { buildExternalXlsxInitialImportTarget } from "../src/external-model/application/external-xlsx-initial-import-target.js"; import { createExternalXlsxInitialImportPlan } from "../src/external-model/application/external-xlsx-initial-import.js"; import { createDefaultExternalModelConfiguration } from "../src/external-model/configuration/external-model-configuration.js"; import { createVirtualRevitObjectWindow, filterAndSortRevitObjects } from "../src/frontend/components/revit-object-table-model.js"; import type { ExternalProjectObjectDto } from "../src/frontend/types.js"; import { createLargeRevitXlsxFixture, externalXlsxTestConfiguration } from "./fixtures/revit-xlsx-fixtures.js"; const objectCount = 5_000; const deviceTypeId = "10000000-0000-4000-8000-000000000001"; const configuration = { ...createDefaultExternalModelConfiguration(), ...externalXlsxTestConfiguration, deviceTypes: [{ id: deviceTypeId, name: "Steckdose", category: "single_phase" as const, defaults: { powerPerUnitW: 125, simultaneityFactor: 1, cosPhi: 0.9, costGroup: "440" } }], familyTypeRules: [{ id: "20000000-0000-4000-8000-000000000001", exactFamilyAndType: "Steckdose: Standard", deviceTypeId, quantityRule: "socketCount" as const }], }; describe("Linear XLSX scale", () => { it("imports, persists, filters and virtualizes 5,000 synthetic objects within bounded resources", async () => { const heapBefore = process.memoryUsage().heapUsed; const bytes = await createLargeRevitXlsxFixture(objectCount); const parseStarted = performance.now(); const { plan, document } = await createExternalXlsxInitialImportPlan({ fileName: "large.xlsx", bytes, configuration, catalogs: { floors: [], rooms: [], distributionBoards: [], projectDevices: [] } }); const parseMs = performance.now() - parseStarted; assert.equal(plan.objectCount, objectCount); assert.ok(bytes.byteLength < 10 * 1024 * 1024); let sequence = 0; const target = buildExternalXlsxInitialImportTarget({ projectId: "project-scale", expectedRevision: 0, sourceName: "Revit", importedAtIso: "2026-09-30T12: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 configurationSnapshot = { id: "config-scale", projectId: "project-scale", configuration }; const command = createExternalInitialImportProjectCommand(configurationSnapshot, configurationSnapshot, createEmptyExternalModelState(), target); const commandBytes = Buffer.byteLength(JSON.stringify(command)); const context = createDatabaseContext(":memory:"); try { migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") }); context.db.insert(projects).values({ id: "project-scale", name: "Lasttest" }).run(); context.db.insert(externalModelConfigurations).values(configurationSnapshot).run(); const transactionStarted = performance.now(); new ExternalInitialImportProjectCommandRepository(context.db).execute({ projectId: "project-scale", expectedRevision: 0, source: "user", command }); const transactionMs = performance.now() - transactionStarted; let snapshot = readProjectStateSnapshot(context.db, "project-scale"); assert.ok(snapshot); assert.equal(snapshot.state.externalModel.objects.length, objectCount); const currentState = new ExternalModelStateRepository(context.db).getByProject("project-scale").state; const returnValues = new Map(currentState.objects.map((object) => [object.id, { circuitCombined: object.acceptedSourceValues.circuitCombined, boardName: object.acceptedSourceValues.boardName, circuitIdentifier: object.acceptedSourceValues.circuitIdentifier, designation: object.acceptedSourceValues.designation, electricalPowerW: object.acceptedSourceValues.electricalPowerW, voltageV: object.acceptedSourceValues.voltageV, currentA: object.acceptedSourceValues.currentA }])); const followParseStarted = performance.now(); const follow = await createExternalXlsxFollowUpImportPlan({ fileName: "large-follow.xlsx", bytes, configuration, catalogs: { floors: [], rooms: [], distributionBoards: [], projectDevices: [] }, currentState, currentReturnValuesByObjectId: returnValues }); const followParseMs = performance.now() - followParseStarted; const followTarget = buildExternalXlsxFollowUpTarget({ projectId: "project-scale", expectedRevision: 1, importedAtIso: "2026-09-30T13:00:00.000Z", configuration, originalContentBase64: Buffer.from(bytes).toString("base64"), document: follow.document, plan: follow.plan, currentState, sourceChangeDecisions: [], roomDecisions: [], familyProjectDeviceDecisions: [], objectsWithoutRoomDefaultDistributionBoardId: null, rowsById: new Map(), createId: () => `40000000-0000-4000-8000-${String(++sequence).padStart(12, "0")}` }); const followCommand = createExternalFollowUpImportProjectCommand({ expectedConfiguration: configurationSnapshot, targetConfiguration: configurationSnapshot, expectedState: currentState, targetState: followTarget.targetState, rows: followTarget.rowTransitions }); const followTransactionStarted = performance.now(); new ExternalFollowUpImportProjectCommandRepository(context.db).execute({ projectId: "project-scale", expectedRevision: 1, source: "user", command: followCommand }); const followTransactionMs = performance.now() - followTransactionStarted; const followedState = new ExternalModelStateRepository(context.db).getByProject("project-scale").state; assert.equal(followedState.importBatches.length, 2); assert.equal(followedState.importBatches[0]!.document, null); assert.equal(followedState.objects.length, objectCount); const namedSnapshot = new ProjectSnapshotRepository(context.db).createNamed({ projectId: "project-scale", expectedRevision: 2, name: "Lasttest" }); assert.ok(namedSnapshot); const preparedRestore = new ProjectSnapshotRepository(context.db).prepareRestore("project-scale", namedSnapshot.id); assert.ok(preparedRestore); new ProjectStateRestoreCommandRepository(context.db).execute({ projectId: "project-scale", expectedRevision: 2, source: "restore", command: preparedRestore.command }); const transferRepository = new ProjectTransferRepository(context.db); const transfer = transferRepository.exportProject("project-scale"); assert.ok(transfer); const duplicate = transferRepository.importDuplicate(transfer); assert.equal(readProjectStateSnapshot(context.db, duplicate.projectId)!.state.externalModel.objects.length, objectCount); snapshot = readProjectStateSnapshot(context.db, "project-scale"); assert.ok(snapshot); const clientRows: ExternalProjectObjectDto[] = target.objects.map((object) => ({ id: object.id, worksharingId: object.worksharingId, ifcGuid: object.ifcGuid, deviceTypeId: object.deviceTypeId, familyAndType: object.acceptedSourceValues.familyAndType, selectionMarker: String(object.acceptedSourceValues.selectionMarker ?? ""), sourceCircuitIdentifier: "", sourceRoomNumber: String(object.acceptedSourceValues.roomNumber ?? ""), sourceRoomName: String(object.acceptedSourceValues.roomName ?? ""), roomId: null, distributionBoardId: object.distributionBoardId, linkedProjectDeviceId: object.linkedProjectDeviceId, circuitDeviceRowId: object.circuitDeviceRowId, presenceStatus: object.presenceStatus, planningValues: object.planningValues })); const clientStarted = performance.now(); const filtered = filterAndSortRevitObjects(clientRows, { search: "Raum 42", boardFilter: "all", assignmentFilter: "all", sort: "worksharing" }); const window = createVirtualRevitObjectWindow(filtered, 4_400, 44, 520); const clientMs = performance.now() - clientStarted; assert.ok(filtered.length > 0); assert.ok(window.rows.length <= 22); const heapDeltaBytes = Math.max(0, process.memoryUsage().heapUsed - heapBefore); const snapshotBytes = Buffer.byteLength(snapshot.payloadJson); assert.ok(parseMs < 15_000, `XLSX parse took ${parseMs.toFixed(0)} ms`); assert.ok(transactionMs < 15_000, `Import transaction took ${transactionMs.toFixed(0)} ms`); assert.ok(followParseMs < 15_000, `Follow-up parse took ${followParseMs.toFixed(0)} ms`); assert.ok(followTransactionMs < 15_000, `Follow-up transaction took ${followTransactionMs.toFixed(0)} ms`); assert.ok(clientMs < 1_000, `Client projection took ${clientMs.toFixed(0)} ms`); assert.ok(heapDeltaBytes < 768 * 1024 * 1024, `Heap grew by ${heapDeltaBytes} bytes`); assert.ok(commandBytes < 100 * 1024 * 1024); console.info("R10 scale metrics", { objectCount, workbookBytes: bytes.byteLength, commandBytes, snapshotBytes, parseMs: Math.round(parseMs), transactionMs: Math.round(transactionMs), followParseMs: Math.round(followParseMs), followTransactionMs: Math.round(followTransactionMs), clientMs: Math.round(clientMs), heapDeltaBytes }); } finally { context.close(); } }); });