@forgehive/task 0.2.2 → 0.2.4
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/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -13
- package/dist/index.js.map +1 -1
- package/dist/test/add-listener-with-boundaries.test.js +78 -7
- package/dist/test/add-listener-with-boundaries.test.js.map +1 -1
- package/dist/test/add-listener.test.js +36 -0
- package/dist/test/add-listener.test.js.map +1 -1
- package/dist/test/boundary-modes.test.js +45 -5
- package/dist/test/boundary-modes.test.js.map +1 -1
- package/dist/test/execution-record-boundaries.test.js +12 -2
- package/dist/test/execution-record-boundaries.test.js.map +1 -1
- package/dist/test/integration-enhanced-records.test.d.ts +2 -0
- package/dist/test/integration-enhanced-records.test.d.ts.map +1 -0
- package/dist/test/integration-enhanced-records.test.js +467 -0
- package/dist/test/integration-enhanced-records.test.js.map +1 -0
- package/dist/test/listen-execution-records.test.d.ts +2 -0
- package/dist/test/listen-execution-records.test.d.ts.map +1 -0
- package/dist/test/listen-execution-records.test.js +223 -0
- package/dist/test/listen-execution-records.test.js.map +1 -0
- package/dist/test/metrics-collection.test.d.ts +2 -0
- package/dist/test/metrics-collection.test.d.ts.map +1 -0
- package/dist/test/metrics-collection.test.js +409 -0
- package/dist/test/metrics-collection.test.js.map +1 -0
- package/dist/test/performance-edge-cases.test.d.ts +2 -0
- package/dist/test/performance-edge-cases.test.d.ts.map +1 -0
- package/dist/test/performance-edge-cases.test.js +502 -0
- package/dist/test/performance-edge-cases.test.js.map +1 -0
- package/dist/test/run-boundary.test.js +27 -3
- package/dist/test/run-boundary.test.js.map +1 -1
- package/dist/test/safe-replay-complex-boundary.test.js +110 -9
- package/dist/test/safe-replay-complex-boundary.test.js.map +1 -1
- package/dist/test/safe-replay.test.js +35 -5
- package/dist/test/safe-replay.test.js.map +1 -1
- package/dist/test/safe-run.test.js +46 -4
- package/dist/test/safe-run.test.js.map +1 -1
- package/dist/test/setmetrics-boundary.test.d.ts +2 -0
- package/dist/test/setmetrics-boundary.test.d.ts.map +1 -0
- package/dist/test/setmetrics-boundary.test.js +195 -0
- package/dist/test/setmetrics-boundary.test.js.map +1 -0
- package/dist/test/task-with-boundaries.test.js +63 -2
- package/dist/test/task-with-boundaries.test.js.map +1 -1
- package/dist/test/timing-capture.test.d.ts +2 -0
- package/dist/test/timing-capture.test.d.ts.map +1 -0
- package/dist/test/timing-capture.test.js +304 -0
- package/dist/test/timing-capture.test.js.map +1 -0
- package/dist/test/timing-utilities.test.d.ts +2 -0
- package/dist/test/timing-utilities.test.d.ts.map +1 -0
- package/dist/test/timing-utilities.test.js +127 -0
- package/dist/test/timing-utilities.test.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +78 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/boundary.d.ts +3 -0
- package/dist/utils/boundary.d.ts.map +1 -1
- package/dist/utils/boundary.js +11 -2
- package/dist/utils/boundary.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +97 -14
- package/src/test/add-listener-with-boundaries.test.ts +78 -7
- package/src/test/add-listener.test.ts +36 -0
- package/src/test/boundary-modes.test.ts +45 -5
- package/src/test/execution-record-boundaries.test.ts +12 -2
- package/src/test/listen-execution-records.test.ts +295 -0
- package/src/test/metrics-collection.test.ts +476 -0
- package/src/test/performance-edge-cases.test.ts +596 -0
- package/src/test/run-boundary.test.ts +27 -3
- package/src/test/safe-replay-complex-boundary.test.ts +115 -10
- package/src/test/safe-replay.test.ts +35 -5
- package/src/test/safe-run.test.ts +46 -4
- package/src/test/setmetrics-boundary.test.ts +223 -0
- package/src/test/task-with-boundaries.test.ts +71 -5
- package/src/test/timing-capture.test.ts +348 -0
- package/src/test/timing-utilities.test.ts +145 -0
- package/src/types.ts +139 -0
- package/src/utils/boundary.ts +15 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from '@forgehive/schema'
|
|
2
|
-
import { createTask, ExecutionRecord, getExecutionRecordType } from '../index'
|
|
2
|
+
import { createTask, ExecutionRecord, getExecutionRecordType, BoundaryRecord } from '../index'
|
|
3
3
|
|
|
4
4
|
describe('Complex boundary replay tests', () => {
|
|
5
5
|
// Helper function to create ExecutionRecord with computed type
|
|
@@ -190,21 +190,41 @@ describe('Complex boundary replay tests', () => {
|
|
|
190
190
|
{ ticker: 'MSFT', quantity: 5 },
|
|
191
191
|
{ ticker: 'GOOGL', quantity: 8 }
|
|
192
192
|
]
|
|
193
|
+
},
|
|
194
|
+
timing: {
|
|
195
|
+
startTime: 1000,
|
|
196
|
+
endTime: 1100,
|
|
197
|
+
duration: 100
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
],
|
|
196
201
|
fetchPrice: [
|
|
197
202
|
{
|
|
198
203
|
input: ['AAPL'],
|
|
199
|
-
output: 190.50
|
|
204
|
+
output: 190.50,
|
|
205
|
+
timing: {
|
|
206
|
+
startTime: 1100,
|
|
207
|
+
endTime: 1200,
|
|
208
|
+
duration: 100
|
|
209
|
+
}
|
|
200
210
|
},
|
|
201
211
|
{
|
|
202
212
|
input: ['MSFT'],
|
|
203
|
-
output: 405.75
|
|
213
|
+
output: 405.75,
|
|
214
|
+
timing: {
|
|
215
|
+
startTime: 1200,
|
|
216
|
+
endTime: 1300,
|
|
217
|
+
duration: 100
|
|
218
|
+
}
|
|
204
219
|
},
|
|
205
220
|
{
|
|
206
221
|
input: ['GOOGL'],
|
|
207
|
-
output: 161.16
|
|
222
|
+
output: 161.16,
|
|
223
|
+
timing: {
|
|
224
|
+
startTime: 1300,
|
|
225
|
+
endTime: 1400,
|
|
226
|
+
duration: 100
|
|
227
|
+
}
|
|
208
228
|
}
|
|
209
229
|
]
|
|
210
230
|
}
|
|
@@ -239,6 +259,22 @@ describe('Complex boundary replay tests', () => {
|
|
|
239
259
|
// Verify that boundary calls were properly replayed
|
|
240
260
|
expect(replayLog.boundaries.fetchPortfolio).toHaveLength(1)
|
|
241
261
|
expect(replayLog.boundaries.fetchPrice).toHaveLength(3)
|
|
262
|
+
|
|
263
|
+
// Verify timing information is preserved
|
|
264
|
+
expect(replayLog.boundaries.fetchPortfolio[0]).toMatchObject({
|
|
265
|
+
timing: expect.objectContaining({
|
|
266
|
+
startTime: expect.any(Number),
|
|
267
|
+
endTime: expect.any(Number),
|
|
268
|
+
duration: expect.any(Number)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
expect(replayLog.boundaries.fetchPrice[0]).toMatchObject({
|
|
272
|
+
timing: expect.objectContaining({
|
|
273
|
+
startTime: expect.any(Number),
|
|
274
|
+
endTime: expect.any(Number),
|
|
275
|
+
duration: expect.any(Number)
|
|
276
|
+
})
|
|
277
|
+
})
|
|
242
278
|
})
|
|
243
279
|
|
|
244
280
|
it('Should handle errors during replay', async () => {
|
|
@@ -258,21 +294,41 @@ describe('Complex boundary replay tests', () => {
|
|
|
258
294
|
{ ticker: 'MSFT', quantity: 5 },
|
|
259
295
|
{ ticker: 'GOOGL', quantity: 8 }
|
|
260
296
|
]
|
|
297
|
+
},
|
|
298
|
+
timing: {
|
|
299
|
+
startTime: 2000,
|
|
300
|
+
endTime: 2100,
|
|
301
|
+
duration: 100
|
|
261
302
|
}
|
|
262
303
|
}
|
|
263
304
|
],
|
|
264
305
|
fetchPrice: [
|
|
265
306
|
{
|
|
266
307
|
input: ['AAPL'],
|
|
267
|
-
output: 190.50
|
|
308
|
+
output: 190.50,
|
|
309
|
+
timing: {
|
|
310
|
+
startTime: 2100,
|
|
311
|
+
endTime: 2200,
|
|
312
|
+
duration: 100
|
|
313
|
+
}
|
|
268
314
|
},
|
|
269
315
|
{
|
|
270
316
|
input: ['MSFT'],
|
|
271
|
-
output: 405.75
|
|
317
|
+
output: 405.75,
|
|
318
|
+
timing: {
|
|
319
|
+
startTime: 2200,
|
|
320
|
+
endTime: 2300,
|
|
321
|
+
duration: 100
|
|
322
|
+
}
|
|
272
323
|
},
|
|
273
324
|
{
|
|
274
325
|
input: ['GOOGL'],
|
|
275
|
-
error: 'Price data not available for ticker: GOOGL'
|
|
326
|
+
error: 'Price data not available for ticker: GOOGL',
|
|
327
|
+
timing: {
|
|
328
|
+
startTime: 2300,
|
|
329
|
+
endTime: 2350,
|
|
330
|
+
duration: 50
|
|
331
|
+
}
|
|
276
332
|
}
|
|
277
333
|
]
|
|
278
334
|
}
|
|
@@ -304,6 +360,15 @@ describe('Complex boundary replay tests', () => {
|
|
|
304
360
|
)
|
|
305
361
|
expect(googPriceCall).toBeDefined()
|
|
306
362
|
expect(googPriceCall?.error).toBe('Price data not available for ticker: GOOGL')
|
|
363
|
+
|
|
364
|
+
// Verify timing information is preserved even with errors
|
|
365
|
+
expect(googPriceCall).toMatchObject({
|
|
366
|
+
timing: expect.objectContaining({
|
|
367
|
+
startTime: expect.any(Number),
|
|
368
|
+
endTime: expect.any(Number),
|
|
369
|
+
duration: expect.any(Number)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
307
372
|
})
|
|
308
373
|
|
|
309
374
|
it('Should support mixed replay with some boundaries in replay mode and others in proxy mode', async () => {
|
|
@@ -335,21 +400,41 @@ describe('Complex boundary replay tests', () => {
|
|
|
335
400
|
{ ticker: 'MSFT', quantity: 5 },
|
|
336
401
|
{ ticker: 'GOOGL', quantity: 8 }
|
|
337
402
|
]
|
|
403
|
+
},
|
|
404
|
+
timing: {
|
|
405
|
+
startTime: 3000,
|
|
406
|
+
endTime: 3100,
|
|
407
|
+
duration: 100
|
|
338
408
|
}
|
|
339
409
|
}
|
|
340
410
|
],
|
|
341
411
|
fetchPrice: [
|
|
342
412
|
{
|
|
343
413
|
input: ['AAPL'],
|
|
344
|
-
output: 190.50
|
|
414
|
+
output: 190.50,
|
|
415
|
+
timing: {
|
|
416
|
+
startTime: 3100,
|
|
417
|
+
endTime: 3200,
|
|
418
|
+
duration: 100
|
|
419
|
+
}
|
|
345
420
|
},
|
|
346
421
|
{
|
|
347
422
|
input: ['MSFT'],
|
|
348
|
-
output: 405.75
|
|
423
|
+
output: 405.75,
|
|
424
|
+
timing: {
|
|
425
|
+
startTime: 3200,
|
|
426
|
+
endTime: 3300,
|
|
427
|
+
duration: 100
|
|
428
|
+
}
|
|
349
429
|
},
|
|
350
430
|
{
|
|
351
431
|
input: ['GOOGL'],
|
|
352
|
-
output: 161.16
|
|
432
|
+
output: 161.16,
|
|
433
|
+
timing: {
|
|
434
|
+
startTime: 3300,
|
|
435
|
+
endTime: 3400,
|
|
436
|
+
duration: 100
|
|
437
|
+
}
|
|
353
438
|
}
|
|
354
439
|
]
|
|
355
440
|
}
|
|
@@ -389,5 +474,25 @@ describe('Complex boundary replay tests', () => {
|
|
|
389
474
|
// Verify the log shows the mixed sources correctly
|
|
390
475
|
expect(replayLog.boundaries.fetchPortfolio).toHaveLength(1)
|
|
391
476
|
expect(replayLog.boundaries.fetchPrice).toHaveLength(3)
|
|
477
|
+
|
|
478
|
+
// Verify timing information for mixed execution
|
|
479
|
+
expect(replayLog.boundaries.fetchPortfolio[0]).toMatchObject({
|
|
480
|
+
timing: expect.objectContaining({
|
|
481
|
+
startTime: expect.any(Number),
|
|
482
|
+
endTime: expect.any(Number),
|
|
483
|
+
duration: expect.any(Number)
|
|
484
|
+
})
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// All price fetches should have timing since they execute in proxy mode
|
|
488
|
+
replayLog.boundaries.fetchPrice.forEach((call: BoundaryRecord) => {
|
|
489
|
+
expect(call).toMatchObject({
|
|
490
|
+
timing: expect.objectContaining({
|
|
491
|
+
startTime: expect.any(Number),
|
|
492
|
+
endTime: expect.any(Number),
|
|
493
|
+
duration: expect.any(Number)
|
|
494
|
+
})
|
|
495
|
+
})
|
|
496
|
+
})
|
|
392
497
|
})
|
|
393
498
|
})
|
|
@@ -72,7 +72,12 @@ describe('safeReplay functionality tests', () => {
|
|
|
72
72
|
fetchData: [
|
|
73
73
|
{
|
|
74
74
|
input: ['AAPL'],
|
|
75
|
-
output: 160.23
|
|
75
|
+
output: 160.23,
|
|
76
|
+
timing: {
|
|
77
|
+
startTime: 1000,
|
|
78
|
+
endTime: 1100,
|
|
79
|
+
duration: 100
|
|
80
|
+
}
|
|
76
81
|
}
|
|
77
82
|
]
|
|
78
83
|
}
|
|
@@ -102,6 +107,11 @@ describe('safeReplay functionality tests', () => {
|
|
|
102
107
|
{
|
|
103
108
|
input: ['AAPL'],
|
|
104
109
|
output: 150.23,
|
|
110
|
+
timing: expect.objectContaining({
|
|
111
|
+
startTime: expect.any(Number),
|
|
112
|
+
endTime: expect.any(Number),
|
|
113
|
+
duration: expect.any(Number)
|
|
114
|
+
})
|
|
105
115
|
}
|
|
106
116
|
]
|
|
107
117
|
}
|
|
@@ -120,7 +130,12 @@ describe('safeReplay functionality tests', () => {
|
|
|
120
130
|
fetchData: [
|
|
121
131
|
{
|
|
122
132
|
input: ['AAPL'],
|
|
123
|
-
output: 160.23
|
|
133
|
+
output: 160.23,
|
|
134
|
+
timing: {
|
|
135
|
+
startTime: 1000,
|
|
136
|
+
endTime: 1100,
|
|
137
|
+
duration: 100
|
|
138
|
+
}
|
|
124
139
|
}
|
|
125
140
|
]
|
|
126
141
|
}
|
|
@@ -153,7 +168,12 @@ describe('safeReplay functionality tests', () => {
|
|
|
153
168
|
fetchData: [
|
|
154
169
|
{
|
|
155
170
|
input: ['AAPL'],
|
|
156
|
-
output: 160.23
|
|
171
|
+
output: 160.23,
|
|
172
|
+
timing: expect.objectContaining({
|
|
173
|
+
startTime: expect.any(Number),
|
|
174
|
+
endTime: expect.any(Number),
|
|
175
|
+
duration: expect.any(Number)
|
|
176
|
+
})
|
|
157
177
|
}
|
|
158
178
|
]
|
|
159
179
|
}
|
|
@@ -170,7 +190,12 @@ describe('safeReplay functionality tests', () => {
|
|
|
170
190
|
fetchData: [
|
|
171
191
|
{
|
|
172
192
|
input: ['AAPL'],
|
|
173
|
-
error: 'API error: Rate limit exceeded'
|
|
193
|
+
error: 'API error: Rate limit exceeded',
|
|
194
|
+
timing: {
|
|
195
|
+
startTime: 1000,
|
|
196
|
+
endTime: 1100,
|
|
197
|
+
duration: 100
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
200
|
]
|
|
176
201
|
}
|
|
@@ -206,7 +231,12 @@ describe('safeReplay functionality tests', () => {
|
|
|
206
231
|
{
|
|
207
232
|
input: ['AAPL'],
|
|
208
233
|
output: 160.23,
|
|
209
|
-
error: null
|
|
234
|
+
error: null,
|
|
235
|
+
timing: {
|
|
236
|
+
startTime: 1000,
|
|
237
|
+
endTime: 1100,
|
|
238
|
+
duration: 100
|
|
239
|
+
}
|
|
210
240
|
}
|
|
211
241
|
]
|
|
212
242
|
}
|
|
@@ -40,6 +40,11 @@ describe('Task safeRun tests', () => {
|
|
|
40
40
|
expect(data.input).toEqual([5])
|
|
41
41
|
expect(data.output).toEqual(10)
|
|
42
42
|
expect(data.error).toBeUndefined()
|
|
43
|
+
expect(data.timing).toEqual(expect.objectContaining({
|
|
44
|
+
startTime: expect.any(Number),
|
|
45
|
+
endTime: expect.any(Number),
|
|
46
|
+
duration: expect.any(Number)
|
|
47
|
+
}))
|
|
43
48
|
})
|
|
44
49
|
|
|
45
50
|
it('returns [null, error, record] on failed execution', async () => {
|
|
@@ -87,6 +92,11 @@ describe('Task safeRun tests', () => {
|
|
|
87
92
|
expect(data.input).toEqual([-5])
|
|
88
93
|
expect(data.error).toContain('Value cannot be negative')
|
|
89
94
|
expect(data.output).toBeUndefined()
|
|
95
|
+
expect(data.timing).toEqual(expect.objectContaining({
|
|
96
|
+
startTime: expect.any(Number),
|
|
97
|
+
endTime: expect.any(Number),
|
|
98
|
+
duration: expect.any(Number)
|
|
99
|
+
}))
|
|
90
100
|
})
|
|
91
101
|
|
|
92
102
|
it('returns [null, error, record] on schema validation failure', async () => {
|
|
@@ -238,9 +248,41 @@ describe('Task safeRun tests', () => {
|
|
|
238
248
|
|
|
239
249
|
expect(record.boundaries.doubleValue).toHaveLength(3)
|
|
240
250
|
expect(record.boundaries.sumValues).toHaveLength(1)
|
|
241
|
-
expect(record.boundaries.doubleValue[0]).toEqual({
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
251
|
+
expect(record.boundaries.doubleValue[0]).toEqual({
|
|
252
|
+
input: [1],
|
|
253
|
+
output: 2,
|
|
254
|
+
timing: expect.objectContaining({
|
|
255
|
+
startTime: expect.any(Number),
|
|
256
|
+
endTime: expect.any(Number),
|
|
257
|
+
duration: expect.any(Number)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
expect(record.boundaries.doubleValue[1]).toEqual({
|
|
261
|
+
input: [2],
|
|
262
|
+
output: 4,
|
|
263
|
+
timing: expect.objectContaining({
|
|
264
|
+
startTime: expect.any(Number),
|
|
265
|
+
endTime: expect.any(Number),
|
|
266
|
+
duration: expect.any(Number)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
expect(record.boundaries.doubleValue[2]).toEqual({
|
|
270
|
+
input: [3],
|
|
271
|
+
output: 6,
|
|
272
|
+
timing: expect.objectContaining({
|
|
273
|
+
startTime: expect.any(Number),
|
|
274
|
+
endTime: expect.any(Number),
|
|
275
|
+
duration: expect.any(Number)
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
expect(record.boundaries.sumValues[0]).toEqual({
|
|
279
|
+
input: [[2, 4, 6]],
|
|
280
|
+
output: 12,
|
|
281
|
+
timing: expect.objectContaining({
|
|
282
|
+
startTime: expect.any(Number),
|
|
283
|
+
endTime: expect.any(Number),
|
|
284
|
+
duration: expect.any(Number)
|
|
285
|
+
})
|
|
286
|
+
})
|
|
245
287
|
})
|
|
246
288
|
})
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createTask, type Metric, validateMetric, createMetric } from '../index'
|
|
2
|
+
import { Schema } from '@forgehive/schema'
|
|
3
|
+
|
|
4
|
+
describe('setMetrics boundary', () => {
|
|
5
|
+
describe('validateMetric function', () => {
|
|
6
|
+
it('should validate correct metrics', () => {
|
|
7
|
+
const validMetric: Metric = {
|
|
8
|
+
type: 'performance',
|
|
9
|
+
name: 'response_time',
|
|
10
|
+
value: 150
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
expect(validateMetric(validMetric)).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should reject invalid metrics', () => {
|
|
17
|
+
expect(validateMetric(null)).toBe(false)
|
|
18
|
+
expect(validateMetric(undefined)).toBe(false)
|
|
19
|
+
expect(validateMetric({})).toBe(false)
|
|
20
|
+
expect(validateMetric({ type: '', name: 'test', value: 1 })).toBe(false)
|
|
21
|
+
expect(validateMetric({ type: 'test', name: '', value: 1 })).toBe(false)
|
|
22
|
+
expect(validateMetric({ type: 'test', name: 'test', value: NaN })).toBe(false)
|
|
23
|
+
expect(validateMetric({ type: 'test', name: 'test', value: Infinity })).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('createMetric function', () => {
|
|
28
|
+
it('should create valid metrics', () => {
|
|
29
|
+
const metric = createMetric('performance', 'response_time', 150)
|
|
30
|
+
|
|
31
|
+
expect(metric).toEqual({
|
|
32
|
+
type: 'performance',
|
|
33
|
+
name: 'response_time',
|
|
34
|
+
value: 150
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should throw for invalid metrics', () => {
|
|
39
|
+
expect(() => createMetric('', 'test', 1)).toThrow('Invalid metric')
|
|
40
|
+
expect(() => createMetric('test', '', 1)).toThrow('Invalid metric')
|
|
41
|
+
expect(() => createMetric('test', 'test', NaN)).toThrow('Invalid metric')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('setMetrics boundary integration', () => {
|
|
46
|
+
it('should collect metrics during task execution', async () => {
|
|
47
|
+
const schema = new Schema({
|
|
48
|
+
userId: Schema.string()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const task = createTask({
|
|
52
|
+
name: 'getUserData',
|
|
53
|
+
schema,
|
|
54
|
+
boundaries: {
|
|
55
|
+
fetchUser: async (userId: string) => ({ id: userId, name: 'Test User' })
|
|
56
|
+
},
|
|
57
|
+
fn: async ({ userId }, { fetchUser, setMetrics }) => {
|
|
58
|
+
// Add a performance metric
|
|
59
|
+
await setMetrics({
|
|
60
|
+
type: 'performance',
|
|
61
|
+
name: 'api_response_time',
|
|
62
|
+
value: 250
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const user = await fetchUser(userId)
|
|
66
|
+
|
|
67
|
+
// Add a business metric
|
|
68
|
+
await setMetrics({
|
|
69
|
+
type: 'business',
|
|
70
|
+
name: 'users_processed',
|
|
71
|
+
value: 1
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return { user }
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const [result, error, record] = await task.safeRun({ userId: 'user123' })
|
|
79
|
+
|
|
80
|
+
expect(error).toBeNull()
|
|
81
|
+
expect(result).toEqual({
|
|
82
|
+
user: { id: 'user123', name: 'Test User' }
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Check that metrics were collected
|
|
86
|
+
expect(record.metrics).toHaveLength(2)
|
|
87
|
+
expect(record.metrics?.[0]).toEqual({
|
|
88
|
+
type: 'performance',
|
|
89
|
+
name: 'api_response_time',
|
|
90
|
+
value: 250
|
|
91
|
+
})
|
|
92
|
+
expect(record.metrics?.[1]).toEqual({
|
|
93
|
+
type: 'business',
|
|
94
|
+
name: 'users_processed',
|
|
95
|
+
value: 1
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Check that timing was captured
|
|
99
|
+
expect(record.timing).toEqual(expect.objectContaining({
|
|
100
|
+
startTime: expect.any(Number),
|
|
101
|
+
endTime: expect.any(Number),
|
|
102
|
+
duration: expect.any(Number)
|
|
103
|
+
}))
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should validate metrics and reject invalid ones', async () => {
|
|
107
|
+
const schema = new Schema({
|
|
108
|
+
value: Schema.number()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const task = createTask({
|
|
112
|
+
name: 'testValidation',
|
|
113
|
+
schema,
|
|
114
|
+
boundaries: {},
|
|
115
|
+
fn: async ({ value }, { setMetrics }) => {
|
|
116
|
+
// Try to set an invalid metric
|
|
117
|
+
await setMetrics({
|
|
118
|
+
type: '',
|
|
119
|
+
name: 'invalid_metric',
|
|
120
|
+
value: value
|
|
121
|
+
} as Metric)
|
|
122
|
+
|
|
123
|
+
return { result: value }
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const [result, error, record] = await task.safeRun({ value: 42 })
|
|
128
|
+
|
|
129
|
+
expect(result).toBeNull()
|
|
130
|
+
expect(error).not.toBeNull()
|
|
131
|
+
expect(error?.message).toContain('Invalid metric provided')
|
|
132
|
+
expect(record.type).toBe('error')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should accumulate multiple metrics correctly', async () => {
|
|
136
|
+
const schema = new Schema({
|
|
137
|
+
count: Schema.number()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const task = createTask({
|
|
141
|
+
name: 'accumulateMetrics',
|
|
142
|
+
schema,
|
|
143
|
+
boundaries: {},
|
|
144
|
+
fn: async ({ count }, { setMetrics }) => {
|
|
145
|
+
// Add metrics in a loop
|
|
146
|
+
for (let i = 0; i < count; i++) {
|
|
147
|
+
await setMetrics({
|
|
148
|
+
type: 'processing',
|
|
149
|
+
name: `item_${i}`,
|
|
150
|
+
value: i * 10
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { processedCount: count }
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const [result, error, record] = await task.safeRun({ count: 3 })
|
|
159
|
+
|
|
160
|
+
expect(error).toBeNull()
|
|
161
|
+
expect(result).toEqual({ processedCount: 3 })
|
|
162
|
+
|
|
163
|
+
// Check that all metrics were collected
|
|
164
|
+
expect(record.metrics).toHaveLength(3)
|
|
165
|
+
expect(record.metrics?.[0]).toEqual({
|
|
166
|
+
type: 'processing',
|
|
167
|
+
name: 'item_0',
|
|
168
|
+
value: 0
|
|
169
|
+
})
|
|
170
|
+
expect(record.metrics?.[1]).toEqual({
|
|
171
|
+
type: 'processing',
|
|
172
|
+
name: 'item_1',
|
|
173
|
+
value: 10
|
|
174
|
+
})
|
|
175
|
+
expect(record.metrics?.[2]).toEqual({
|
|
176
|
+
type: 'processing',
|
|
177
|
+
name: 'item_2',
|
|
178
|
+
value: 20
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should not include setMetrics calls in boundary logs', async () => {
|
|
183
|
+
const schema = new Schema({
|
|
184
|
+
value: Schema.number()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const task = createTask({
|
|
188
|
+
name: 'metricsNotInBoundaries',
|
|
189
|
+
schema,
|
|
190
|
+
boundaries: {
|
|
191
|
+
calculate: async (val: number) => val * 2
|
|
192
|
+
},
|
|
193
|
+
fn: async ({ value }, { calculate, setMetrics }) => {
|
|
194
|
+
const result = await calculate(value)
|
|
195
|
+
|
|
196
|
+
await setMetrics({
|
|
197
|
+
type: 'calculation',
|
|
198
|
+
name: 'result_value',
|
|
199
|
+
value: result
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return { result }
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const [, error, record] = await task.safeRun({ value: 5 })
|
|
207
|
+
|
|
208
|
+
expect(error).toBeNull()
|
|
209
|
+
|
|
210
|
+
// Verify that setMetrics is not in the boundary logs
|
|
211
|
+
expect(record.boundaries).toHaveProperty('calculate')
|
|
212
|
+
expect(record.boundaries).not.toHaveProperty('setMetrics')
|
|
213
|
+
|
|
214
|
+
// But metrics should be collected
|
|
215
|
+
expect(record.metrics).toHaveLength(1)
|
|
216
|
+
expect(record.metrics?.[0]).toEqual({
|
|
217
|
+
type: 'calculation',
|
|
218
|
+
name: 'result_value',
|
|
219
|
+
value: 10
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
})
|