/
Starolat
/
DeepDive
Обзор
Документация
Войти
/
Starolat
/
DeepDive
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/js-data-engine-phase5.node-test.js
574 строки
16 KB
Starolat Sergei
Фаза 6: полное удаление SQL.js и переход на JS Data Engine
28 июн 2026, 10:02
28 июн 2026, 10:02
ff2000f
Код
Авторство
О чём код?
// @ts-check /** * @fileoverview Phase 5 JS Data Engine integration tests * Run: node tests/js-data-engine-phase5.node-test.js * @version 1.0.0 * * Проверяет JS-пути в legacy-потребителях данных: * - FocusedGantt * - MtoSummaryPanel (через MtoJsQueryEngine) * - volumeProgress / volumeSCurve / volumeTable через SummaryDataService */ import assert from "assert"; import { fileURLToPath } from "url"; import path from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); if (typeof global !== "undefined") { global.self = global; } if (typeof global.window === "undefined") { global.window = {}; } global.window.ENV = {}; global.window.echarts = { init: () => ({ setOption: () => {}, dispose: () => {}, resize: () => {}, on: () => {}, off: () => {}, getZr: () => ({ on: () => {}, off: () => {} }), }), util: { map: () => [] }, }; if (typeof global.document === "undefined") { global.document = { addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => true, }; } class FakeHTMLElementBase { constructor() { this.isConnected = true; this.shadowRoot = null; this._attributes = new Map(); this._listeners = new Map(); } addEventListener(name, fn) { if (!this._listeners.has(name)) this._listeners.set(name, []); this._listeners.get(name).push(fn); } removeEventListener(name, fn) { const list = this._listeners.get(name) || []; const idx = list.indexOf(fn); if (idx >= 0) list.splice(idx, 1); } getAttribute(name) { return this._attributes.get(name) ?? null; } setAttribute(name, value) { this._attributes.set(name, String(value)); } hasAttribute(name) { return this._attributes.has(name); } dispatchEvent(event) { const list = this._listeners.get(event.type) || []; for (const fn of list) fn(event); return true; } attachShadow() { this.shadowRoot = { innerHTML: "", querySelector: () => null, querySelectorAll: () => [], appendChild: (n) => n, }; return this.shadowRoot; } } global.HTMLElement = FakeHTMLElementBase; const customElementRegistry = new Map(); global.customElements = { get: (name) => customElementRegistry.get(name), define: (name, ctor) => customElementRegistry.set(name, ctor), }; global.ResizeObserver = class { observe() {} disconnect() {} }; global.IntersectionObserver = class { observe() {} disconnect() {} }; const { default: dataIndexer } = await import("../js/services/DataIndexer.js"); const { default: summaryDataService } = await import("../js/services/SummaryDataService.js"); const { default: mtoJsQueryEngine } = await import("../js/services/MtoJsQueryEngine.js"); await import("../js/components/FocusedGantt.js"); const FocusedGantt = global.customElements.get("focused-gantt"); 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, 4) .map((s) => ` ${s}`) .join("\n"), ); } } } console.log("\nJS Data Engine Phase 5 Tests\n"); // ============================================================================= // Fixture // ============================================================================= function createApiData(scheduleType = "current") { const isTarget = scheduleType === "target"; const idOffset = isTarget ? 100 : 0; const assignOffset = isTarget ? 10000 : 0; 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 + idOffset, projectId: 1, wbsId: 10, activityCode: "A001", taskName: "Concrete work", taskType: "Task", duration: 10, totalFloat: 5, freeFloat: 0, startDate: "2024-03-04", endDate: "2024-03-15", }, { id: 2 + idOffset, projectId: 1, wbsId: 11, activityCode: "A002", taskName: "RKD development", taskType: "Task", duration: 8, totalFloat: 2, freeFloat: 0, startDate: "2024-03-04", endDate: "2024-03-13", }, { id: 3 + idOffset, projectId: 1, wbsId: 11, activityCode: "A003", taskName: "Procurement contract", taskType: "Task", duration: 6, totalFloat: 8, freeFloat: 0, startDate: "2024-03-04", endDate: "2024-03-11", }, ], codeTypes: [ { typeId: 1, name: "Вид работ", maxLength: 20, scope: "AS_Global" }, { typeId: 2, name: "ЦПС_Отчетность", maxLength: 20, scope: "AS_Global" }, { typeId: 3, name: "ЦПС_Вид работ для отчета", maxLength: 30, scope: "AS_Global", }, { typeId: 4, name: "Позиция_ГП_1", maxLength: 30, scope: "AS_Global" }, ], codeValues: [ { valueId: 10, typeId: 1, shortName: "СМР", description: "Строительно-монтажные", sortOrder: 1, }, { valueId: 11, typeId: 1, shortName: "МТО", description: "Материально-техническое обеспечение", sortOrder: 2, }, { valueId: 20, typeId: 2, shortName: "S", description: "Отчетность", sortOrder: 1, }, { valueId: 30, typeId: 3, shortName: "Кон.ОТО", description: "Конкурс ОТО", sortOrder: 1, }, { valueId: 31, typeId: 3, shortName: "РКД", description: "Разработка РКД", sortOrder: 2, }, { valueId: 32, typeId: 3, shortName: "ДостОТО", description: "Доставка ОТО", sortOrder: 3, }, { valueId: 40, typeId: 4, shortName: "POS1", description: "Позиция 1", sortOrder: 1, }, { valueId: 41, typeId: 4, shortName: "POS2", description: "Позиция 2", sortOrder: 2, }, ], codeAssignments: [ { activityId: 1 + idOffset, codeTypeId: 1, codeValueId: 10 }, // СМР { activityId: 2 + idOffset, codeTypeId: 2, codeValueId: 20 }, // S { activityId: 2 + idOffset, codeTypeId: 3, codeValueId: 31 }, // РКД { activityId: 2 + idOffset, codeTypeId: 4, codeValueId: 40 }, // Pos1 { activityId: 3 + idOffset, codeTypeId: 2, codeValueId: 20 }, // S { activityId: 3 + idOffset, codeTypeId: 3, codeValueId: 30 }, // Кон.ОТО { activityId: 3 + idOffset, codeTypeId: 4, codeValueId: 41 }, // Pos2 ], wbs: [ { wbsId: 10, projectId: 1, parentId: null, wbsShortName: "WBS1", wbsName: "Package 1", seqNum: 0, }, { wbsId: 11, projectId: 1, parentId: 10, wbsShortName: "WBS1.1", wbsName: "Sub Package", seqNum: 1, }, ], umeasures: [{ unitId: 2, unitName: "m3" }], 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 + assignOffset, activityId: 1 + idOffset, resourceId: 101, resourceType: "material", targetUnits: isTarget ? 10 : 10, actualUnits: isTarget ? 0 : 4, remainingUnits: isTarget ? 10 : 6, }, { assignmentId: 2000 + assignOffset, activityId: 2 + idOffset, resourceId: 100, resourceType: "labor", targetUnits: 100, actualUnits: isTarget ? 0 : 30, remainingUnits: isTarget ? 100 : 70, }, { assignmentId: 3000 + assignOffset, activityId: 3 + idOffset, resourceId: 100, resourceType: "labor", targetUnits: 50, actualUnits: isTarget ? 0 : 20, remainingUnits: isTarget ? 50 : 30, }, ], resourceAssignmentSpread: [ { ResourceAssignmentObjectId: 1000 + assignOffset, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: isTarget ? 0 : 2, RemainingUnits: isTarget ? 5 : 3, PlannedUnits: isTarget ? 5 : 5, }, { StartDate: "2024-03-11", EndDate: "2024-03-17", ActualUnits: isTarget ? 0 : 2, RemainingUnits: isTarget ? 5 : 3, PlannedUnits: isTarget ? 5 : 5, }, ], }, { ResourceAssignmentObjectId: 2000 + assignOffset, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: isTarget ? 0 : 15, RemainingUnits: isTarget ? 50 : 35, PlannedUnits: isTarget ? 50 : 50, }, { StartDate: "2024-03-11", EndDate: "2024-03-17", ActualUnits: isTarget ? 0 : 15, RemainingUnits: isTarget ? 50 : 35, PlannedUnits: isTarget ? 50 : 50, }, ], }, { ResourceAssignmentObjectId: 3000 + assignOffset, Period: [ { StartDate: "2024-03-04", EndDate: "2024-03-10", ActualUnits: isTarget ? 0 : 10, RemainingUnits: isTarget ? 25 : 15, PlannedUnits: isTarget ? 25 : 25, }, { StartDate: "2024-03-11", EndDate: "2024-03-17", ActualUnits: isTarget ? 0 : 10, RemainingUnits: isTarget ? 25 : 15, PlannedUnits: isTarget ? 25 : 25, }, ], }, ], relationships: isTarget ? [] : [ { relationshipId: 1, activityId: 2, predecessorId: 1, relationshipType: "FS", lagDays: 0, }, ], }; } let dbId; await test("DataIndexer builds current+target indexes from API", async () => { dbId = await dataIndexer.buildIndexesFromAPI( createApiData("current"), "Phase5-Test", { scheduleType: "current", }, ); await dataIndexer.buildIndexesFromAPI( createApiData("target"), "Phase5-Test", { scheduleType: "target", existingDatabaseId: dbId, }, ); const indexes = dataIndexer.getIndexes(dbId); assert.ok(indexes, "indexes exist"); assert.strictEqual( indexes.activitiesById.size, 6, "6 activities indexed (3 current + 3 target)", ); }); // ============================================================================= // Volume widgets // ============================================================================= await test("SummaryDataService.buildWidgetData(volumeProgress) uses JS Engine", async () => { const data = await summaryDataService.buildWidgetData( "volumeProgress", dbId, {}, ); assert.ok(data, "volumeProgress data returned"); assert.strictEqual( data.totalPlan, 10, "total plan = 10 target material units", ); assert.strictEqual(data.totalActual, 4, "total actual = 4"); assert.strictEqual(data.totalRemaining, 6, "total remaining = 6"); assert.strictEqual(data.items.length, 1, "one material resource"); assert.strictEqual(data.items[0].resourceName, "Cement"); assert.strictEqual(data.items[0].unitName, "m3"); assert.strictEqual(data.percentComplete, 40); }); await test("SummaryDataService.buildWidgetData(volumeSCurve) uses JS Engine", async () => { const data = await summaryDataService.buildWidgetData( "volumeSCurve", dbId, {}, ); assert.ok(data, "volumeSCurve data returned"); assert.ok(Array.isArray(data.xAxis), "xAxis is array"); assert.ok(Array.isArray(data.categories), "categories is array"); assert.strictEqual(data.totalPlanSum, 10, "total plan sum"); assert.strictEqual(data.totalActualSum, 4, "total actual sum"); assert.strictEqual(data.reportDate, "2024-03-10"); }); await test("SummaryDataService.buildWidgetData(volumeTable) uses JS Engine", async () => { const data = await summaryDataService.buildWidgetData( "volumeTable", dbId, {}, ); assert.ok(data, "volumeTable data returned"); assert.ok(Array.isArray(data), "table data is array"); assert.strictEqual(data.length, 2, "current + target material rows"); assert.ok(data.find((r) => r.scheduleType === "current")); assert.ok(data.find((r) => r.scheduleType === "target")); const currentRow = data.find((r) => r.scheduleType === "current"); assert.strictEqual(currentRow.resourceName, "Cement"); assert.strictEqual(currentRow.planQty, 10); assert.strictEqual(currentRow.actualQty, 4); }); // ============================================================================= // FocusedGantt // ============================================================================= await test("FocusedGantt._fetchActivities uses JS Engine", async () => { const gantt = new FocusedGantt(); gantt._activityIds = [1, 2]; gantt.setAttribute("schedule-type", "current"); const activities = await gantt._fetchActivities(dbId); assert.strictEqual(activities.length, 2); assert.ok(activities.find((a) => a.activityCode === "A001")); assert.ok(activities.find((a) => a.activityCode === "A002")); }); await test("FocusedGantt._fetchRelationships uses JS Engine", async () => { const gantt = new FocusedGantt(); gantt._activityIds = [1, 2]; gantt.setAttribute("schedule-type", "current"); const rels = await gantt._fetchRelationships(dbId); assert.strictEqual(rels.length, 1); assert.strictEqual(rels[0].predecessorId, 1); assert.strictEqual(rels[0].successorId, 2); assert.strictEqual(rels[0].type, "FS"); }); await test("FocusedGantt._fetchDataDate uses JS Engine", async () => { const gantt = new FocusedGantt(); const dataDate = await gantt._fetchDataDate(dbId); assert.strictEqual(dataDate, "2024-03-10"); }); // ============================================================================= // MTO // ============================================================================= await test("MtoJsQueryEngine.getProjectDataDate returns data date", async () => { const dataDate = mtoJsQueryEngine.getProjectDataDate(dbId); assert.strictEqual(dataDate, "2024-03-10"); }); await test("MtoJsQueryEngine.getCategoryData returns gauge data for RKD", async () => { const data = await mtoJsQueryEngine.getCategoryData(dbId, { key: "rkd", title: "Разработка РКД", codeValues: ["РКД"], chartCategory: "RKD", }); assert.ok(data, "category data returned"); assert.strictEqual(data.totalPlan, 100, "RKD total plan"); assert.strictEqual(data.totalFact, 100, "RKD total fact"); assert.ok(data.weeklyDataTable.length > 0, "weekly table present"); }); await test("MtoJsQueryEngine.getChartsData returns bar-chart data by GP positions", async () => { const charts = await mtoJsQueryEngine.getChartsData(dbId, { categories: [ { key: "procurement", title: "Закупка", codeValues: ["Кон.ОТО"], chartCategory: "PROCUREMENT", }, { key: "rkd", title: "РКД", codeValues: ["РКД"], chartCategory: "RKD" }, ], reportDate: "2024-03-10", }); assert.ok(Array.isArray(charts), "charts array returned"); assert.strictEqual(charts.length, 2, "two GP positions"); assert.strictEqual(charts[0].title, "Позиция 1"); assert.strictEqual(charts[0].plan.length, 2); assert.strictEqual(charts[0].fact.length, 2); }); console.log(`\nPassed: ${passed}, Failed: ${failed}\n`); process.exit(failed > 0 ? 1 : 0);