@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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../index");
|
|
4
|
+
describe('Task.listenExecutionRecords', () => {
|
|
5
|
+
let mockListener;
|
|
6
|
+
let consoleErrorSpy;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockListener = jest.fn();
|
|
9
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
10
|
+
// Clear any existing global listener
|
|
11
|
+
index_1.Task.globalListener = undefined;
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
// Clean up global listener after each test
|
|
15
|
+
index_1.Task.globalListener = undefined;
|
|
16
|
+
consoleErrorSpy.mockRestore();
|
|
17
|
+
});
|
|
18
|
+
describe('static listenExecutionRecords method', () => {
|
|
19
|
+
it('should set global listener', () => {
|
|
20
|
+
index_1.Task.listenExecutionRecords(mockListener);
|
|
21
|
+
expect(index_1.Task.globalListener).toBe(mockListener);
|
|
22
|
+
});
|
|
23
|
+
it('should replace existing global listener', () => {
|
|
24
|
+
const firstListener = jest.fn();
|
|
25
|
+
const secondListener = jest.fn();
|
|
26
|
+
index_1.Task.listenExecutionRecords(firstListener);
|
|
27
|
+
expect(index_1.Task.globalListener).toBe(firstListener);
|
|
28
|
+
index_1.Task.listenExecutionRecords(secondListener);
|
|
29
|
+
expect(index_1.Task.globalListener).toBe(secondListener);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('global listener execution', () => {
|
|
33
|
+
it('should call global listener when task is executed via safeRun', async () => {
|
|
34
|
+
index_1.Task.listenExecutionRecords(mockListener);
|
|
35
|
+
const schema = new index_1.Schema({
|
|
36
|
+
value: index_1.Schema.number()
|
|
37
|
+
});
|
|
38
|
+
const testTask = (0, index_1.createTask)({
|
|
39
|
+
schema,
|
|
40
|
+
boundaries: {},
|
|
41
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
42
|
+
});
|
|
43
|
+
await testTask.safeRun({ value: 5 });
|
|
44
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
45
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
46
|
+
expect(mockListener).toHaveBeenCalledWith(expect.objectContaining({
|
|
47
|
+
input: { value: 5 },
|
|
48
|
+
output: { result: 10 },
|
|
49
|
+
type: 'success'
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
it('should call global listener when task is executed via run', async () => {
|
|
53
|
+
index_1.Task.listenExecutionRecords(mockListener);
|
|
54
|
+
const schema = new index_1.Schema({
|
|
55
|
+
value: index_1.Schema.number()
|
|
56
|
+
});
|
|
57
|
+
const testTask = (0, index_1.createTask)({
|
|
58
|
+
schema,
|
|
59
|
+
boundaries: {},
|
|
60
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
61
|
+
});
|
|
62
|
+
await testTask.run({ value: 3 });
|
|
63
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
64
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(mockListener).toHaveBeenCalledWith(expect.objectContaining({
|
|
66
|
+
input: { value: 3 },
|
|
67
|
+
output: { result: 6 },
|
|
68
|
+
type: 'success'
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
it('should call global listener when task fails', async () => {
|
|
72
|
+
index_1.Task.listenExecutionRecords(mockListener);
|
|
73
|
+
const schema = new index_1.Schema({});
|
|
74
|
+
const errorTask = (0, index_1.createTask)({
|
|
75
|
+
schema,
|
|
76
|
+
boundaries: {},
|
|
77
|
+
fn: async () => {
|
|
78
|
+
throw new Error('Test error');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
await errorTask.safeRun({});
|
|
82
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
83
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(mockListener).toHaveBeenCalledWith(expect.objectContaining({
|
|
85
|
+
input: {},
|
|
86
|
+
error: 'Test error',
|
|
87
|
+
type: 'error'
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
it('should call both instance and global listeners', async () => {
|
|
91
|
+
const instanceListener = jest.fn();
|
|
92
|
+
index_1.Task.listenExecutionRecords(mockListener);
|
|
93
|
+
const schema = new index_1.Schema({
|
|
94
|
+
value: index_1.Schema.number()
|
|
95
|
+
});
|
|
96
|
+
const testTask = (0, index_1.createTask)({
|
|
97
|
+
schema,
|
|
98
|
+
boundaries: {},
|
|
99
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
100
|
+
});
|
|
101
|
+
testTask.addListener(instanceListener);
|
|
102
|
+
await testTask.safeRun({ value: 7 });
|
|
103
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
104
|
+
expect(instanceListener).toHaveBeenCalledTimes(1);
|
|
105
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
106
|
+
const expectedRecord = expect.objectContaining({
|
|
107
|
+
input: { value: 7 },
|
|
108
|
+
output: { result: 14 },
|
|
109
|
+
type: 'success'
|
|
110
|
+
});
|
|
111
|
+
expect(instanceListener).toHaveBeenCalledWith(expectedRecord);
|
|
112
|
+
expect(mockListener).toHaveBeenCalledWith(expectedRecord);
|
|
113
|
+
});
|
|
114
|
+
it('should work when no global listener is set', async () => {
|
|
115
|
+
// No global listener set
|
|
116
|
+
const schema = new index_1.Schema({
|
|
117
|
+
value: index_1.Schema.number()
|
|
118
|
+
});
|
|
119
|
+
const testTask = (0, index_1.createTask)({
|
|
120
|
+
schema,
|
|
121
|
+
boundaries: {},
|
|
122
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
123
|
+
});
|
|
124
|
+
// Should not throw error
|
|
125
|
+
await expect(testTask.safeRun({ value: 1 })).resolves.toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('async listener support', () => {
|
|
129
|
+
it('should support async global listeners', async () => {
|
|
130
|
+
const asyncListener = jest.fn().mockImplementation(async (_record) => {
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
});
|
|
134
|
+
index_1.Task.listenExecutionRecords(asyncListener);
|
|
135
|
+
const schema = new index_1.Schema({
|
|
136
|
+
value: index_1.Schema.number()
|
|
137
|
+
});
|
|
138
|
+
const testTask = (0, index_1.createTask)({
|
|
139
|
+
schema,
|
|
140
|
+
boundaries: {},
|
|
141
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
142
|
+
});
|
|
143
|
+
await testTask.safeRun({ value: 4 });
|
|
144
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
145
|
+
expect(asyncListener).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(asyncListener).toHaveBeenCalledWith(expect.objectContaining({
|
|
147
|
+
input: { value: 4 },
|
|
148
|
+
output: { result: 8 },
|
|
149
|
+
type: 'success'
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
it('should handle long-running async listeners without blocking task execution', async () => {
|
|
153
|
+
const slowListener = jest.fn().mockImplementation(async () => {
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms
|
|
155
|
+
});
|
|
156
|
+
index_1.Task.listenExecutionRecords(slowListener);
|
|
157
|
+
const schema = new index_1.Schema({
|
|
158
|
+
value: index_1.Schema.number()
|
|
159
|
+
});
|
|
160
|
+
const testTask = (0, index_1.createTask)({
|
|
161
|
+
schema,
|
|
162
|
+
boundaries: {},
|
|
163
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
164
|
+
});
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
await testTask.safeRun({ value: 1 });
|
|
167
|
+
const endTime = Date.now();
|
|
168
|
+
// Task should complete quickly without waiting for listener
|
|
169
|
+
expect(endTime - startTime).toBeLessThan(50); // Should complete in under 50ms
|
|
170
|
+
// Wait for next tick to ensure listener was called
|
|
171
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
172
|
+
expect(slowListener).toHaveBeenCalledTimes(1);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('error handling', () => {
|
|
176
|
+
it('should catch and log listener errors without affecting task execution', async () => {
|
|
177
|
+
const errorListener = jest.fn().mockImplementation(() => {
|
|
178
|
+
throw new Error('Listener error');
|
|
179
|
+
});
|
|
180
|
+
index_1.Task.listenExecutionRecords(errorListener);
|
|
181
|
+
const schema = new index_1.Schema({
|
|
182
|
+
value: index_1.Schema.number()
|
|
183
|
+
});
|
|
184
|
+
const testTask = (0, index_1.createTask)({
|
|
185
|
+
schema,
|
|
186
|
+
boundaries: {},
|
|
187
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
188
|
+
});
|
|
189
|
+
const [result, error] = await testTask.safeRun({ value: 5 });
|
|
190
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
191
|
+
// Task should complete successfully despite listener error
|
|
192
|
+
expect(result).toEqual({ result: 10 });
|
|
193
|
+
expect(error).toBeNull();
|
|
194
|
+
// Listener should have been called
|
|
195
|
+
expect(errorListener).toHaveBeenCalledTimes(1);
|
|
196
|
+
// Error should have been logged
|
|
197
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('ExecutionRecord listener error:', expect.any(Error));
|
|
198
|
+
});
|
|
199
|
+
it('should catch and log async listener errors', async () => {
|
|
200
|
+
const asyncErrorListener = jest.fn().mockImplementation(async () => {
|
|
201
|
+
throw new Error('Async listener error');
|
|
202
|
+
});
|
|
203
|
+
index_1.Task.listenExecutionRecords(asyncErrorListener);
|
|
204
|
+
const schema = new index_1.Schema({
|
|
205
|
+
value: index_1.Schema.number()
|
|
206
|
+
});
|
|
207
|
+
const testTask = (0, index_1.createTask)({
|
|
208
|
+
schema,
|
|
209
|
+
boundaries: {},
|
|
210
|
+
fn: async (input) => ({ result: input.value * 2 })
|
|
211
|
+
});
|
|
212
|
+
const [result, error] = await testTask.safeRun({ value: 3 });
|
|
213
|
+
// Wait a bit for the async error to be logged
|
|
214
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
215
|
+
// Task should complete successfully
|
|
216
|
+
expect(result).toEqual({ result: 6 });
|
|
217
|
+
expect(error).toBeNull();
|
|
218
|
+
// Error should have been logged
|
|
219
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('ExecutionRecord listener error:', expect.any(Error));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=listen-execution-records.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-execution-records.test.js","sourceRoot":"","sources":["../../src/test/listen-execution-records.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-collection.test.d.ts","sourceRoot":"","sources":["../../src/test/metrics-collection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../index");
|
|
4
|
+
const schema_1 = require("@forgehive/schema");
|
|
5
|
+
describe('Metrics Collection Tests', () => {
|
|
6
|
+
describe('setMetrics boundary functionality and validation', () => {
|
|
7
|
+
it('should validate metrics with correct structure', () => {
|
|
8
|
+
const validMetric = { type: 'business', name: 'user_count', value: 42 };
|
|
9
|
+
expect((0, index_1.validateMetric)(validMetric)).toBe(true);
|
|
10
|
+
const performanceMetric = { type: 'performance', name: 'response_time', value: 150.5 };
|
|
11
|
+
expect((0, index_1.validateMetric)(performanceMetric)).toBe(true);
|
|
12
|
+
const errorMetric = { type: 'error', name: 'failed_requests', value: 0 };
|
|
13
|
+
expect((0, index_1.validateMetric)(errorMetric)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('should reject invalid metric structures', () => {
|
|
16
|
+
expect((0, index_1.validateMetric)(null)).toBe(false);
|
|
17
|
+
expect((0, index_1.validateMetric)(undefined)).toBe(false);
|
|
18
|
+
expect((0, index_1.validateMetric)({})).toBe(false);
|
|
19
|
+
expect((0, index_1.validateMetric)({ type: 'business' })).toBe(false); // missing name and value
|
|
20
|
+
expect((0, index_1.validateMetric)({ name: 'test', value: 1 })).toBe(false); // missing type
|
|
21
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: 'test' })).toBe(false); // missing value
|
|
22
|
+
});
|
|
23
|
+
it('should reject metrics with invalid types', () => {
|
|
24
|
+
expect((0, index_1.validateMetric)({ type: '', name: 'test', value: 1 })).toBe(false); // empty type
|
|
25
|
+
expect((0, index_1.validateMetric)({ type: 123, name: 'test', value: 1 })).toBe(false); // numeric type
|
|
26
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: '', value: 1 })).toBe(false); // empty name
|
|
27
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: 123, value: 1 })).toBe(false); // numeric name
|
|
28
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: 'test', value: 'not-a-number' })).toBe(false); // string value
|
|
29
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: 'test', value: NaN })).toBe(false); // NaN value
|
|
30
|
+
expect((0, index_1.validateMetric)({ type: 'business', name: 'test', value: Infinity })).toBe(false); // Infinity value
|
|
31
|
+
});
|
|
32
|
+
it('should create valid metrics using createMetric helper', () => {
|
|
33
|
+
const metric = (0, index_1.createMetric)('performance', 'api_response_time', 250);
|
|
34
|
+
expect(metric).toEqual({
|
|
35
|
+
type: 'performance',
|
|
36
|
+
name: 'api_response_time',
|
|
37
|
+
value: 250
|
|
38
|
+
});
|
|
39
|
+
expect((0, index_1.validateMetric)(metric)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('should throw error when creating metric with invalid data', () => {
|
|
42
|
+
expect(() => (0, index_1.createMetric)('', 'test', 1)).toThrow('Invalid metric');
|
|
43
|
+
expect(() => (0, index_1.createMetric)('business', '', 1)).toThrow('Invalid metric');
|
|
44
|
+
expect(() => (0, index_1.createMetric)('business', 'test', NaN)).toThrow('Invalid metric');
|
|
45
|
+
expect(() => (0, index_1.createMetric)('business', 'test', Infinity)).toThrow('Invalid metric');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('Metrics accumulation and storage in execution records', () => {
|
|
49
|
+
it('should collect single metric in execution record', async () => {
|
|
50
|
+
var _a;
|
|
51
|
+
const task = (0, index_1.createTask)({
|
|
52
|
+
name: 'single-metric-test',
|
|
53
|
+
schema: new schema_1.Schema({ input: schema_1.Schema.string() }),
|
|
54
|
+
boundaries: {},
|
|
55
|
+
fn: async ({ input }, { setMetrics }) => {
|
|
56
|
+
await setMetrics({
|
|
57
|
+
type: 'business',
|
|
58
|
+
name: 'items_processed',
|
|
59
|
+
value: 1
|
|
60
|
+
});
|
|
61
|
+
return { result: input.toUpperCase() };
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const [result, error, record] = await task.safeRun({ input: 'test' });
|
|
65
|
+
expect(error).toBeNull();
|
|
66
|
+
expect(result).toEqual({ result: 'TEST' });
|
|
67
|
+
expect(record.metrics).toHaveLength(1);
|
|
68
|
+
expect((_a = record.metrics) === null || _a === void 0 ? void 0 : _a[0]).toEqual({
|
|
69
|
+
type: 'business',
|
|
70
|
+
name: 'items_processed',
|
|
71
|
+
value: 1
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('should accumulate multiple metrics in execution record', async () => {
|
|
75
|
+
const task = (0, index_1.createTask)({
|
|
76
|
+
name: 'multiple-metrics-test',
|
|
77
|
+
schema: new schema_1.Schema({ count: schema_1.Schema.number() }),
|
|
78
|
+
boundaries: {},
|
|
79
|
+
fn: async ({ count }, { setMetrics }) => {
|
|
80
|
+
await setMetrics({ type: 'business', name: 'input_count', value: count });
|
|
81
|
+
await setMetrics({ type: 'performance', name: 'processing_time', value: 150 });
|
|
82
|
+
await setMetrics({ type: 'error', name: 'error_count', value: 0 });
|
|
83
|
+
return { processed: count * 2 };
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const [result, error, record] = await task.safeRun({ count: 5 });
|
|
87
|
+
expect(error).toBeNull();
|
|
88
|
+
expect(result).toEqual({ processed: 10 });
|
|
89
|
+
expect(record.metrics).toHaveLength(3);
|
|
90
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
91
|
+
{ type: 'business', name: 'input_count', value: 5 },
|
|
92
|
+
{ type: 'performance', name: 'processing_time', value: 150 },
|
|
93
|
+
{ type: 'error', name: 'error_count', value: 0 }
|
|
94
|
+
]));
|
|
95
|
+
});
|
|
96
|
+
it('should allow duplicate metric names with different values', async () => {
|
|
97
|
+
const task = (0, index_1.createTask)({
|
|
98
|
+
name: 'duplicate-metrics-test',
|
|
99
|
+
schema: new schema_1.Schema({ iterations: schema_1.Schema.number() }),
|
|
100
|
+
boundaries: {},
|
|
101
|
+
fn: async ({ iterations }, { setMetrics }) => {
|
|
102
|
+
for (let i = 0; i < iterations; i++) {
|
|
103
|
+
await setMetrics({
|
|
104
|
+
type: 'performance',
|
|
105
|
+
name: 'iteration_time',
|
|
106
|
+
value: (i + 1) * 10
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return { completed: iterations };
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const [result, error, record] = await task.safeRun({ iterations: 3 });
|
|
113
|
+
expect(error).toBeNull();
|
|
114
|
+
expect(result).toEqual({ completed: 3 });
|
|
115
|
+
expect(record.metrics).toHaveLength(3);
|
|
116
|
+
expect(record.metrics).toEqual([
|
|
117
|
+
{ type: 'performance', name: 'iteration_time', value: 10 },
|
|
118
|
+
{ type: 'performance', name: 'iteration_time', value: 20 },
|
|
119
|
+
{ type: 'performance', name: 'iteration_time', value: 30 }
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
it('should collect metrics from boundaries and main function', async () => {
|
|
123
|
+
const task = (0, index_1.createTask)({
|
|
124
|
+
name: 'boundary-metrics-test',
|
|
125
|
+
schema: new schema_1.Schema({ input: schema_1.Schema.string() }),
|
|
126
|
+
boundaries: {
|
|
127
|
+
processData: async (data) => {
|
|
128
|
+
return data.length;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
fn: async ({ input }, { processData, setMetrics }) => {
|
|
132
|
+
await setMetrics({ type: 'business', name: 'requests', value: 1 });
|
|
133
|
+
const length = await processData(input);
|
|
134
|
+
await setMetrics({ type: 'business', name: 'input_length', value: length });
|
|
135
|
+
return { length };
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const [result, error, record] = await task.safeRun({ input: 'hello world' });
|
|
139
|
+
expect(error).toBeNull();
|
|
140
|
+
expect(result).toEqual({ length: 11 });
|
|
141
|
+
expect(record.metrics).toHaveLength(2);
|
|
142
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
143
|
+
{ type: 'business', name: 'requests', value: 1 },
|
|
144
|
+
{ type: 'business', name: 'input_length', value: 11 }
|
|
145
|
+
]));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe('Metrics behavior in error scenarios', () => {
|
|
149
|
+
it('should preserve metrics collected before error occurs', async () => {
|
|
150
|
+
const task = (0, index_1.createTask)({
|
|
151
|
+
name: 'error-metrics-test',
|
|
152
|
+
schema: new schema_1.Schema({ shouldFail: schema_1.Schema.boolean() }),
|
|
153
|
+
boundaries: {},
|
|
154
|
+
fn: async ({ shouldFail }, { setMetrics }) => {
|
|
155
|
+
await setMetrics({ type: 'business', name: 'attempt_count', value: 1 });
|
|
156
|
+
await setMetrics({ type: 'performance', name: 'preparation_time', value: 50 });
|
|
157
|
+
if (shouldFail) {
|
|
158
|
+
await setMetrics({ type: 'error', name: 'failure_count', value: 1 });
|
|
159
|
+
throw new Error('Intentional failure');
|
|
160
|
+
}
|
|
161
|
+
return { success: true };
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
const [result, error, record] = await task.safeRun({ shouldFail: true });
|
|
165
|
+
expect(result).toBeNull();
|
|
166
|
+
expect(error).not.toBeNull();
|
|
167
|
+
expect(record.type).toBe('error');
|
|
168
|
+
expect(record.metrics).toHaveLength(3);
|
|
169
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
170
|
+
{ type: 'business', name: 'attempt_count', value: 1 },
|
|
171
|
+
{ type: 'performance', name: 'preparation_time', value: 50 },
|
|
172
|
+
{ type: 'error', name: 'failure_count', value: 1 }
|
|
173
|
+
]));
|
|
174
|
+
});
|
|
175
|
+
it('should handle boundary errors while preserving metrics', async () => {
|
|
176
|
+
const task = (0, index_1.createTask)({
|
|
177
|
+
name: 'boundary-error-metrics-test',
|
|
178
|
+
schema: new schema_1.Schema({ input: schema_1.Schema.string() }),
|
|
179
|
+
boundaries: {
|
|
180
|
+
failingBoundary: async (data) => {
|
|
181
|
+
throw new Error(`Boundary failed for: ${data}`);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
fn: async ({ input }, { failingBoundary, setMetrics }) => {
|
|
185
|
+
await setMetrics({ type: 'business', name: 'attempts', value: 1 });
|
|
186
|
+
try {
|
|
187
|
+
await failingBoundary(input);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
await setMetrics({ type: 'error', name: 'boundary_failures', value: 1 });
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
return { success: true };
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const [result, error, record] = await task.safeRun({ input: 'test' });
|
|
197
|
+
expect(result).toBeNull();
|
|
198
|
+
expect(error).not.toBeNull();
|
|
199
|
+
expect(record.type).toBe('error');
|
|
200
|
+
expect(record.metrics).toHaveLength(2);
|
|
201
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
202
|
+
{ type: 'business', name: 'attempts', value: 1 },
|
|
203
|
+
{ type: 'error', name: 'boundary_failures', value: 1 }
|
|
204
|
+
]));
|
|
205
|
+
});
|
|
206
|
+
it('should reject invalid metrics and continue execution', async () => {
|
|
207
|
+
const task = (0, index_1.createTask)({
|
|
208
|
+
name: 'invalid-metrics-test',
|
|
209
|
+
schema: new schema_1.Schema({ input: schema_1.Schema.string() }),
|
|
210
|
+
boundaries: {},
|
|
211
|
+
fn: async ({ input }, { setMetrics }) => {
|
|
212
|
+
// Valid metric should be stored
|
|
213
|
+
await setMetrics({ type: 'business', name: 'valid_metric', value: 1 });
|
|
214
|
+
// Invalid metrics should be rejected but not crash the task
|
|
215
|
+
try {
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
await setMetrics({ type: '', name: 'invalid', value: 1 });
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
// Expected to fail validation
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
224
|
+
await setMetrics({ type: 'business', name: 'invalid', value: NaN });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Expected to fail validation
|
|
228
|
+
}
|
|
229
|
+
// Another valid metric should still work
|
|
230
|
+
await setMetrics({ type: 'performance', name: 'final_metric', value: 100 });
|
|
231
|
+
return { result: input };
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const [result, error, record] = await task.safeRun({ input: 'test' });
|
|
235
|
+
expect(error).toBeNull();
|
|
236
|
+
expect(result).toEqual({ result: 'test' });
|
|
237
|
+
expect(record.metrics).toHaveLength(2); // Only valid metrics should be stored
|
|
238
|
+
expect(record.metrics).toEqual(expect.arrayContaining([
|
|
239
|
+
{ type: 'business', name: 'valid_metric', value: 1 },
|
|
240
|
+
{ type: 'performance', name: 'final_metric', value: 100 }
|
|
241
|
+
]));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('Metrics behavior during replay', () => {
|
|
245
|
+
it('should preserve original metrics during replay', async () => {
|
|
246
|
+
var _a;
|
|
247
|
+
const task = (0, index_1.createTask)({
|
|
248
|
+
name: 'replay-metrics-test',
|
|
249
|
+
schema: new schema_1.Schema({ input: schema_1.Schema.string() }),
|
|
250
|
+
boundaries: {
|
|
251
|
+
dataFetch: async (query) => {
|
|
252
|
+
return `data-for-${query}`;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
fn: async ({ input }, { dataFetch, setMetrics }) => {
|
|
256
|
+
await setMetrics({ type: 'business', name: 'queries', value: 1 });
|
|
257
|
+
const data = await dataFetch(input);
|
|
258
|
+
await setMetrics({ type: 'performance', name: 'data_size', value: data.length });
|
|
259
|
+
return { data };
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
// First run to get original execution
|
|
263
|
+
const [originalResult, originalError, originalRecord] = await task.safeRun({ input: 'test' });
|
|
264
|
+
expect(originalError).toBeNull();
|
|
265
|
+
expect(originalResult).toEqual({ data: 'data-for-test' });
|
|
266
|
+
expect(originalRecord.metrics).toHaveLength(2);
|
|
267
|
+
// Replay the execution
|
|
268
|
+
const [replayResult, replayError, replayRecord] = await task.safeReplay(originalRecord, { boundaries: {} });
|
|
269
|
+
expect(replayError).toBeNull();
|
|
270
|
+
expect(replayResult).toEqual({ data: 'data-for-test' });
|
|
271
|
+
// Replay should preserve original metrics and may add new ones
|
|
272
|
+
expect((_a = replayRecord.metrics) === null || _a === void 0 ? void 0 : _a.length).toBeGreaterThanOrEqual(2);
|
|
273
|
+
// Original metrics should be included
|
|
274
|
+
expect(replayRecord.metrics).toEqual(expect.arrayContaining([
|
|
275
|
+
{ type: 'business', name: 'queries', value: 1 },
|
|
276
|
+
{ type: 'performance', name: 'data_size', value: 13 }
|
|
277
|
+
]));
|
|
278
|
+
});
|
|
279
|
+
it('should allow new metrics during replay execution', async () => {
|
|
280
|
+
var _a, _b;
|
|
281
|
+
const task = (0, index_1.createTask)({
|
|
282
|
+
name: 'replay-new-metrics-test',
|
|
283
|
+
schema: new schema_1.Schema({ mode: schema_1.Schema.string() }),
|
|
284
|
+
boundaries: {
|
|
285
|
+
operation: async (mode) => {
|
|
286
|
+
return `result-${mode}`;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
fn: async ({ mode }, { operation, setMetrics }) => {
|
|
290
|
+
if (mode === 'original') {
|
|
291
|
+
await setMetrics({ type: 'business', name: 'original_run', value: 1 });
|
|
292
|
+
}
|
|
293
|
+
else if (mode === 'replay') {
|
|
294
|
+
await setMetrics({ type: 'business', name: 'replay_run', value: 1 });
|
|
295
|
+
}
|
|
296
|
+
const result = await operation(mode);
|
|
297
|
+
await setMetrics({ type: 'performance', name: 'execution_count', value: 1 });
|
|
298
|
+
return { result };
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
// Original run
|
|
302
|
+
const [, originalError, originalRecord] = await task.safeRun({ mode: 'original' });
|
|
303
|
+
expect(originalError).toBeNull();
|
|
304
|
+
expect(originalRecord.metrics).toHaveLength(2);
|
|
305
|
+
// Modify the record for replay to change the mode
|
|
306
|
+
const modifiedRecord = Object.assign(Object.assign({}, originalRecord), { input: { mode: 'replay' } });
|
|
307
|
+
// Replay with different mode
|
|
308
|
+
const [, replayError, replayRecord] = await task.safeReplay(modifiedRecord, { boundaries: {} });
|
|
309
|
+
expect(replayError).toBeNull();
|
|
310
|
+
expect((_a = replayRecord.metrics) === null || _a === void 0 ? void 0 : _a.length).toBeGreaterThanOrEqual(2);
|
|
311
|
+
// Should have both original metrics and new replay metrics
|
|
312
|
+
const metricNames = ((_b = replayRecord.metrics) === null || _b === void 0 ? void 0 : _b.map(m => m.name)) || [];
|
|
313
|
+
expect(metricNames).toContain('execution_count');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe('Performance metrics and complex scenarios', () => {
|
|
317
|
+
it('should handle high-frequency metric collection', async () => {
|
|
318
|
+
var _a;
|
|
319
|
+
const task = (0, index_1.createTask)({
|
|
320
|
+
name: 'high-frequency-metrics-test',
|
|
321
|
+
schema: new schema_1.Schema({ count: schema_1.Schema.number() }),
|
|
322
|
+
boundaries: {},
|
|
323
|
+
fn: async ({ count }, { setMetrics }) => {
|
|
324
|
+
for (let i = 0; i < count; i++) {
|
|
325
|
+
await setMetrics({
|
|
326
|
+
type: 'performance',
|
|
327
|
+
name: 'iteration',
|
|
328
|
+
value: i
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return { completed: count };
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
const [result, error, record] = await task.safeRun({ count: 100 });
|
|
335
|
+
expect(error).toBeNull();
|
|
336
|
+
expect(result).toEqual({ completed: 100 });
|
|
337
|
+
expect(record.metrics).toHaveLength(100);
|
|
338
|
+
// Verify all metrics were collected correctly
|
|
339
|
+
(_a = record.metrics) === null || _a === void 0 ? void 0 : _a.forEach((metric, index) => {
|
|
340
|
+
expect(metric).toEqual({
|
|
341
|
+
type: 'performance',
|
|
342
|
+
name: 'iteration',
|
|
343
|
+
value: index
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
it('should support different metric types in complex workflow', async () => {
|
|
348
|
+
var _a, _b, _c, _d;
|
|
349
|
+
const task = (0, index_1.createTask)({
|
|
350
|
+
name: 'complex-workflow-metrics-test',
|
|
351
|
+
schema: new schema_1.Schema({
|
|
352
|
+
userId: schema_1.Schema.string(),
|
|
353
|
+
operations: schema_1.Schema.array(schema_1.Schema.string())
|
|
354
|
+
}),
|
|
355
|
+
boundaries: {
|
|
356
|
+
validateUser: async (userId) => {
|
|
357
|
+
return userId.length > 0;
|
|
358
|
+
},
|
|
359
|
+
processOperation: async (operation) => {
|
|
360
|
+
return `processed-${operation}`;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
fn: async ({ userId, operations }, { validateUser, processOperation, setMetrics }) => {
|
|
364
|
+
// Business metrics
|
|
365
|
+
await setMetrics({ type: 'business', name: 'user_requests', value: 1 });
|
|
366
|
+
await setMetrics({ type: 'business', name: 'operation_count', value: operations.length });
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
// Validate user
|
|
369
|
+
const isValid = await validateUser(userId);
|
|
370
|
+
if (!isValid) {
|
|
371
|
+
await setMetrics({ type: 'error', name: 'validation_failures', value: 1 });
|
|
372
|
+
throw new Error('Invalid user');
|
|
373
|
+
}
|
|
374
|
+
// Process operations
|
|
375
|
+
const results = [];
|
|
376
|
+
for (const operation of operations) {
|
|
377
|
+
const result = await processOperation(operation);
|
|
378
|
+
results.push(result);
|
|
379
|
+
await setMetrics({ type: 'business', name: 'operations_processed', value: 1 });
|
|
380
|
+
}
|
|
381
|
+
// Performance metrics
|
|
382
|
+
const duration = Date.now() - startTime;
|
|
383
|
+
await setMetrics({ type: 'performance', name: 'total_processing_time', value: duration });
|
|
384
|
+
await setMetrics({ type: 'performance', name: 'avg_operation_time', value: duration / operations.length });
|
|
385
|
+
// Success metrics
|
|
386
|
+
await setMetrics({ type: 'business', name: 'successful_requests', value: 1 });
|
|
387
|
+
await setMetrics({ type: 'error', name: 'error_count', value: 0 });
|
|
388
|
+
return { userId, results, processingTime: duration };
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
const [result, error, record] = await task.safeRun({
|
|
392
|
+
userId: 'user123',
|
|
393
|
+
operations: ['op1', 'op2', 'op3']
|
|
394
|
+
});
|
|
395
|
+
expect(error).toBeNull();
|
|
396
|
+
expect(result === null || result === void 0 ? void 0 : result.userId).toBe('user123');
|
|
397
|
+
expect(result === null || result === void 0 ? void 0 : result.results).toEqual(['processed-op1', 'processed-op2', 'processed-op3']);
|
|
398
|
+
// Should have collected multiple types of metrics
|
|
399
|
+
expect((_a = record.metrics) === null || _a === void 0 ? void 0 : _a.length).toBeGreaterThanOrEqual(8);
|
|
400
|
+
const businessMetrics = ((_b = record.metrics) === null || _b === void 0 ? void 0 : _b.filter(m => m.type === 'business')) || [];
|
|
401
|
+
const performanceMetrics = ((_c = record.metrics) === null || _c === void 0 ? void 0 : _c.filter(m => m.type === 'performance')) || [];
|
|
402
|
+
const errorMetrics = ((_d = record.metrics) === null || _d === void 0 ? void 0 : _d.filter(m => m.type === 'error')) || [];
|
|
403
|
+
expect(businessMetrics.length).toBeGreaterThanOrEqual(5);
|
|
404
|
+
expect(performanceMetrics.length).toBeGreaterThanOrEqual(2);
|
|
405
|
+
expect(errorMetrics.length).toBeGreaterThanOrEqual(1);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
//# sourceMappingURL=metrics-collection.test.js.map
|