bulltrackers-module 1.0.735 → 1.0.736
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/functions/computation-system-v2/config/bulltrackers.config.js +75 -5
- package/functions/computation-system-v2/framework/data/DataFetcher.js +107 -105
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +357 -150
- package/functions/computation-system-v2/framework/execution/RemoteTaskRunner.js +327 -0
- package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +9 -4
- package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +9 -21
- package/functions/computation-system-v2/framework/index.js +10 -3
- package/functions/computation-system-v2/framework/lineage/LineageTracker.js +53 -57
- package/functions/computation-system-v2/framework/monitoring/Profiler.js +54 -52
- package/functions/computation-system-v2/framework/resilience/Checkpointer.js +173 -27
- package/functions/computation-system-v2/framework/storage/StorageManager.js +419 -187
- package/functions/computation-system-v2/handlers/index.js +10 -1
- package/functions/computation-system-v2/handlers/scheduler.js +85 -193
- package/functions/computation-system-v2/handlers/worker.js +242 -0
- package/functions/computation-system-v2/test/analyze-results.js +238 -0
- package/functions/computation-system-v2/test/{test-dispatcher.js → other/test-dispatcher.js} +6 -6
- package/functions/computation-system-v2/test/{test-framework.js → other/test-framework.js} +14 -14
- package/functions/computation-system-v2/test/{test-real-execution.js → other/test-real-execution.js} +1 -1
- package/functions/computation-system-v2/test/{test-real-integration.js → other/test-real-integration.js} +3 -3
- package/functions/computation-system-v2/test/{test-refactor-e2e.js → other/test-refactor-e2e.js} +3 -3
- package/functions/computation-system-v2/test/{test-risk-metrics-computation.js → other/test-risk-metrics-computation.js} +4 -4
- package/functions/computation-system-v2/test/{test-scheduler.js → other/test-scheduler.js} +1 -1
- package/functions/computation-system-v2/test/{test-storage.js → other/test-storage.js} +2 -2
- package/functions/computation-system-v2/test/run-pipeline-test.js +554 -0
- package/functions/computation-system-v2/test/test-worker-pool.js +494 -0
- package/package.json +1 -1
- package/functions/computation-system-v2/computations/TestComputation.js +0 -46
- /package/functions/computation-system-v2/test/{test-results.json → other/test-results.json} +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Worker Pool Test Harness
|
|
3
|
+
*
|
|
4
|
+
* Tests the serverless worker pool functionality in LOCAL MODE.
|
|
5
|
+
* No GCS or HTTP required - workers run in-process.
|
|
6
|
+
*
|
|
7
|
+
* USAGE:
|
|
8
|
+
* node test-worker-pool.js [computation-name]
|
|
9
|
+
*
|
|
10
|
+
* EXAMPLES:
|
|
11
|
+
* node test-worker-pool.js # Test with mock data
|
|
12
|
+
* node test-worker-pool.js UserPortfolioSummary # Test specific computation
|
|
13
|
+
*
|
|
14
|
+
* FEATURES:
|
|
15
|
+
* - Tests worker handler directly (no HTTP)
|
|
16
|
+
* - Tests RemoteTaskRunner in local mode
|
|
17
|
+
* - Tests full Orchestrator with worker pool
|
|
18
|
+
* - Measures performance and validates results
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// Set local mode BEFORE importing modules
|
|
24
|
+
process.env.WORKER_LOCAL_MODE = 'true';
|
|
25
|
+
process.env.WORKER_POOL_ENABLED = 'true';
|
|
26
|
+
|
|
27
|
+
// Import modules
|
|
28
|
+
const { executeLocal, loadComputation, workerHandler } = require('../handlers/worker');
|
|
29
|
+
const { RemoteTaskRunner } = require('../framework/execution/RemoteTaskRunner');
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// MOCK DATA GENERATORS
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate mock portfolio data for testing
|
|
37
|
+
*/
|
|
38
|
+
function generateMockPortfolioData(userId, positionCount = 5) {
|
|
39
|
+
const positions = [];
|
|
40
|
+
const instruments = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'TSLA', 'META', 'NVDA', 'BRK.B', 'JPM', 'V'];
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < positionCount; i++) {
|
|
43
|
+
const investedAmount = Math.random() * 10000 + 1000;
|
|
44
|
+
const profitPercent = (Math.random() * 100 - 20); // -20% to +80%
|
|
45
|
+
|
|
46
|
+
positions.push({
|
|
47
|
+
InstrumentID: 1000 + i,
|
|
48
|
+
Ticker: instruments[i % instruments.length],
|
|
49
|
+
Direction: Math.random() > 0.1 ? 'Buy' : 'Sell',
|
|
50
|
+
InvestedAmount: investedAmount,
|
|
51
|
+
Value: investedAmount * (1 + profitPercent / 100),
|
|
52
|
+
Profit: investedAmount * profitPercent / 100,
|
|
53
|
+
ProfitPercent: profitPercent,
|
|
54
|
+
OpenedDate: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
Positions: positions,
|
|
60
|
+
TotalEquity: positions.reduce((sum, p) => sum + p.Value, 0),
|
|
61
|
+
TotalInvestedAmount: positions.reduce((sum, p) => sum + p.InvestedAmount, 0)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate mock asset prices
|
|
67
|
+
*/
|
|
68
|
+
function generateMockAssetPrices() {
|
|
69
|
+
return {
|
|
70
|
+
AAPL: { close: 175.50, change: 1.2 },
|
|
71
|
+
GOOGL: { close: 142.30, change: -0.5 },
|
|
72
|
+
MSFT: { close: 378.90, change: 0.8 },
|
|
73
|
+
AMZN: { close: 178.25, change: 2.1 },
|
|
74
|
+
TSLA: { close: 248.50, change: -1.5 },
|
|
75
|
+
META: { close: 505.75, change: 1.8 },
|
|
76
|
+
NVDA: { close: 875.20, change: 3.2 },
|
|
77
|
+
'BRK.B': { close: 410.30, change: 0.3 },
|
|
78
|
+
JPM: { close: 195.80, change: 0.9 },
|
|
79
|
+
V: { close: 275.40, change: 0.6 }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// TEST CASES
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Test 1: Direct worker execution (executeLocal)
|
|
89
|
+
*/
|
|
90
|
+
async function testDirectWorkerExecution() {
|
|
91
|
+
console.log('\n📦 TEST 1: Direct Worker Execution (executeLocal)');
|
|
92
|
+
console.log('=' .repeat(60));
|
|
93
|
+
|
|
94
|
+
const entityId = 'user-test-001';
|
|
95
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
96
|
+
|
|
97
|
+
const contextPackage = {
|
|
98
|
+
entityData: {
|
|
99
|
+
'portfolio_snapshots': generateMockPortfolioData(entityId, 5),
|
|
100
|
+
'asset_prices': generateMockAssetPrices()
|
|
101
|
+
},
|
|
102
|
+
references: {},
|
|
103
|
+
dependencies: {},
|
|
104
|
+
config: {}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await executeLocal({
|
|
111
|
+
computationName: 'UserPortfolioSummary',
|
|
112
|
+
entityId,
|
|
113
|
+
date,
|
|
114
|
+
contextPackage
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const duration = Date.now() - startTime;
|
|
118
|
+
|
|
119
|
+
console.log(`✅ Worker executed successfully in ${duration}ms`);
|
|
120
|
+
console.log(` Entity ID: ${result.entityId}`);
|
|
121
|
+
console.log(` Result:`, JSON.stringify(result.result, null, 2));
|
|
122
|
+
|
|
123
|
+
// Validate result structure
|
|
124
|
+
if (result.result) {
|
|
125
|
+
console.log(` ✓ Has userId: ${!!result.result.userId}`);
|
|
126
|
+
console.log(` ✓ Has totalValue: ${!!result.result.totalValue}`);
|
|
127
|
+
console.log(` ✓ Has positionCount: ${result.result.positionCount}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { success: true, duration, result: result.result };
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.log(`❌ Worker execution failed: ${error.message}`);
|
|
133
|
+
console.log(error.stack);
|
|
134
|
+
return { success: false, error: error.message };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Test 2: Worker HTTP handler simulation
|
|
140
|
+
*/
|
|
141
|
+
async function testWorkerHttpHandler() {
|
|
142
|
+
console.log('\n📦 TEST 2: Worker HTTP Handler (workerHandler)');
|
|
143
|
+
console.log('='.repeat(60));
|
|
144
|
+
|
|
145
|
+
const entityId = 'user-http-001';
|
|
146
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
147
|
+
|
|
148
|
+
// Create mock request/response objects
|
|
149
|
+
const mockReq = {
|
|
150
|
+
body: {
|
|
151
|
+
computationName: 'UserPortfolioSummary',
|
|
152
|
+
entityId,
|
|
153
|
+
date,
|
|
154
|
+
localContext: {
|
|
155
|
+
entityData: {
|
|
156
|
+
'portfolio_snapshots': generateMockPortfolioData(entityId, 3),
|
|
157
|
+
'asset_prices': generateMockAssetPrices()
|
|
158
|
+
},
|
|
159
|
+
references: {},
|
|
160
|
+
dependencies: {},
|
|
161
|
+
config: {}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
let responseData = null;
|
|
167
|
+
let responseStatus = null;
|
|
168
|
+
|
|
169
|
+
const mockRes = {
|
|
170
|
+
status: (code) => {
|
|
171
|
+
responseStatus = code;
|
|
172
|
+
return mockRes;
|
|
173
|
+
},
|
|
174
|
+
json: (data) => {
|
|
175
|
+
responseData = data;
|
|
176
|
+
return mockRes;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const startTime = Date.now();
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await workerHandler(mockReq, mockRes);
|
|
184
|
+
|
|
185
|
+
const duration = Date.now() - startTime;
|
|
186
|
+
|
|
187
|
+
console.log(`✅ HTTP handler completed in ${duration}ms`);
|
|
188
|
+
console.log(` Status: ${responseStatus}`);
|
|
189
|
+
console.log(` Response status: ${responseData?.status}`);
|
|
190
|
+
|
|
191
|
+
if (responseData?.result) {
|
|
192
|
+
console.log(` Total Value: $${responseData.result.totalValue?.toFixed(2)}`);
|
|
193
|
+
console.log(` Positions: ${responseData.result.positionCount}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { success: responseStatus === 200, duration, response: responseData };
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.log(`❌ HTTP handler failed: ${error.message}`);
|
|
199
|
+
return { success: false, error: error.message };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Test 3: RemoteTaskRunner in local mode
|
|
205
|
+
*/
|
|
206
|
+
async function testRemoteTaskRunnerLocal() {
|
|
207
|
+
console.log('\n📦 TEST 3: RemoteTaskRunner (Local Mode)');
|
|
208
|
+
console.log('='.repeat(60));
|
|
209
|
+
|
|
210
|
+
const config = {
|
|
211
|
+
workerPool: {
|
|
212
|
+
enabled: true,
|
|
213
|
+
localMode: true,
|
|
214
|
+
concurrency: 10
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const runner = new RemoteTaskRunner(config, console);
|
|
219
|
+
|
|
220
|
+
// Create mock manifest entry
|
|
221
|
+
const entry = {
|
|
222
|
+
name: 'userportfoliosummary',
|
|
223
|
+
originalName: 'UserPortfolioSummary',
|
|
224
|
+
type: 'per-entity'
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
228
|
+
const baseContext = {
|
|
229
|
+
references: {},
|
|
230
|
+
config: {}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Generate test entities
|
|
234
|
+
const entityIds = ['user-batch-001', 'user-batch-002', 'user-batch-003', 'user-batch-004', 'user-batch-005'];
|
|
235
|
+
const entityDataMap = new Map();
|
|
236
|
+
|
|
237
|
+
for (const entityId of entityIds) {
|
|
238
|
+
entityDataMap.set(entityId, {
|
|
239
|
+
'portfolio_snapshots': generateMockPortfolioData(entityId, Math.floor(Math.random() * 8) + 2),
|
|
240
|
+
'asset_prices': generateMockAssetPrices()
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const depResults = {};
|
|
245
|
+
|
|
246
|
+
const startTime = Date.now();
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const { results, errors } = await runner.runBatch(
|
|
250
|
+
entry,
|
|
251
|
+
dateStr,
|
|
252
|
+
baseContext,
|
|
253
|
+
entityIds,
|
|
254
|
+
entityDataMap,
|
|
255
|
+
depResults
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const duration = Date.now() - startTime;
|
|
259
|
+
|
|
260
|
+
console.log(`✅ Batch completed in ${duration}ms`);
|
|
261
|
+
console.log(` Entities processed: ${Object.keys(results).length}/${entityIds.length}`);
|
|
262
|
+
console.log(` Errors: ${errors.length}`);
|
|
263
|
+
console.log(` Throughput: ${(entityIds.length / (duration / 1000)).toFixed(2)} entities/sec`);
|
|
264
|
+
|
|
265
|
+
// Show sample results
|
|
266
|
+
const firstResult = Object.values(results)[0];
|
|
267
|
+
if (firstResult) {
|
|
268
|
+
console.log(` Sample result - Total Value: $${firstResult.totalValue?.toFixed(2)}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (errors.length > 0) {
|
|
272
|
+
console.log(` First error: ${errors[0].error}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { success: errors.length === 0, duration, resultCount: Object.keys(results).length };
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.log(`❌ RemoteTaskRunner failed: ${error.message}`);
|
|
278
|
+
console.log(error.stack);
|
|
279
|
+
return { success: false, error: error.message };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Test 4: Performance test with larger batch
|
|
285
|
+
*/
|
|
286
|
+
async function testPerformance() {
|
|
287
|
+
console.log('\n📦 TEST 4: Performance Test (Large Batch)');
|
|
288
|
+
console.log('='.repeat(60));
|
|
289
|
+
|
|
290
|
+
const config = {
|
|
291
|
+
workerPool: {
|
|
292
|
+
enabled: true,
|
|
293
|
+
localMode: true,
|
|
294
|
+
concurrency: 50 // Higher concurrency for perf test
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const runner = new RemoteTaskRunner(config, console);
|
|
299
|
+
|
|
300
|
+
const entry = {
|
|
301
|
+
name: 'userportfoliosummary',
|
|
302
|
+
originalName: 'UserPortfolioSummary',
|
|
303
|
+
type: 'per-entity'
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
307
|
+
const baseContext = { references: {}, config: {} };
|
|
308
|
+
|
|
309
|
+
// Generate 100 test entities
|
|
310
|
+
const entityCount = 100;
|
|
311
|
+
const entityIds = Array.from({ length: entityCount }, (_, i) => `user-perf-${i.toString().padStart(4, '0')}`);
|
|
312
|
+
const entityDataMap = new Map();
|
|
313
|
+
|
|
314
|
+
console.log(` Generating ${entityCount} test entities...`);
|
|
315
|
+
|
|
316
|
+
for (const entityId of entityIds) {
|
|
317
|
+
entityDataMap.set(entityId, {
|
|
318
|
+
'portfolio_snapshots': generateMockPortfolioData(entityId, Math.floor(Math.random() * 10) + 1),
|
|
319
|
+
'asset_prices': generateMockAssetPrices()
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log(` Running batch...`);
|
|
324
|
+
const startTime = Date.now();
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const { results, errors } = await runner.runBatch(
|
|
328
|
+
entry,
|
|
329
|
+
dateStr,
|
|
330
|
+
baseContext,
|
|
331
|
+
entityIds,
|
|
332
|
+
entityDataMap,
|
|
333
|
+
{}
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const duration = Date.now() - startTime;
|
|
337
|
+
const throughput = (entityCount / (duration / 1000)).toFixed(2);
|
|
338
|
+
|
|
339
|
+
console.log(`\n 📊 PERFORMANCE RESULTS:`);
|
|
340
|
+
console.log(` ─────────────────────────`);
|
|
341
|
+
console.log(` Total entities: ${entityCount}`);
|
|
342
|
+
console.log(` Successful: ${Object.keys(results).length}`);
|
|
343
|
+
console.log(` Failed: ${errors.length}`);
|
|
344
|
+
console.log(` Total duration: ${duration}ms`);
|
|
345
|
+
console.log(` Throughput: ${throughput} entities/sec`);
|
|
346
|
+
console.log(` Avg per entity: ${(duration / entityCount).toFixed(2)}ms`);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
success: errors.length === 0,
|
|
350
|
+
duration,
|
|
351
|
+
throughput: parseFloat(throughput),
|
|
352
|
+
resultCount: Object.keys(results).length
|
|
353
|
+
};
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.log(`❌ Performance test failed: ${error.message}`);
|
|
356
|
+
return { success: false, error: error.message };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Test 5: Error handling
|
|
362
|
+
*/
|
|
363
|
+
async function testErrorHandling() {
|
|
364
|
+
console.log('\n📦 TEST 5: Error Handling');
|
|
365
|
+
console.log('='.repeat(60));
|
|
366
|
+
|
|
367
|
+
// Test 5a: Unknown computation
|
|
368
|
+
console.log('\n 5a. Unknown computation name:');
|
|
369
|
+
try {
|
|
370
|
+
const result = await executeLocal({
|
|
371
|
+
computationName: 'NonExistentComputation',
|
|
372
|
+
entityId: 'test',
|
|
373
|
+
date: '2026-01-25',
|
|
374
|
+
contextPackage: {}
|
|
375
|
+
});
|
|
376
|
+
console.log(` ❌ Should have thrown error, got: ${JSON.stringify(result)}`);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.log(` ✅ Correctly threw error: ${error.message}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Test 5b: Missing data
|
|
382
|
+
console.log('\n 5b. Missing required data:');
|
|
383
|
+
try {
|
|
384
|
+
const result = await executeLocal({
|
|
385
|
+
computationName: 'UserPortfolioSummary',
|
|
386
|
+
entityId: 'test-missing-data',
|
|
387
|
+
date: '2026-01-25',
|
|
388
|
+
contextPackage: {
|
|
389
|
+
entityData: {}, // No portfolio data
|
|
390
|
+
references: {},
|
|
391
|
+
dependencies: {}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
console.log(` ✅ Handled gracefully: ${result.result === null ? 'null result' : JSON.stringify(result.result)}`);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
console.log(` ⚠️ Error (may be expected): ${error.message}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { success: true };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Test 6: Load all computations
|
|
404
|
+
*/
|
|
405
|
+
async function testLoadComputations() {
|
|
406
|
+
console.log('\n📦 TEST 6: Load All Computations');
|
|
407
|
+
console.log('='.repeat(60));
|
|
408
|
+
|
|
409
|
+
const computationNames = [
|
|
410
|
+
'UserPortfolioSummary',
|
|
411
|
+
'PopularInvestorProfileMetrics',
|
|
412
|
+
'PopularInvestorRiskAssessment',
|
|
413
|
+
'PopularInvestorRiskMetrics'
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
const results = [];
|
|
417
|
+
|
|
418
|
+
for (const name of computationNames) {
|
|
419
|
+
try {
|
|
420
|
+
const cls = loadComputation(name);
|
|
421
|
+
if (cls) {
|
|
422
|
+
console.log(` ✅ ${name}: Loaded successfully`);
|
|
423
|
+
results.push({ name, success: true });
|
|
424
|
+
} else {
|
|
425
|
+
console.log(` ❌ ${name}: Failed to load (returned null)`);
|
|
426
|
+
results.push({ name, success: false });
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
console.log(` ❌ ${name}: ${error.message}`);
|
|
430
|
+
results.push({ name, success: false, error: error.message });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const successCount = results.filter(r => r.success).length;
|
|
435
|
+
console.log(`\n Summary: ${successCount}/${computationNames.length} loaded successfully`);
|
|
436
|
+
|
|
437
|
+
return { success: successCount === computationNames.length, results };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ============================================================================
|
|
441
|
+
// MAIN TEST RUNNER
|
|
442
|
+
// ============================================================================
|
|
443
|
+
|
|
444
|
+
async function runAllTests() {
|
|
445
|
+
console.log('\n');
|
|
446
|
+
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
447
|
+
console.log('║ WORKER POOL LOCAL TEST HARNESS ║');
|
|
448
|
+
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
449
|
+
console.log(`\n📅 Date: ${new Date().toISOString()}`);
|
|
450
|
+
console.log(`🔧 Mode: LOCAL (WORKER_LOCAL_MODE=true)`);
|
|
451
|
+
console.log(`🔧 Worker Pool: ENABLED (WORKER_POOL_ENABLED=true)`);
|
|
452
|
+
|
|
453
|
+
const results = {};
|
|
454
|
+
|
|
455
|
+
// Run all tests
|
|
456
|
+
results.directExecution = await testDirectWorkerExecution();
|
|
457
|
+
results.httpHandler = await testWorkerHttpHandler();
|
|
458
|
+
results.remoteTaskRunner = await testRemoteTaskRunnerLocal();
|
|
459
|
+
results.performance = await testPerformance();
|
|
460
|
+
results.errorHandling = await testErrorHandling();
|
|
461
|
+
results.loadComputations = await testLoadComputations();
|
|
462
|
+
|
|
463
|
+
// Summary
|
|
464
|
+
console.log('\n');
|
|
465
|
+
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
466
|
+
console.log('║ TEST SUMMARY ║');
|
|
467
|
+
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
468
|
+
|
|
469
|
+
let passCount = 0;
|
|
470
|
+
let failCount = 0;
|
|
471
|
+
|
|
472
|
+
for (const [name, result] of Object.entries(results)) {
|
|
473
|
+
const icon = result.success ? '✅' : '❌';
|
|
474
|
+
const status = result.success ? 'PASS' : 'FAIL';
|
|
475
|
+
console.log(`${icon} ${name}: ${status}`);
|
|
476
|
+
if (result.success) passCount++;
|
|
477
|
+
else failCount++;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
console.log(`\n📊 Results: ${passCount} passed, ${failCount} failed`);
|
|
481
|
+
|
|
482
|
+
if (results.performance && results.performance.throughput) {
|
|
483
|
+
console.log(`📈 Performance: ${results.performance.throughput} entities/sec`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Exit with appropriate code
|
|
487
|
+
process.exit(failCount > 0 ? 1 : 0);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Run tests
|
|
491
|
+
runAllTests().catch(err => {
|
|
492
|
+
console.error('Fatal test error:', err);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
});
|
package/package.json
CHANGED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Test Computation for E2E Verification
|
|
3
|
-
*/
|
|
4
|
-
const { Computation } = require('../framework');
|
|
5
|
-
|
|
6
|
-
class TestComputation extends Computation {
|
|
7
|
-
static getConfig() {
|
|
8
|
-
return {
|
|
9
|
-
name: 'TestComputation',
|
|
10
|
-
version: '1.0.0',
|
|
11
|
-
type: 'per-entity', // Forces the Streaming/Batching path
|
|
12
|
-
schedule: 'daily',
|
|
13
|
-
requires: {
|
|
14
|
-
// We will mock these tables in the test
|
|
15
|
-
users: { fields: ['id', 'status'], mandatory: true },
|
|
16
|
-
transactions: { fields: ['userId', 'amount'], mandatory: false }
|
|
17
|
-
},
|
|
18
|
-
dependencies: []
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async process(context) {
|
|
23
|
-
const { entityId, data, rules } = context;
|
|
24
|
-
|
|
25
|
-
// 1. Validate Data Injection
|
|
26
|
-
const user = data.users;
|
|
27
|
-
const txs = data.transactions || []; // Should be array of transactions for this user
|
|
28
|
-
|
|
29
|
-
// 2. Validate Rule Injection (Dynamic)
|
|
30
|
-
// We expect the test framework to provide a mock rule named 'math'
|
|
31
|
-
const multiplier = rules.math ? rules.math.double(1) : 1;
|
|
32
|
-
|
|
33
|
-
// 3. Perform Calculation
|
|
34
|
-
const totalAmount = txs.reduce((sum, t) => sum + t.amount, 0);
|
|
35
|
-
|
|
36
|
-
// 4. Return Result
|
|
37
|
-
this.results[entityId] = {
|
|
38
|
-
userId: entityId,
|
|
39
|
-
score: totalAmount * multiplier,
|
|
40
|
-
status: user.status,
|
|
41
|
-
processedAt: new Date().toISOString()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
module.exports = TestComputation;
|
|
File without changes
|