bulltrackers-module 1.0.765 → 1.0.768
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/computations/BehavioralAnomaly.js +298 -186
- package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
- package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
- package/functions/computation-system-v2/config/bulltrackers.config.js +26 -14
- package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
- package/functions/computation-system-v2/framework/data/DataFetcher.js +142 -4
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +119 -122
- package/functions/computation-system-v2/framework/storage/StorageManager.js +16 -18
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +155 -66
- package/functions/computation-system-v2/handlers/scheduler.js +15 -5
- package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
- package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
- package/package.json +1 -1
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
- package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
- package/functions/computation-system-v2/test/analyze-results.js +0 -238
- package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
- package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
- package/functions/computation-system-v2/test/other/test-framework.js +0 -500
- package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
- package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
- package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
- package/functions/computation-system-v2/test/other/test-results.json +0 -31
- package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
- package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
- package/functions/computation-system-v2/test/other/test-storage.js +0 -449
- package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
- package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
- package/functions/computation-system-v2/test/test-worker-pool.js +0 -266
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Test Unified Scheduler
|
|
3
|
-
*
|
|
4
|
-
* Tests the scheduler logic:
|
|
5
|
-
* 1. Clock drift handling (floor to minute)
|
|
6
|
-
* 2. Schedule matching (isScheduleDue)
|
|
7
|
-
* 3. Finding due computations
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const schedulerModule = require('../../handlers/scheduler');
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Test Clock Drift Handling
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
function testClockDrift() {
|
|
17
|
-
console.log('\n=== Test Clock Drift Handling ===\n');
|
|
18
|
-
|
|
19
|
-
const floorToMinute = schedulerModule._floorToMinute;
|
|
20
|
-
|
|
21
|
-
// Test case 1: Late by 58 seconds
|
|
22
|
-
const late = new Date('2026-01-24T14:00:58.123Z');
|
|
23
|
-
const floored1 = floorToMinute(late);
|
|
24
|
-
console.log(` 14:00:58.123 → ${floored1.toISOString()}`);
|
|
25
|
-
console.assert(floored1.getUTCMinutes() === 0, 'Should floor to minute 0');
|
|
26
|
-
console.assert(floored1.getUTCSeconds() === 0, 'Seconds should be 0');
|
|
27
|
-
console.assert(floored1.getUTCMilliseconds() === 0, 'Milliseconds should be 0');
|
|
28
|
-
|
|
29
|
-
// Test case 2: Exactly on time
|
|
30
|
-
const exact = new Date('2026-01-24T14:00:00.000Z');
|
|
31
|
-
const floored2 = floorToMinute(exact);
|
|
32
|
-
console.log(` 14:00:00.000 → ${floored2.toISOString()}`);
|
|
33
|
-
console.assert(floored2.getTime() === exact.getTime(), 'Should remain unchanged');
|
|
34
|
-
|
|
35
|
-
// Test case 3: Early (1ms into the minute)
|
|
36
|
-
const early = new Date('2026-01-24T14:00:00.001Z');
|
|
37
|
-
const floored3 = floorToMinute(early);
|
|
38
|
-
console.log(` 14:00:00.001 → ${floored3.toISOString()}`);
|
|
39
|
-
console.assert(floored3.getUTCMilliseconds() === 0, 'Milliseconds should be 0');
|
|
40
|
-
|
|
41
|
-
console.log('\n✅ Clock drift handling tests complete');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// Test Schedule Matching
|
|
46
|
-
// ============================================================================
|
|
47
|
-
|
|
48
|
-
function testScheduleMatching() {
|
|
49
|
-
console.log('\n=== Test Schedule Matching ===\n');
|
|
50
|
-
|
|
51
|
-
const isScheduleDue = schedulerModule._isScheduleDue;
|
|
52
|
-
|
|
53
|
-
// Daily schedule at 14:00
|
|
54
|
-
const dailySchedule = { frequency: 'daily', time: '14:00' };
|
|
55
|
-
|
|
56
|
-
console.log('Testing daily schedule at 14:00...');
|
|
57
|
-
console.log(` At 14:00: ${isScheduleDue(dailySchedule, '14:00', 0, 1)}`);
|
|
58
|
-
console.assert(isScheduleDue(dailySchedule, '14:00', 0, 1) === true, 'Should match at 14:00');
|
|
59
|
-
|
|
60
|
-
console.log(` At 14:01: ${isScheduleDue(dailySchedule, '14:01', 0, 1)}`);
|
|
61
|
-
console.assert(isScheduleDue(dailySchedule, '14:01', 0, 1) === false, 'Should not match at 14:01');
|
|
62
|
-
|
|
63
|
-
console.log(` At 13:59: ${isScheduleDue(dailySchedule, '13:59', 0, 1)}`);
|
|
64
|
-
console.assert(isScheduleDue(dailySchedule, '13:59', 0, 1) === false, 'Should not match at 13:59');
|
|
65
|
-
|
|
66
|
-
// Weekly schedule (Monday at 06:00)
|
|
67
|
-
const weeklySchedule = { frequency: 'weekly', time: '06:00', dayOfWeek: 1 };
|
|
68
|
-
|
|
69
|
-
console.log('\nTesting weekly schedule (Monday 06:00)...');
|
|
70
|
-
console.log(` Monday 06:00: ${isScheduleDue(weeklySchedule, '06:00', 1, 15)}`);
|
|
71
|
-
console.assert(isScheduleDue(weeklySchedule, '06:00', 1, 15) === true, 'Should match Monday at 06:00');
|
|
72
|
-
|
|
73
|
-
console.log(` Tuesday 06:00: ${isScheduleDue(weeklySchedule, '06:00', 2, 16)}`);
|
|
74
|
-
console.assert(isScheduleDue(weeklySchedule, '06:00', 2, 16) === false, 'Should not match Tuesday');
|
|
75
|
-
|
|
76
|
-
console.log(` Monday 07:00: ${isScheduleDue(weeklySchedule, '07:00', 1, 15)}`);
|
|
77
|
-
console.assert(isScheduleDue(weeklySchedule, '07:00', 1, 15) === false, 'Should not match wrong time');
|
|
78
|
-
|
|
79
|
-
// Monthly schedule (1st at 02:00)
|
|
80
|
-
const monthlySchedule = { frequency: 'monthly', time: '02:00', dayOfMonth: 1 };
|
|
81
|
-
|
|
82
|
-
console.log('\nTesting monthly schedule (1st at 02:00)...');
|
|
83
|
-
console.log(` 1st at 02:00: ${isScheduleDue(monthlySchedule, '02:00', 4, 1)}`);
|
|
84
|
-
console.assert(isScheduleDue(monthlySchedule, '02:00', 4, 1) === true, 'Should match 1st at 02:00');
|
|
85
|
-
|
|
86
|
-
console.log(` 15th at 02:00: ${isScheduleDue(monthlySchedule, '02:00', 4, 15)}`);
|
|
87
|
-
console.assert(isScheduleDue(monthlySchedule, '02:00', 4, 15) === false, 'Should not match 15th');
|
|
88
|
-
|
|
89
|
-
// Hourly schedule (at :30)
|
|
90
|
-
const hourlySchedule = { frequency: 'hourly', time: '00:30' };
|
|
91
|
-
|
|
92
|
-
console.log('\nTesting hourly schedule (at :30)...');
|
|
93
|
-
console.log(` 14:30: ${isScheduleDue(hourlySchedule, '14:30', 0, 1)}`);
|
|
94
|
-
console.assert(isScheduleDue(hourlySchedule, '14:30', 0, 1) === true, 'Should match at :30');
|
|
95
|
-
|
|
96
|
-
console.log(` 14:00: ${isScheduleDue(hourlySchedule, '14:00', 0, 1)}`);
|
|
97
|
-
console.assert(isScheduleDue(hourlySchedule, '14:00', 0, 1) === false, 'Should not match at :00');
|
|
98
|
-
|
|
99
|
-
console.log('\n✅ Schedule matching tests complete');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============================================================================
|
|
103
|
-
// Test Finding Due Computations
|
|
104
|
-
// ============================================================================
|
|
105
|
-
|
|
106
|
-
async function testFindDueComputations() {
|
|
107
|
-
console.log('\n=== Test Finding Due Computations ===\n');
|
|
108
|
-
|
|
109
|
-
// Initialize scheduler with test computations
|
|
110
|
-
await schedulerModule.initialize();
|
|
111
|
-
|
|
112
|
-
const findDueComputations = schedulerModule._findDueComputations;
|
|
113
|
-
|
|
114
|
-
// Test at 02:00 (default schedule time)
|
|
115
|
-
const time0200 = new Date('2026-01-24T02:00:00.000Z');
|
|
116
|
-
const due0200 = findDueComputations(time0200);
|
|
117
|
-
console.log(` At 02:00: ${due0200.length} computations due`);
|
|
118
|
-
due0200.forEach(c => console.log(` - ${c.originalName}`));
|
|
119
|
-
|
|
120
|
-
// Test at 14:00 (no default schedules)
|
|
121
|
-
const time1400 = new Date('2026-01-24T14:00:00.000Z');
|
|
122
|
-
const due1400 = findDueComputations(time1400);
|
|
123
|
-
console.log(` At 14:00: ${due1400.length} computations due`);
|
|
124
|
-
|
|
125
|
-
// Test clock drift scenario
|
|
126
|
-
const driftedTime = new Date('2026-01-24T02:00:58.999Z');
|
|
127
|
-
const flooredTime = schedulerModule._floorToMinute(driftedTime);
|
|
128
|
-
const dueDrifted = findDueComputations(flooredTime);
|
|
129
|
-
console.log(` At 02:00:58 (floored): ${dueDrifted.length} computations due`);
|
|
130
|
-
console.assert(dueDrifted.length === due0200.length, 'Drifted time should match exact time');
|
|
131
|
-
|
|
132
|
-
console.log('\n✅ Finding due computations tests complete');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ============================================================================
|
|
136
|
-
// Test End-to-End Scenario
|
|
137
|
-
// ============================================================================
|
|
138
|
-
|
|
139
|
-
function testEndToEndScenario() {
|
|
140
|
-
console.log('\n=== Test End-to-End Scenario ===\n');
|
|
141
|
-
|
|
142
|
-
console.log('Scenario: Scheduler triggers late at 14:00:58');
|
|
143
|
-
console.log('');
|
|
144
|
-
|
|
145
|
-
const floorToMinute = schedulerModule._floorToMinute;
|
|
146
|
-
const isScheduleDue = schedulerModule._isScheduleDue;
|
|
147
|
-
|
|
148
|
-
// Step 1: Scheduler triggers at 14:00:58
|
|
149
|
-
const triggerTime = new Date('2026-01-24T14:00:58.500Z');
|
|
150
|
-
console.log(`1. Trigger time: ${triggerTime.toISOString()}`);
|
|
151
|
-
|
|
152
|
-
// Step 2: Floor to minute boundary
|
|
153
|
-
const now = floorToMinute(triggerTime);
|
|
154
|
-
console.log(`2. Floored time: ${now.toISOString()}`);
|
|
155
|
-
console.assert(now.getUTCSeconds() === 0, 'Seconds should be 0');
|
|
156
|
-
|
|
157
|
-
// Step 3: Extract time for schedule check
|
|
158
|
-
const currentTime = `${String(now.getUTCHours()).padStart(2, '0')}:${String(now.getUTCMinutes()).padStart(2, '0')}`;
|
|
159
|
-
console.log(`3. Current time for check: ${currentTime}`);
|
|
160
|
-
console.assert(currentTime === '14:00', 'Time should be 14:00');
|
|
161
|
-
|
|
162
|
-
// Step 4: Check against schedule
|
|
163
|
-
const schedule = { frequency: 'daily', time: '14:00' };
|
|
164
|
-
const isDue = isScheduleDue(schedule, currentTime, now.getUTCDay(), now.getUTCDate());
|
|
165
|
-
console.log(`4. Is schedule due? ${isDue}`);
|
|
166
|
-
console.assert(isDue === true, 'Schedule should be due');
|
|
167
|
-
|
|
168
|
-
// Step 5: Generate payload
|
|
169
|
-
const targetDate = now.toISOString().split('T')[0];
|
|
170
|
-
console.log(`5. Target date for payload: ${targetDate}`);
|
|
171
|
-
console.assert(targetDate === '2026-01-24', 'Date should be correct');
|
|
172
|
-
|
|
173
|
-
console.log('');
|
|
174
|
-
console.log('Result: System behaves exactly as if it ran at 14:00:00 ✓');
|
|
175
|
-
|
|
176
|
-
console.log('\n✅ End-to-end scenario test complete');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ============================================================================
|
|
180
|
-
// Main
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
|
-
async function main() {
|
|
184
|
-
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
185
|
-
console.log('║ Computation System v2 - Unified Scheduler Tests ║');
|
|
186
|
-
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
testClockDrift();
|
|
190
|
-
testScheduleMatching();
|
|
191
|
-
testEndToEndScenario();
|
|
192
|
-
await testFindDueComputations();
|
|
193
|
-
|
|
194
|
-
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
|
195
|
-
console.log('║ All Tests Passed! ✅ ║');
|
|
196
|
-
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
197
|
-
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error('\n❌ Test failed:', error);
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
main();
|
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Storage Manager Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the unified storage system (BigQuery + Firestore)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { StorageManager } = require('../../framework/storage/StorageManager');
|
|
8
|
-
const config = require('../../config/bulltrackers.config');
|
|
9
|
-
|
|
10
|
-
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
11
|
-
console.log('║ Computation System v2 - Storage Tests ║');
|
|
12
|
-
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
13
|
-
|
|
14
|
-
// Test logger
|
|
15
|
-
const logger = {
|
|
16
|
-
log: (level, msg) => console.log(`[${level}] ${msg}`)
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Test Path Resolution
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
console.log('=== Test Path Template Resolution ===\n');
|
|
24
|
-
|
|
25
|
-
// Create a storage manager instance
|
|
26
|
-
const storageManager = new StorageManager(config, logger);
|
|
27
|
-
|
|
28
|
-
// Test the path resolution (we need to access the private method for testing)
|
|
29
|
-
function testPathResolution() {
|
|
30
|
-
// Note: Firestore paths must have EVEN number of segments (collection/doc/collection/doc)
|
|
31
|
-
const testCases = [
|
|
32
|
-
{
|
|
33
|
-
template: '/users/{entityId}/computations/portfolio_summary',
|
|
34
|
-
values: { entityId: '12345', date: '2026-01-24', computationName: 'test' },
|
|
35
|
-
expected: 'users/12345/computations/portfolio_summary'
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
template: '/computations/{computationName}/dates/{date}', // 4 segments
|
|
39
|
-
values: { entityId: '_global', date: '2026-01-24', computationName: 'marketsummary' },
|
|
40
|
-
expected: 'computations/marketsummary/dates/2026-01-24'
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
template: '/users/{entityId}/daily/{date}',
|
|
44
|
-
values: { entityId: 'user_abc', date: '2026-01-24', computationName: 'test' },
|
|
45
|
-
expected: 'users/user_abc/daily/2026-01-24'
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
template: '/analytics/{category}/computations/{computationName}', // 4 segments
|
|
49
|
-
values: { entityId: '123', date: '2026-01-24', computationName: 'usermetrics', category: 'user_analytics' },
|
|
50
|
-
expected: 'analytics/user_analytics/computations/usermetrics'
|
|
51
|
-
}
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
let passed = 0;
|
|
55
|
-
for (const tc of testCases) {
|
|
56
|
-
const result = storageManager._resolvePath(tc.template, tc.values);
|
|
57
|
-
const success = result === tc.expected;
|
|
58
|
-
console.log(` ${tc.template}`);
|
|
59
|
-
console.log(` → ${result} ${success ? '✓' : '✗'}`);
|
|
60
|
-
if (!success) {
|
|
61
|
-
console.log(` Expected: ${tc.expected}`);
|
|
62
|
-
}
|
|
63
|
-
if (success) passed++;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
console.log(`\n Passed: ${passed}/${testCases.length}`);
|
|
67
|
-
return passed === testCases.length;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const pathTestsPassed = testPathResolution();
|
|
71
|
-
console.log(`\n✅ Path resolution tests ${pathTestsPassed ? 'complete' : 'FAILED'}\n`);
|
|
72
|
-
|
|
73
|
-
// =============================================================================
|
|
74
|
-
// Test Path Validation (should reject odd segment counts)
|
|
75
|
-
// =============================================================================
|
|
76
|
-
|
|
77
|
-
console.log('=== Test Path Validation ===\n');
|
|
78
|
-
|
|
79
|
-
function testPathValidation() {
|
|
80
|
-
const invalidPaths = [
|
|
81
|
-
'/users/{entityId}/computations', // 3 segments - points to collection
|
|
82
|
-
'/single', // 1 segment
|
|
83
|
-
'/a/b/c', // 3 segments
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const validPaths = [
|
|
87
|
-
'/users/{entityId}', // 2 segments
|
|
88
|
-
'/a/b/c/d', // 4 segments
|
|
89
|
-
'/users/{entityId}/data/{date}', // 4 segments
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
let passed = 0;
|
|
93
|
-
|
|
94
|
-
console.log(' Invalid paths (should throw):');
|
|
95
|
-
for (const path of invalidPaths) {
|
|
96
|
-
try {
|
|
97
|
-
storageManager._resolvePath(path, { entityId: '123', date: '2026-01-24' });
|
|
98
|
-
console.log(` ${path} → DID NOT THROW ✗`);
|
|
99
|
-
} catch (e) {
|
|
100
|
-
console.log(` ${path} → Threw error ✓`);
|
|
101
|
-
passed++;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
console.log('\n Valid paths (should not throw):');
|
|
106
|
-
for (const path of validPaths) {
|
|
107
|
-
try {
|
|
108
|
-
const resolved = storageManager._resolvePath(path, { entityId: '123', date: '2026-01-24' });
|
|
109
|
-
console.log(` ${path} → ${resolved} ✓`);
|
|
110
|
-
passed++;
|
|
111
|
-
} catch (e) {
|
|
112
|
-
console.log(` ${path} → THREW ERROR ✗: ${e.message}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const total = invalidPaths.length + validPaths.length;
|
|
117
|
-
console.log(`\n Passed: ${passed}/${total}`);
|
|
118
|
-
return passed === total;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const validationTestsPassed = testPathValidation();
|
|
122
|
-
console.log(`\n✅ Path validation tests ${validationTestsPassed ? 'complete' : 'FAILED'}\n`);
|
|
123
|
-
|
|
124
|
-
// =============================================================================
|
|
125
|
-
// Test Storage Config Resolution
|
|
126
|
-
// =============================================================================
|
|
127
|
-
|
|
128
|
-
console.log('=== Test Storage Config Resolution ===\n');
|
|
129
|
-
|
|
130
|
-
function testStorageConfigResolution() {
|
|
131
|
-
const testCases = [
|
|
132
|
-
{
|
|
133
|
-
name: 'Default (no config)',
|
|
134
|
-
input: { storage: undefined },
|
|
135
|
-
expected: { bigquery: true, firestore: { enabled: false, path: null, merge: false, includeMetadata: true } }
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: 'BigQuery only explicit',
|
|
139
|
-
input: { storage: { bigquery: true } },
|
|
140
|
-
expected: { bigquery: true, firestore: { enabled: false, path: null, merge: false, includeMetadata: true } }
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'BigQuery disabled',
|
|
144
|
-
input: { storage: { bigquery: false } },
|
|
145
|
-
expected: { bigquery: false, firestore: { enabled: false, path: null, merge: false, includeMetadata: true } }
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
name: 'Firestore enabled with path',
|
|
149
|
-
input: {
|
|
150
|
-
storage: {
|
|
151
|
-
firestore: {
|
|
152
|
-
enabled: true,
|
|
153
|
-
path: '/users/{entityId}/data'
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
expected: {
|
|
158
|
-
bigquery: true,
|
|
159
|
-
firestore: {
|
|
160
|
-
enabled: true,
|
|
161
|
-
path: '/users/{entityId}/data',
|
|
162
|
-
merge: false,
|
|
163
|
-
includeMetadata: true
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: 'Firestore with merge and no metadata',
|
|
169
|
-
input: {
|
|
170
|
-
storage: {
|
|
171
|
-
firestore: {
|
|
172
|
-
enabled: true,
|
|
173
|
-
path: '/data/{entityId}',
|
|
174
|
-
merge: true,
|
|
175
|
-
includeMetadata: false
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
expected: {
|
|
180
|
-
bigquery: true,
|
|
181
|
-
firestore: {
|
|
182
|
-
enabled: true,
|
|
183
|
-
path: '/data/{entityId}',
|
|
184
|
-
merge: true,
|
|
185
|
-
includeMetadata: false
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'Firestore only (no BigQuery)',
|
|
191
|
-
input: {
|
|
192
|
-
storage: {
|
|
193
|
-
bigquery: false,
|
|
194
|
-
firestore: {
|
|
195
|
-
enabled: true,
|
|
196
|
-
path: '/users/{entityId}/cache'
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
expected: {
|
|
201
|
-
bigquery: false,
|
|
202
|
-
firestore: {
|
|
203
|
-
enabled: true,
|
|
204
|
-
path: '/users/{entityId}/cache',
|
|
205
|
-
merge: false,
|
|
206
|
-
includeMetadata: true
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
let passed = 0;
|
|
213
|
-
for (const tc of testCases) {
|
|
214
|
-
const result = storageManager._resolveStorageConfig(tc.input);
|
|
215
|
-
|
|
216
|
-
// Deep compare
|
|
217
|
-
const resultStr = JSON.stringify(result);
|
|
218
|
-
const expectedStr = JSON.stringify(tc.expected);
|
|
219
|
-
const success = resultStr === expectedStr;
|
|
220
|
-
|
|
221
|
-
console.log(` ${tc.name}:`);
|
|
222
|
-
console.log(` Input: ${JSON.stringify(tc.input.storage || 'undefined')}`);
|
|
223
|
-
console.log(` Result: ${success ? '✓' : '✗'}`);
|
|
224
|
-
|
|
225
|
-
if (!success) {
|
|
226
|
-
console.log(` Got: ${resultStr}`);
|
|
227
|
-
console.log(` Expected: ${expectedStr}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (success) passed++;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
console.log(`\n Passed: ${passed}/${testCases.length}`);
|
|
234
|
-
return passed === testCases.length;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const configTestsPassed = testStorageConfigResolution();
|
|
238
|
-
console.log(`\n✅ Storage config resolution tests ${configTestsPassed ? 'complete' : 'FAILED'}\n`);
|
|
239
|
-
|
|
240
|
-
// =============================================================================
|
|
241
|
-
// Test Live Firestore Write (requires real credentials)
|
|
242
|
-
// =============================================================================
|
|
243
|
-
|
|
244
|
-
console.log('=== Test Live Firestore Write ===\n');
|
|
245
|
-
|
|
246
|
-
async function testLiveFirestoreWrite() {
|
|
247
|
-
// Create a mock entry with Firestore storage config
|
|
248
|
-
const entry = {
|
|
249
|
-
name: 'test_computation',
|
|
250
|
-
originalName: 'TestComputation',
|
|
251
|
-
category: 'test',
|
|
252
|
-
type: 'per-entity',
|
|
253
|
-
hash: 'test_hash_123',
|
|
254
|
-
storage: {
|
|
255
|
-
bigquery: false, // Skip BigQuery for this test
|
|
256
|
-
firestore: {
|
|
257
|
-
enabled: true,
|
|
258
|
-
path: '/test_computations/{entityId}/results/{date}',
|
|
259
|
-
merge: false,
|
|
260
|
-
includeMetadata: true
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Mock results for 3 entities
|
|
266
|
-
const results = {
|
|
267
|
-
'entity_001': { score: 85, rank: 1, metrics: { a: 1, b: 2 } },
|
|
268
|
-
'entity_002': { score: 72, rank: 2, metrics: { a: 3, b: 4 } },
|
|
269
|
-
'entity_003': { score: 65, rank: 3, metrics: { a: 5, b: 6 } }
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const dateStr = '2026-01-24';
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
console.log(' Writing test data to Firestore...');
|
|
276
|
-
console.log(` Entry: ${entry.name} (${entry.type})`);
|
|
277
|
-
console.log(` Path template: ${entry.storage.firestore.path}`);
|
|
278
|
-
console.log(` Entities: ${Object.keys(results).join(', ')}`);
|
|
279
|
-
|
|
280
|
-
const writeResult = await storageManager.commitResults(dateStr, entry, results, {});
|
|
281
|
-
|
|
282
|
-
console.log('\n Write Results:');
|
|
283
|
-
console.log(` BigQuery: ${writeResult.bigquery ? `${writeResult.bigquery.rowCount} rows` : 'skipped'}`);
|
|
284
|
-
console.log(` Firestore: ${writeResult.firestore?.docCount || 0} documents`);
|
|
285
|
-
|
|
286
|
-
if (writeResult.firestore?.error) {
|
|
287
|
-
console.log(` Firestore Error: ${writeResult.firestore.error}`);
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Verify the documents were written by reading them back
|
|
292
|
-
console.log('\n Verifying written documents...');
|
|
293
|
-
|
|
294
|
-
const firestore = storageManager.firestore;
|
|
295
|
-
let verified = 0;
|
|
296
|
-
|
|
297
|
-
for (const entityId of Object.keys(results)) {
|
|
298
|
-
const docPath = `test_computations/${entityId}/results/${dateStr}`;
|
|
299
|
-
const docRef = firestore.doc(docPath);
|
|
300
|
-
const doc = await docRef.get();
|
|
301
|
-
|
|
302
|
-
if (doc.exists) {
|
|
303
|
-
const data = doc.data();
|
|
304
|
-
console.log(` ${docPath}: ✓`);
|
|
305
|
-
console.log(` - score: ${data.score}`);
|
|
306
|
-
console.log(` - _computedAt: ${data._computedAt ? 'present' : 'missing'}`);
|
|
307
|
-
verified++;
|
|
308
|
-
} else {
|
|
309
|
-
console.log(` ${docPath}: NOT FOUND ✗`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
console.log(`\n Verified: ${verified}/${Object.keys(results).length} documents`);
|
|
314
|
-
|
|
315
|
-
// Clean up test data
|
|
316
|
-
console.log('\n Cleaning up test data...');
|
|
317
|
-
for (const entityId of Object.keys(results)) {
|
|
318
|
-
const docPath = `test_computations/${entityId}/results/${dateStr}`;
|
|
319
|
-
await firestore.doc(docPath).delete();
|
|
320
|
-
}
|
|
321
|
-
// Also delete the parent documents/collections if empty
|
|
322
|
-
for (const entityId of Object.keys(results)) {
|
|
323
|
-
try {
|
|
324
|
-
await firestore.doc(`test_computations/${entityId}`).delete();
|
|
325
|
-
} catch (e) {
|
|
326
|
-
// Ignore if collection not empty
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
console.log(' Cleanup complete');
|
|
330
|
-
|
|
331
|
-
return verified === Object.keys(results).length;
|
|
332
|
-
|
|
333
|
-
} catch (error) {
|
|
334
|
-
console.log(`\n Error: ${error.message}`);
|
|
335
|
-
|
|
336
|
-
if (error.message.includes('Could not load the default credentials')) {
|
|
337
|
-
console.log('\n Note: Firestore test requires GCP credentials.');
|
|
338
|
-
console.log(' Set GOOGLE_APPLICATION_CREDENTIALS or run in GCP environment.');
|
|
339
|
-
return 'skipped';
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// =============================================================================
|
|
347
|
-
// Test Global Computation Storage
|
|
348
|
-
// =============================================================================
|
|
349
|
-
|
|
350
|
-
async function testGlobalComputationStorage() {
|
|
351
|
-
console.log('\n=== Test Global Computation Firestore Write ===\n');
|
|
352
|
-
|
|
353
|
-
const entry = {
|
|
354
|
-
name: 'market_summary',
|
|
355
|
-
originalName: 'MarketSummary',
|
|
356
|
-
category: 'market',
|
|
357
|
-
type: 'global',
|
|
358
|
-
hash: 'global_hash_456',
|
|
359
|
-
storage: {
|
|
360
|
-
bigquery: false,
|
|
361
|
-
firestore: {
|
|
362
|
-
enabled: true,
|
|
363
|
-
// Path must have even segments: collection/doc/collection/doc
|
|
364
|
-
path: '/computations/{computationName}/dates/{date}',
|
|
365
|
-
merge: false,
|
|
366
|
-
includeMetadata: true
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const results = {
|
|
372
|
-
totalVolume: 1500000000,
|
|
373
|
-
topGainers: ['AAPL', 'MSFT', 'GOOGL'],
|
|
374
|
-
topLosers: ['META', 'NFLX'],
|
|
375
|
-
marketSentiment: 0.65
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const dateStr = '2026-01-24';
|
|
379
|
-
|
|
380
|
-
try {
|
|
381
|
-
console.log(' Writing global computation to Firestore...');
|
|
382
|
-
console.log(` Path template: ${entry.storage.firestore.path}`);
|
|
383
|
-
|
|
384
|
-
const writeResult = await storageManager.commitResults(dateStr, entry, results, {});
|
|
385
|
-
|
|
386
|
-
console.log('\n Write Results:');
|
|
387
|
-
console.log(` Firestore: ${writeResult.firestore?.docCount || 0} documents`);
|
|
388
|
-
|
|
389
|
-
// Verify
|
|
390
|
-
const firestore = storageManager.firestore;
|
|
391
|
-
const docPath = `computations/market_summary/dates/${dateStr}`;
|
|
392
|
-
const doc = await firestore.doc(docPath).get();
|
|
393
|
-
|
|
394
|
-
if (doc.exists) {
|
|
395
|
-
const data = doc.data();
|
|
396
|
-
console.log(`\n Verified document at ${docPath}:`);
|
|
397
|
-
console.log(` - totalVolume: ${data.totalVolume}`);
|
|
398
|
-
console.log(` - marketSentiment: ${data.marketSentiment}`);
|
|
399
|
-
console.log(` - _computedAt: ${data._computedAt ? 'present' : 'missing'}`);
|
|
400
|
-
|
|
401
|
-
// Cleanup
|
|
402
|
-
await firestore.doc(docPath).delete();
|
|
403
|
-
console.log('\n Cleanup complete');
|
|
404
|
-
|
|
405
|
-
return true;
|
|
406
|
-
} else {
|
|
407
|
-
console.log(`\n Document not found at ${docPath}`);
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
} catch (error) {
|
|
412
|
-
if (error.message.includes('Could not load the default credentials')) {
|
|
413
|
-
console.log(' Skipped: No GCP credentials');
|
|
414
|
-
return 'skipped';
|
|
415
|
-
}
|
|
416
|
-
console.log(` Error: ${error.message}`);
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// =============================================================================
|
|
422
|
-
// Run All Tests
|
|
423
|
-
// =============================================================================
|
|
424
|
-
|
|
425
|
-
async function runAllTests() {
|
|
426
|
-
const results = {
|
|
427
|
-
pathResolution: pathTestsPassed,
|
|
428
|
-
pathValidation: validationTestsPassed,
|
|
429
|
-
configResolution: configTestsPassed,
|
|
430
|
-
liveFirestore: await testLiveFirestoreWrite(),
|
|
431
|
-
globalComputation: await testGlobalComputationStorage()
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
|
435
|
-
console.log('║ Test Summary ║');
|
|
436
|
-
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
437
|
-
|
|
438
|
-
let allPassed = true;
|
|
439
|
-
for (const [name, result] of Object.entries(results)) {
|
|
440
|
-
const status = result === true ? '✅ PASSED' :
|
|
441
|
-
result === 'skipped' ? '⏭️ SKIPPED' : '❌ FAILED';
|
|
442
|
-
console.log(` ${name}: ${status}`);
|
|
443
|
-
if (result === false) allPassed = false;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
console.log('\n' + (allPassed ? '✅ All tests passed!' : '❌ Some tests failed'));
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
runAllTests().catch(console.error);
|