@forgehive/task 0.2.3 → 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 +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -9
- 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/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 +63 -8
- 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/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
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { createTask, validateMetric, createMetric } from '../index'
|
|
2
|
+
import { Schema } from '@forgehive/schema'
|
|
3
|
+
|
|
4
|
+
describe('Metrics Collection Tests', () => {
|
|
5
|
+
describe('setMetrics boundary functionality and validation', () => {
|
|
6
|
+
it('should validate metrics with correct structure', () => {
|
|
7
|
+
const validMetric = { type: 'business', name: 'user_count', value: 42 }
|
|
8
|
+
expect(validateMetric(validMetric)).toBe(true)
|
|
9
|
+
|
|
10
|
+
const performanceMetric = { type: 'performance', name: 'response_time', value: 150.5 }
|
|
11
|
+
expect(validateMetric(performanceMetric)).toBe(true)
|
|
12
|
+
|
|
13
|
+
const errorMetric = { type: 'error', name: 'failed_requests', value: 0 }
|
|
14
|
+
expect(validateMetric(errorMetric)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should reject invalid metric structures', () => {
|
|
18
|
+
expect(validateMetric(null)).toBe(false)
|
|
19
|
+
expect(validateMetric(undefined)).toBe(false)
|
|
20
|
+
expect(validateMetric({})).toBe(false)
|
|
21
|
+
expect(validateMetric({ type: 'business' })).toBe(false) // missing name and value
|
|
22
|
+
expect(validateMetric({ name: 'test', value: 1 })).toBe(false) // missing type
|
|
23
|
+
expect(validateMetric({ type: 'business', name: 'test' })).toBe(false) // missing value
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should reject metrics with invalid types', () => {
|
|
27
|
+
expect(validateMetric({ type: '', name: 'test', value: 1 })).toBe(false) // empty type
|
|
28
|
+
expect(validateMetric({ type: 123, name: 'test', value: 1 })).toBe(false) // numeric type
|
|
29
|
+
expect(validateMetric({ type: 'business', name: '', value: 1 })).toBe(false) // empty name
|
|
30
|
+
expect(validateMetric({ type: 'business', name: 123, value: 1 })).toBe(false) // numeric name
|
|
31
|
+
expect(validateMetric({ type: 'business', name: 'test', value: 'not-a-number' })).toBe(false) // string value
|
|
32
|
+
expect(validateMetric({ type: 'business', name: 'test', value: NaN })).toBe(false) // NaN value
|
|
33
|
+
expect(validateMetric({ type: 'business', name: 'test', value: Infinity })).toBe(false) // Infinity value
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should create valid metrics using createMetric helper', () => {
|
|
37
|
+
const metric = createMetric('performance', 'api_response_time', 250)
|
|
38
|
+
expect(metric).toEqual({
|
|
39
|
+
type: 'performance',
|
|
40
|
+
name: 'api_response_time',
|
|
41
|
+
value: 250
|
|
42
|
+
})
|
|
43
|
+
expect(validateMetric(metric)).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should throw error when creating metric with invalid data', () => {
|
|
47
|
+
expect(() => createMetric('', 'test', 1)).toThrow('Invalid metric')
|
|
48
|
+
expect(() => createMetric('business', '', 1)).toThrow('Invalid metric')
|
|
49
|
+
expect(() => createMetric('business', 'test', NaN)).toThrow('Invalid metric')
|
|
50
|
+
expect(() => createMetric('business', 'test', Infinity)).toThrow('Invalid metric')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('Metrics accumulation and storage in execution records', () => {
|
|
55
|
+
it('should collect single metric in execution record', async () => {
|
|
56
|
+
const task = createTask({
|
|
57
|
+
name: 'single-metric-test',
|
|
58
|
+
schema: new Schema({ input: Schema.string() }),
|
|
59
|
+
boundaries: {},
|
|
60
|
+
fn: async ({ input }, { setMetrics }) => {
|
|
61
|
+
await setMetrics({
|
|
62
|
+
type: 'business',
|
|
63
|
+
name: 'items_processed',
|
|
64
|
+
value: 1
|
|
65
|
+
})
|
|
66
|
+
return { result: input.toUpperCase() }
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const [result, error, record] = await task.safeRun({ input: 'test' })
|
|
71
|
+
|
|
72
|
+
expect(error).toBeNull()
|
|
73
|
+
expect(result).toEqual({ result: 'TEST' })
|
|
74
|
+
expect(record.metrics).toHaveLength(1)
|
|
75
|
+
expect(record.metrics?.[0]).toEqual({
|
|
76
|
+
type: 'business',
|
|
77
|
+
name: 'items_processed',
|
|
78
|
+
value: 1
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should accumulate multiple metrics in execution record', async () => {
|
|
83
|
+
const task = createTask({
|
|
84
|
+
name: 'multiple-metrics-test',
|
|
85
|
+
schema: new Schema({ count: Schema.number() }),
|
|
86
|
+
boundaries: {},
|
|
87
|
+
fn: async ({ count }, { setMetrics }) => {
|
|
88
|
+
await setMetrics({ type: 'business', name: 'input_count', value: count })
|
|
89
|
+
await setMetrics({ type: 'performance', name: 'processing_time', value: 150 })
|
|
90
|
+
await setMetrics({ type: 'error', name: 'error_count', value: 0 })
|
|
91
|
+
|
|
92
|
+
return { processed: count * 2 }
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const [result, error, record] = await task.safeRun({ count: 5 })
|
|
97
|
+
|
|
98
|
+
expect(error).toBeNull()
|
|
99
|
+
expect(result).toEqual({ processed: 10 })
|
|
100
|
+
expect(record.metrics).toHaveLength(3)
|
|
101
|
+
|
|
102
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
103
|
+
{ type: 'business', name: 'input_count', value: 5 },
|
|
104
|
+
{ type: 'performance', name: 'processing_time', value: 150 },
|
|
105
|
+
{ type: 'error', name: 'error_count', value: 0 }
|
|
106
|
+
]))
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should allow duplicate metric names with different values', async () => {
|
|
110
|
+
const task = createTask({
|
|
111
|
+
name: 'duplicate-metrics-test',
|
|
112
|
+
schema: new Schema({ iterations: Schema.number() }),
|
|
113
|
+
boundaries: {},
|
|
114
|
+
fn: async ({ iterations }, { setMetrics }) => {
|
|
115
|
+
for (let i = 0; i < iterations; i++) {
|
|
116
|
+
await setMetrics({
|
|
117
|
+
type: 'performance',
|
|
118
|
+
name: 'iteration_time',
|
|
119
|
+
value: (i + 1) * 10
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
return { completed: iterations }
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const [result, error, record] = await task.safeRun({ iterations: 3 })
|
|
127
|
+
|
|
128
|
+
expect(error).toBeNull()
|
|
129
|
+
expect(result).toEqual({ completed: 3 })
|
|
130
|
+
expect(record.metrics).toHaveLength(3)
|
|
131
|
+
|
|
132
|
+
expect(record.metrics).toEqual([
|
|
133
|
+
{ type: 'performance', name: 'iteration_time', value: 10 },
|
|
134
|
+
{ type: 'performance', name: 'iteration_time', value: 20 },
|
|
135
|
+
{ type: 'performance', name: 'iteration_time', value: 30 }
|
|
136
|
+
])
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should collect metrics from boundaries and main function', async () => {
|
|
140
|
+
const task = createTask({
|
|
141
|
+
name: 'boundary-metrics-test',
|
|
142
|
+
schema: new Schema({ input: Schema.string() }),
|
|
143
|
+
boundaries: {
|
|
144
|
+
processData: async (data: string) => {
|
|
145
|
+
return data.length
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
fn: async ({ input }, { processData, setMetrics }) => {
|
|
149
|
+
await setMetrics({ type: 'business', name: 'requests', value: 1 })
|
|
150
|
+
|
|
151
|
+
const length = await processData(input)
|
|
152
|
+
await setMetrics({ type: 'business', name: 'input_length', value: length })
|
|
153
|
+
|
|
154
|
+
return { length }
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const [result, error, record] = await task.safeRun({ input: 'hello world' })
|
|
159
|
+
|
|
160
|
+
expect(error).toBeNull()
|
|
161
|
+
expect(result).toEqual({ length: 11 })
|
|
162
|
+
expect(record.metrics).toHaveLength(2)
|
|
163
|
+
|
|
164
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
165
|
+
{ type: 'business', name: 'requests', value: 1 },
|
|
166
|
+
{ type: 'business', name: 'input_length', value: 11 }
|
|
167
|
+
]))
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('Metrics behavior in error scenarios', () => {
|
|
172
|
+
it('should preserve metrics collected before error occurs', async () => {
|
|
173
|
+
const task = createTask({
|
|
174
|
+
name: 'error-metrics-test',
|
|
175
|
+
schema: new Schema({ shouldFail: Schema.boolean() }),
|
|
176
|
+
boundaries: {},
|
|
177
|
+
fn: async ({ shouldFail }, { setMetrics }) => {
|
|
178
|
+
await setMetrics({ type: 'business', name: 'attempt_count', value: 1 })
|
|
179
|
+
await setMetrics({ type: 'performance', name: 'preparation_time', value: 50 })
|
|
180
|
+
|
|
181
|
+
if (shouldFail) {
|
|
182
|
+
await setMetrics({ type: 'error', name: 'failure_count', value: 1 })
|
|
183
|
+
throw new Error('Intentional failure')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { success: true }
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const [result, error, record] = await task.safeRun({ shouldFail: true })
|
|
191
|
+
|
|
192
|
+
expect(result).toBeNull()
|
|
193
|
+
expect(error).not.toBeNull()
|
|
194
|
+
expect(record.type).toBe('error')
|
|
195
|
+
expect(record.metrics).toHaveLength(3)
|
|
196
|
+
|
|
197
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
198
|
+
{ type: 'business', name: 'attempt_count', value: 1 },
|
|
199
|
+
{ type: 'performance', name: 'preparation_time', value: 50 },
|
|
200
|
+
{ type: 'error', name: 'failure_count', value: 1 }
|
|
201
|
+
]))
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should handle boundary errors while preserving metrics', async () => {
|
|
205
|
+
const task = createTask({
|
|
206
|
+
name: 'boundary-error-metrics-test',
|
|
207
|
+
schema: new Schema({ input: Schema.string() }),
|
|
208
|
+
boundaries: {
|
|
209
|
+
failingBoundary: async (data: string) => {
|
|
210
|
+
throw new Error(`Boundary failed for: ${data}`)
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
fn: async ({ input }, { failingBoundary, setMetrics }) => {
|
|
214
|
+
await setMetrics({ type: 'business', name: 'attempts', value: 1 })
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
await failingBoundary(input)
|
|
218
|
+
} catch (error) {
|
|
219
|
+
await setMetrics({ type: 'error', name: 'boundary_failures', value: 1 })
|
|
220
|
+
throw error
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { success: true }
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const [result, error, record] = await task.safeRun({ input: 'test' })
|
|
228
|
+
|
|
229
|
+
expect(result).toBeNull()
|
|
230
|
+
expect(error).not.toBeNull()
|
|
231
|
+
expect(record.type).toBe('error')
|
|
232
|
+
expect(record.metrics).toHaveLength(2)
|
|
233
|
+
|
|
234
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
235
|
+
{ type: 'business', name: 'attempts', value: 1 },
|
|
236
|
+
{ type: 'error', name: 'boundary_failures', value: 1 }
|
|
237
|
+
]))
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should reject invalid metrics and continue execution', async () => {
|
|
241
|
+
const task = createTask({
|
|
242
|
+
name: 'invalid-metrics-test',
|
|
243
|
+
schema: new Schema({ input: Schema.string() }),
|
|
244
|
+
boundaries: {},
|
|
245
|
+
fn: async ({ input }, { setMetrics }) => {
|
|
246
|
+
// Valid metric should be stored
|
|
247
|
+
await setMetrics({ type: 'business', name: 'valid_metric', value: 1 })
|
|
248
|
+
|
|
249
|
+
// Invalid metrics should be rejected but not crash the task
|
|
250
|
+
try {
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
+
await setMetrics({ type: '', name: 'invalid', value: 1 } as any)
|
|
253
|
+
} catch (error) {
|
|
254
|
+
// Expected to fail validation
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
await setMetrics({ type: 'business', name: 'invalid', value: NaN } as any)
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// Expected to fail validation
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Another valid metric should still work
|
|
265
|
+
await setMetrics({ type: 'performance', name: 'final_metric', value: 100 })
|
|
266
|
+
|
|
267
|
+
return { result: input }
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const [result, error, record] = await task.safeRun({ input: 'test' })
|
|
272
|
+
|
|
273
|
+
expect(error).toBeNull()
|
|
274
|
+
expect(result).toEqual({ result: 'test' })
|
|
275
|
+
expect(record.metrics).toHaveLength(2) // Only valid metrics should be stored
|
|
276
|
+
|
|
277
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
278
|
+
{ type: 'business', name: 'valid_metric', value: 1 },
|
|
279
|
+
{ type: 'performance', name: 'final_metric', value: 100 }
|
|
280
|
+
]))
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
describe('Metrics behavior during replay', () => {
|
|
285
|
+
it('should preserve original metrics during replay', async () => {
|
|
286
|
+
const task = createTask({
|
|
287
|
+
name: 'replay-metrics-test',
|
|
288
|
+
schema: new Schema({ input: Schema.string() }),
|
|
289
|
+
boundaries: {
|
|
290
|
+
dataFetch: async (query: string) => {
|
|
291
|
+
return `data-for-${query}`
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
fn: async ({ input }, { dataFetch, setMetrics }) => {
|
|
295
|
+
await setMetrics({ type: 'business', name: 'queries', value: 1 })
|
|
296
|
+
const data = await dataFetch(input)
|
|
297
|
+
await setMetrics({ type: 'performance', name: 'data_size', value: data.length })
|
|
298
|
+
return { data }
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// First run to get original execution
|
|
303
|
+
const [originalResult, originalError, originalRecord] = await task.safeRun({ input: 'test' })
|
|
304
|
+
|
|
305
|
+
expect(originalError).toBeNull()
|
|
306
|
+
expect(originalResult).toEqual({ data: 'data-for-test' })
|
|
307
|
+
expect(originalRecord.metrics).toHaveLength(2)
|
|
308
|
+
|
|
309
|
+
// Replay the execution
|
|
310
|
+
const [replayResult, replayError, replayRecord] = await task.safeReplay(originalRecord, { boundaries: {} })
|
|
311
|
+
|
|
312
|
+
expect(replayError).toBeNull()
|
|
313
|
+
expect(replayResult).toEqual({ data: 'data-for-test' })
|
|
314
|
+
|
|
315
|
+
// Replay should preserve original metrics and may add new ones
|
|
316
|
+
expect(replayRecord.metrics?.length).toBeGreaterThanOrEqual(2)
|
|
317
|
+
|
|
318
|
+
// Original metrics should be included
|
|
319
|
+
expect(replayRecord.metrics).toEqual(expect.arrayContaining([
|
|
320
|
+
{ type: 'business', name: 'queries', value: 1 },
|
|
321
|
+
{ type: 'performance', name: 'data_size', value: 13 }
|
|
322
|
+
]))
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('should allow new metrics during replay execution', async () => {
|
|
326
|
+
const task = createTask({
|
|
327
|
+
name: 'replay-new-metrics-test',
|
|
328
|
+
schema: new Schema({ mode: Schema.string() }),
|
|
329
|
+
boundaries: {
|
|
330
|
+
operation: async (mode: string) => {
|
|
331
|
+
return `result-${mode}`
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
fn: async ({ mode }, { operation, setMetrics }) => {
|
|
335
|
+
if (mode === 'original') {
|
|
336
|
+
await setMetrics({ type: 'business', name: 'original_run', value: 1 })
|
|
337
|
+
} else if (mode === 'replay') {
|
|
338
|
+
await setMetrics({ type: 'business', name: 'replay_run', value: 1 })
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = await operation(mode)
|
|
342
|
+
await setMetrics({ type: 'performance', name: 'execution_count', value: 1 })
|
|
343
|
+
|
|
344
|
+
return { result }
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
// Original run
|
|
349
|
+
const [, originalError, originalRecord] = await task.safeRun({ mode: 'original' })
|
|
350
|
+
expect(originalError).toBeNull()
|
|
351
|
+
expect(originalRecord.metrics).toHaveLength(2)
|
|
352
|
+
|
|
353
|
+
// Modify the record for replay to change the mode
|
|
354
|
+
const modifiedRecord = {
|
|
355
|
+
...originalRecord,
|
|
356
|
+
input: { mode: 'replay' }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Replay with different mode
|
|
360
|
+
const [, replayError, replayRecord] = await task.safeReplay(modifiedRecord, { boundaries: {} })
|
|
361
|
+
|
|
362
|
+
expect(replayError).toBeNull()
|
|
363
|
+
expect(replayRecord.metrics?.length).toBeGreaterThanOrEqual(2)
|
|
364
|
+
|
|
365
|
+
// Should have both original metrics and new replay metrics
|
|
366
|
+
const metricNames = replayRecord.metrics?.map(m => m.name) || []
|
|
367
|
+
expect(metricNames).toContain('execution_count')
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
describe('Performance metrics and complex scenarios', () => {
|
|
372
|
+
it('should handle high-frequency metric collection', async () => {
|
|
373
|
+
const task = createTask({
|
|
374
|
+
name: 'high-frequency-metrics-test',
|
|
375
|
+
schema: new Schema({ count: Schema.number() }),
|
|
376
|
+
boundaries: {},
|
|
377
|
+
fn: async ({ count }, { setMetrics }) => {
|
|
378
|
+
for (let i = 0; i < count; i++) {
|
|
379
|
+
await setMetrics({
|
|
380
|
+
type: 'performance',
|
|
381
|
+
name: 'iteration',
|
|
382
|
+
value: i
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
return { completed: count }
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
const [result, error, record] = await task.safeRun({ count: 100 })
|
|
390
|
+
|
|
391
|
+
expect(error).toBeNull()
|
|
392
|
+
expect(result).toEqual({ completed: 100 })
|
|
393
|
+
expect(record.metrics).toHaveLength(100)
|
|
394
|
+
|
|
395
|
+
// Verify all metrics were collected correctly
|
|
396
|
+
record.metrics?.forEach((metric, index) => {
|
|
397
|
+
expect(metric).toEqual({
|
|
398
|
+
type: 'performance',
|
|
399
|
+
name: 'iteration',
|
|
400
|
+
value: index
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should support different metric types in complex workflow', async () => {
|
|
406
|
+
const task = createTask({
|
|
407
|
+
name: 'complex-workflow-metrics-test',
|
|
408
|
+
schema: new Schema({
|
|
409
|
+
userId: Schema.string(),
|
|
410
|
+
operations: Schema.array(Schema.string())
|
|
411
|
+
}),
|
|
412
|
+
boundaries: {
|
|
413
|
+
validateUser: async (userId: string) => {
|
|
414
|
+
return userId.length > 0
|
|
415
|
+
},
|
|
416
|
+
processOperation: async (operation: string) => {
|
|
417
|
+
return `processed-${operation}`
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
fn: async ({ userId, operations }, { validateUser, processOperation, setMetrics }) => {
|
|
421
|
+
// Business metrics
|
|
422
|
+
await setMetrics({ type: 'business', name: 'user_requests', value: 1 })
|
|
423
|
+
await setMetrics({ type: 'business', name: 'operation_count', value: operations.length })
|
|
424
|
+
|
|
425
|
+
const startTime = Date.now()
|
|
426
|
+
|
|
427
|
+
// Validate user
|
|
428
|
+
const isValid = await validateUser(userId)
|
|
429
|
+
if (!isValid) {
|
|
430
|
+
await setMetrics({ type: 'error', name: 'validation_failures', value: 1 })
|
|
431
|
+
throw new Error('Invalid user')
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Process operations
|
|
435
|
+
const results = []
|
|
436
|
+
for (const operation of operations) {
|
|
437
|
+
const result = await processOperation(operation)
|
|
438
|
+
results.push(result)
|
|
439
|
+
await setMetrics({ type: 'business', name: 'operations_processed', value: 1 })
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Performance metrics
|
|
443
|
+
const duration = Date.now() - startTime
|
|
444
|
+
await setMetrics({ type: 'performance', name: 'total_processing_time', value: duration })
|
|
445
|
+
await setMetrics({ type: 'performance', name: 'avg_operation_time', value: duration / operations.length })
|
|
446
|
+
|
|
447
|
+
// Success metrics
|
|
448
|
+
await setMetrics({ type: 'business', name: 'successful_requests', value: 1 })
|
|
449
|
+
await setMetrics({ type: 'error', name: 'error_count', value: 0 })
|
|
450
|
+
|
|
451
|
+
return { userId, results, processingTime: duration }
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const [result, error, record] = await task.safeRun({
|
|
456
|
+
userId: 'user123',
|
|
457
|
+
operations: ['op1', 'op2', 'op3']
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
expect(error).toBeNull()
|
|
461
|
+
expect(result?.userId).toBe('user123')
|
|
462
|
+
expect(result?.results).toEqual(['processed-op1', 'processed-op2', 'processed-op3'])
|
|
463
|
+
|
|
464
|
+
// Should have collected multiple types of metrics
|
|
465
|
+
expect(record.metrics?.length).toBeGreaterThanOrEqual(8)
|
|
466
|
+
|
|
467
|
+
const businessMetrics = record.metrics?.filter(m => m.type === 'business') || []
|
|
468
|
+
const performanceMetrics = record.metrics?.filter(m => m.type === 'performance') || []
|
|
469
|
+
const errorMetrics = record.metrics?.filter(m => m.type === 'error') || []
|
|
470
|
+
|
|
471
|
+
expect(businessMetrics.length).toBeGreaterThanOrEqual(5)
|
|
472
|
+
expect(performanceMetrics.length).toBeGreaterThanOrEqual(2)
|
|
473
|
+
expect(errorMetrics.length).toBeGreaterThanOrEqual(1)
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
})
|