bulltrackers-module 1.0.766 → 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.
Files changed (32) hide show
  1. package/functions/computation-system-v2/computations/BehavioralAnomaly.js +298 -186
  2. package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
  3. package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
  4. package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
  5. package/functions/computation-system-v2/config/bulltrackers.config.js +26 -14
  6. package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
  7. package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
  8. package/functions/computation-system-v2/framework/data/DataFetcher.js +142 -4
  9. package/functions/computation-system-v2/framework/execution/Orchestrator.js +18 -31
  10. package/functions/computation-system-v2/framework/storage/StorageManager.js +7 -17
  11. package/functions/computation-system-v2/framework/testing/ComputationTester.js +155 -66
  12. package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
  13. package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
  14. package/package.json +1 -1
  15. package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
  16. package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
  17. package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
  18. package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
  19. package/functions/computation-system-v2/test/analyze-results.js +0 -238
  20. package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
  21. package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
  22. package/functions/computation-system-v2/test/other/test-framework.js +0 -500
  23. package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
  24. package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
  25. package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
  26. package/functions/computation-system-v2/test/other/test-results.json +0 -31
  27. package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
  28. package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
  29. package/functions/computation-system-v2/test/other/test-storage.js +0 -449
  30. package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
  31. package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
  32. 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);