/
Starolat
/
DeepDive
Обзор
Документация
Войти
/
Starolat
/
DeepDive
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/data-indexer.node-test.js
597 строк
17 KB
Starolat Sergei
feat: локальный критический путь (SYS-024) и календари Primavera в формате .dddb v2.7.0 (SYS-025)
18 июл 2026, 07:28
18 июл 2026, 07:28
2da8691
Код
Авторство
О чём код?
// @ts-check /** * @fileoverview Node.js standalone tests for DataIndexer (Phase 2) * Run: node tests/data-indexer.node-test.js * @version 0.1.0 */ import assert from "assert"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; // fzstd expects global.self in Node.js if (typeof global !== "undefined") { global.self = global; } import { DataIndexer } from "../js/services/DataIndexer.js"; import binarySerializer from "../js/services/BinarySerializer.js"; import binaryDeserializer from "../js/services/BinaryDeserializer.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); let passed = 0; let failed = 0; async function test(name, fn) { try { await fn(); passed++; console.log(` ✓ ${name}`); } catch (err) { failed++; console.error(` ✗ ${name}`); console.error(` ${err.message}`); if (err.stack) { console.error( err.stack .split("\n") .slice(1, 3) .map((s) => ` ${s}`) .join("\n"), ); } } } console.log("\nDataIndexer Tests\n"); // ============================================================================= // Helpers // ============================================================================= function createApiData() { return { projects: [ { projectId: 1, shortName: "TEST", name: "Test Project", startDate: "2024-01-01", endDate: "2024-12-31", status: "Active", dataDate: "2024-03-10", }, ], activities: [ { id: 1, projectId: 1, wbsId: 10, activityCode: "A001", taskName: "Activity One", taskType: "TT_Task", duration: 10, totalFloat: 2, freeFloat: 1, startDate: "2024-03-01", endDate: "2024-03-15", actualStartDate: "2024-03-02", actualEndDate: "2024-03-14", cstrType: "CS_MEOA", cstrDate: "2024-03-20", }, { id: 2, projectId: 1, wbsId: 10, activityCode: "A002", taskName: "Activity Two", taskType: "TT_LOE", duration: 5, totalFloat: 0, freeFloat: 0, startDate: "2024-04-01", endDate: "2024-04-05", }, ], codeTypes: [ { typeId: 100, name: "Location", maxLength: 20, scope: "AS_Global", }, ], codeValues: [ { valueId: 1000, typeId: 100, shortName: "ZONE_A", description: "Zone A (North)", color: "#FF6B6B", sortOrder: 1, }, ], codeAssignments: [ { activityId: 1, codeTypeId: 100, codeValueId: 1000, }, ], wbs: [ { wbsId: 10, projectId: 1, parentId: null, wbsShortName: "WBS1", wbsName: "Work Package 1", seqNum: 0, scheduleType: "current", }, { wbsId: 11, projectId: 1, parentId: 10, wbsShortName: "WBS1.1", wbsName: "Sub Package", seqNum: 1, scheduleType: "current", }, ], resources: [ { resourceId: 100, shortName: "WORKER", name: "Worker", type: "labor", unitId: 1, isActive: "Y", }, { resourceId: 101, shortName: "CEMENT", name: "Cement", type: "material", unitId: 2, isActive: "Y", }, ], resourceAssignments: [ { assignmentId: 1000, activityId: 1, resourceId: 100, resourceType: "labor", targetUnits: 80, actualUnits: 40, remainingUnits: 40, }, { assignmentId: 1001, activityId: 1, resourceId: 101, resourceType: "material", targetUnits: 100, actualUnits: 50, remainingUnits: 50, }, ], resourceAssignmentSpread: [ { ResourceAssignmentObjectId: 1000, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: 20, RemainingUnits: 20, PlannedUnits: 40, }, { StartDate: "2024-03-11", EndDate: "2024-03-17", ActualUnits: 20, RemainingUnits: 20, PlannedUnits: 40, }, ], }, { ResourceAssignmentObjectId: 1001, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: 30, RemainingUnits: 20, PlannedUnits: 50, }, ], }, ], relationships: [ { relationshipId: 500, activityId: 2, predecessorId: 1, relationshipType: "FS", lagDays: 1, }, ], umeasures: [ { unitId: 1, unitName: "Hours", unitShortName: "h" }, { unitId: 2, unitName: "Tons", unitShortName: "t" }, ], }; } function createTargetApiData() { return { projects: [ { projectId: 1, shortName: "TEST", name: "Test Project", startDate: "2024-01-01", endDate: "2024-12-31", status: "Active", dataDate: "2024-03-10", }, ], activities: [ { id: 101, projectId: 1, wbsId: 110, activityCode: "A001", taskName: "Activity One Target", taskType: "Task", duration: 12, totalFloat: 1, freeFloat: 0, startDate: "2024-03-01", endDate: "2024-03-17", }, ], codeTypes: [], codeValues: [], codeAssignments: [], wbs: [ { wbsId: 110, projectId: 1, parentId: null, wbsShortName: "WBS1-T", wbsName: "Work Package 1 Target", seqNum: 0, scheduleType: "target", }, ], resources: [ { resourceId: 100, shortName: "WORKER", name: "Worker", type: "labor", unitId: 1, isActive: "Y", }, ], resourceAssignments: [ { assignmentId: 2000, activityId: 101, resourceId: 100, resourceType: "labor", targetUnits: 90, actualUnits: 0, remainingUnits: 90, }, ], resourceAssignmentSpread: [ { ResourceAssignmentObjectId: 2000, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: 0, RemainingUnits: 45, PlannedUnits: 45, }, { StartDate: "2024-03-11", EndDate: "2024-03-17", ActualUnits: 0, RemainingUnits: 45, PlannedUnits: 45, }, ], }, ], relationships: [], umeasures: [], }; } // ============================================================================= // Tests // ============================================================================= await test("buildIndexesFromAPI creates DatabaseIndexes structure", async () => { const indexer = new DataIndexer(); const apiData = createApiData(); const dbId = await indexer.buildIndexesFromAPI(apiData, "Test DB", { scheduleType: "current", }); assert.strictEqual(typeof dbId, "string"); assert(dbId.startsWith("jsdb_")); const indexes = indexer.getIndexes(dbId); assert(indexes); assert.strictEqual(indexes.name, "Test DB"); assert.strictEqual(indexes.source, "api"); assert.strictEqual(indexes.activitiesById.size, 2); assert.strictEqual(indexes.projectsById.size, 1); assert.strictEqual(indexes.wbsById.size, 2); assert.strictEqual(indexes.codeTypeById.size, 1); assert.strictEqual(indexes.codeValueById.size, 1); assert.strictEqual(indexes.resourcesById.size, 2); assert.strictEqual(indexes.assignmentsById.size, 2); assert.strictEqual(indexes.relationships.length, 1); }); await test("buildIndexesFromAPI normalizes activities and WBS", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); const a1 = indexes.activitiesById.get(1); assert(a1); assert.strictEqual(a1.taskName, "Activity One"); assert.strictEqual(a1.scheduleType, "current"); assert.strictEqual(a1.taskType, "TT_Task"); assert.strictEqual(a1.freeFloat, 1); assert.strictEqual(a1.actualStartDate, "2024-03-02"); assert.strictEqual(a1.actualEndDate, "2024-03-14"); assert.strictEqual(a1.cstrType, "CS_MEOA"); assert.strictEqual(a1.cstrDate, "2024-03-20"); assert.strictEqual(a1.wbsId, 10); assert.strictEqual(a1.projectId, 1); const wbs = indexes.wbsById.get(10); assert(wbs); assert.strictEqual(wbs.code, "WBS1"); assert.strictEqual(wbs.level, 0); assert.strictEqual(wbs.scheduleType, "current"); const child = indexes.wbsById.get(11); assert(child); assert.strictEqual(child.parentId, 10); assert.strictEqual(child.level, 1); assert.strictEqual(child.scheduleType, "current"); assert(indexes.wbsTree.get(10)?.has(11)); }); await test("buildIndexesFromAPI builds code indexes", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); const value = indexes.codeValueById.get(1000); assert(value); assert.strictEqual(value.shortName, "ZONE_A"); assert.strictEqual(value.color, "#FF6B6B"); const activities = indexes.activitiesByCodeValue.get(1000); assert(activities); assert(activities.has(1)); const codesForA1 = indexes.activityCodesByType.get(1); assert(codesForA1); assert.strictEqual(codesForA1.get(100), 1000); }); await test("buildIndexesFromAPI builds spread columns and indexes", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); assert.strictEqual(indexes.spread.assignmentIds.length, 3); assert.deepStrictEqual( Array.from(indexes.spread.assignmentIds), [1000, 1000, 1001], ); const range = indexes.spreadIndexByAssignment.get(1000); assert(range); assert.strictEqual(range.end - range.start, 2); const weekRows = indexes.spreadIndexByWeek.get(2826); assert(weekRows); assert.strictEqual(weekRows.length, 2); }); await test("buildIndexesFromAPI calculates labor manhours only", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); const a1 = indexes.activitiesById.get(1); assert(a1); // labor assignment 1000: 20+20 actual + 20+20 remaining = 80 total assert.strictEqual(a1.actualManhours, 40); assert.strictEqual(a1.remainingManhours, 40); assert.strictEqual(a1.totalManhours, 80); }); await test("buildIndexesFromAPI builds weeks", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); assert(indexes.weeks.length > 0); assert(indexes.minWeekIndex <= indexes.maxWeekIndex); const first = indexes.weeks[0]; assert.strictEqual(first.weekStart, "2024-02-26"); // Monday before 2024-03-01 const dataDateWeek = indexes.weeks.find((w) => w.weekEnd === "2024-03-10"); assert(dataDateWeek); assert.strictEqual(dataDateWeek.isPast, false); const pastWeek = indexes.weeks.find((w) => w.weekEnd === "2024-03-03"); assert(pastWeek); assert.strictEqual(pastWeek.isPast, true); }); await test("buildIndexesFromAPI supports current+target pairs", async () => { const indexer = new DataIndexer(); const currentData = createApiData(); const dbId = await indexer.buildIndexesFromAPI(currentData, "Pair DB", { scheduleType: "current", }); const targetData = createTargetApiData(); await indexer.buildIndexesFromAPI(targetData, "Pair DB", { scheduleType: "target", existingDatabaseId: dbId, }); const indexes = indexer.getIndexes(dbId); assert.strictEqual(indexes.activitiesById.size, 3); assert.strictEqual(indexes.activitiesBySchedule.get("current")?.size, 2); assert.strictEqual(indexes.activitiesBySchedule.get("target")?.size, 1); // WBS scheduleType preserved from source data const rootWbs = indexes.wbsById.get(10); assert(rootWbs); assert.strictEqual(rootWbs.scheduleType, "current"); const targetActivity = indexes.activitiesById.get(101); assert(targetActivity); assert.strictEqual(targetActivity.scheduleType, "target"); assert.strictEqual(targetActivity.totalManhours, 90); // Current data preserved const currentActivity = indexes.activitiesById.get(1); assert(currentActivity); assert.strictEqual(currentActivity.scheduleType, "current"); }); await test("dropIndexes removes database", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); assert(indexer.getIndexes(dbId)); assert.strictEqual(indexer.dropIndexes(dbId), true); assert.strictEqual(indexer.getIndexes(dbId), null); }); await test("round-trip through BinarySerializer / buildIndexesFromBinary", async () => { const sourceIndexer = new DataIndexer(); const dbId = await sourceIndexer.buildIndexesFromAPI( createApiData(), "Round-trip DB", { scheduleType: "current" }, ); const sourceIndexes = sourceIndexer.getIndexes(dbId); assert(sourceIndexes); const buffer = await binarySerializer.serialize(sourceIndexes); assert(binaryDeserializer.isDDDB(buffer)); const targetIndexer = new DataIndexer(); const newDbId = await targetIndexer.buildIndexesFromBinary( buffer, "Round-trip DB", ); const indexes = targetIndexer.getIndexes(newDbId); assert(indexes); assert.strictEqual(indexes.activitiesById.size, 2); assert.strictEqual(indexes.projectsById.size, 1); assert.strictEqual(indexes.resourcesById.size, 2); assert.strictEqual(indexes.spread.assignmentIds.length, 3); const a1 = indexes.activitiesById.get(1); assert(a1); assert.strictEqual(a1.taskName, "Activity One"); assert.strictEqual(a1.totalManhours, 80); assert.strictEqual(a1.taskType, "TT_Task"); assert.strictEqual(a1.freeFloat, 1); assert.strictEqual(a1.actualStartDate, "2024-03-02"); assert.strictEqual(a1.actualEndDate, "2024-03-14"); assert.strictEqual(a1.cstrType, "CS_MEOA"); assert.strictEqual(a1.cstrDate, "2024-03-20"); const rootWbs = indexes.wbsById.get(10); assert(rootWbs); assert.strictEqual(rootWbs.scheduleType, "current"); const range = indexes.spreadIndexByAssignment.get(1000); assert(range); assert.strictEqual(range.end - range.start, 2); }); await test("legacy v2.2.0 file works via buildIndexesFromBinary", async () => { const fixturePath = path.join( __dirname, "fixtures", "demo_legacy_v220.dddb.zst", ); const compressed = new Uint8Array(fs.readFileSync(fixturePath)); const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromBinary( compressed.buffer.slice( compressed.byteOffset, compressed.byteOffset + compressed.byteLength, ), "Converted DB", ); const indexes = indexer.getIndexes(dbId); assert(indexes); assert.strictEqual(indexes.activitiesById.size, 3); assert.strictEqual(indexes.wbsById.size, 2); assert.strictEqual(indexes.codeTypeById.size, 2); assert.strictEqual(indexes.resourcesById.size, 3); }); await test("getDatabaseMetadata returns correct counts", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const meta = indexer.getDatabaseMetadata(dbId); assert(meta); assert.strictEqual(meta.schemaVersion, "2.0-js"); assert.strictEqual(meta.activityCount, 2); assert.strictEqual(meta.projectCount, 1); assert.strictEqual(meta.projectName, "Test Project"); }); await test("buildIndexesFromAPI initializes empty material delivery indexes", async () => { const indexer = new DataIndexer(); const dbId = await indexer.buildIndexesFromAPI(createApiData(), "Test DB"); const indexes = indexer.getIndexes(dbId); assert(indexes); assert(indexes.materialDeliveriesById instanceof Map); assert.strictEqual(indexes.materialDeliveriesById.size, 0); assert(indexes.materialDeliveriesByActivity instanceof Map); assert(indexes.materialDeliveriesByResource instanceof Map); assert(indexes.materialDeliveriesByMaterialCode instanceof Map); assert(indexes.materialDeliveryQtyByWeek instanceof Map); assert(indexes.materialDeliveryQtyByMaterialWeek instanceof Map); }); // ============================================================================= // Summary // ============================================================================= console.log(`\n${passed} passed, ${failed} failed`); process.exit(failed > 0 ? 1 : 0);