@onlineapps/error-handler-core 1.1.14 → 1.1.16
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/README.md +1 -3
- package/package.json +2 -3
- package/jest.config.js +0 -23
- package/tests/unit/DLQRouter.test.js +0 -94
- package/tests/unit/ErrorClassifier.test.js +0 -82
- package/tests/unit/ErrorHandlerCore.test.js +0 -183
- package/tests/unit/RetryHandler.test.js +0 -81
- package/tests/unit/UnifiedLogSchema.test.js +0 -75
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ const result = await errorHandler.executeWithCircuitBreaker({
|
|
|
105
105
|
|
|
106
106
|
## API Reference
|
|
107
107
|
|
|
108
|
-
See [
|
|
108
|
+
See [ERROR_HANDLING.md](/docs/standards/ERROR_HANDLING.md) for the cross-cutting standard (classification table, fail-loudly rules).
|
|
109
109
|
|
|
110
110
|
## Architecture
|
|
111
111
|
|
|
@@ -115,8 +115,6 @@ See [UNIFIED_ERROR_HANDLING.md](/docs/standards/UNIFIED_ERROR_HANDLING.md) for c
|
|
|
115
115
|
|
|
116
116
|
## Related Documentation
|
|
117
117
|
|
|
118
|
-
- [Unified Error Handling Standard](/docs/standards/UNIFIED_ERROR_HANDLING.md)
|
|
119
|
-
- [Error Handling Architecture](/docs/standards/ERROR_HANDLING_ARCHITECTURE.md)
|
|
120
118
|
- [Error Handling Standard](/docs/standards/ERROR_HANDLING.md)
|
|
121
119
|
|
|
122
120
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/error-handler-core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Core error handling functionality for OA Drive microservices - classification, retry, circuit breaker, DLQ routing",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
"author": "OnlineApps",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onlineapps/
|
|
24
|
-
"@onlineapps/runtime-config": "1.0.2",
|
|
23
|
+
"@onlineapps/runtime-config": "1.0.3",
|
|
25
24
|
"opossum": "^7.0.0"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
package/jest.config.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
testEnvironment: 'node',
|
|
3
|
-
coveragePathIgnorePatterns: [
|
|
4
|
-
'/node_modules/',
|
|
5
|
-
'/tests/'
|
|
6
|
-
],
|
|
7
|
-
testMatch: [
|
|
8
|
-
'**/tests/**/*.test.js'
|
|
9
|
-
],
|
|
10
|
-
collectCoverageFrom: [
|
|
11
|
-
'src/**/*.js',
|
|
12
|
-
'!src/**/*.test.js'
|
|
13
|
-
],
|
|
14
|
-
coverageThreshold: {
|
|
15
|
-
global: {
|
|
16
|
-
branches: 70,
|
|
17
|
-
functions: 70,
|
|
18
|
-
lines: 70,
|
|
19
|
-
statements: 70
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DLQRouter Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const DLQRouter = require('../../src/DLQRouter');
|
|
6
|
-
|
|
7
|
-
describe('DLQRouter @unit', () => {
|
|
8
|
-
let dlqRouter;
|
|
9
|
-
let mockMQClient;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
mockMQClient = {
|
|
13
|
-
publish: jest.fn().mockResolvedValue()
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
dlqRouter = new DLQRouter({
|
|
17
|
-
dlqEnabled: true,
|
|
18
|
-
mqClient: mockMQClient,
|
|
19
|
-
dlqNamePattern: '{queue}.dlq'
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('routeToDLQ', () => {
|
|
24
|
-
test('should route message to DLQ', async () => {
|
|
25
|
-
const message = { id: 'msg-123', data: 'test' };
|
|
26
|
-
const error = new Error('Test error');
|
|
27
|
-
const context = { queue: 'test.queue' };
|
|
28
|
-
|
|
29
|
-
await dlqRouter.routeToDLQ(message, error, 'BUSINESS', context);
|
|
30
|
-
|
|
31
|
-
expect(mockMQClient.publish).toHaveBeenCalledWith(
|
|
32
|
-
'test.queue.dlq',
|
|
33
|
-
expect.objectContaining({
|
|
34
|
-
id: 'msg-123',
|
|
35
|
-
error: expect.objectContaining({
|
|
36
|
-
message: 'Test error',
|
|
37
|
-
type: 'BUSINESS'
|
|
38
|
-
}),
|
|
39
|
-
originalQueue: 'test.queue'
|
|
40
|
-
}),
|
|
41
|
-
{ persistent: true }
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('should not route if DLQ disabled', async () => {
|
|
46
|
-
const disabledRouter = new DLQRouter({
|
|
47
|
-
dlqEnabled: false,
|
|
48
|
-
mqClient: mockMQClient
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await disabledRouter.routeToDLQ({}, new Error('Test'), 'BUSINESS', {});
|
|
52
|
-
|
|
53
|
-
expect(mockMQClient.publish).not.toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('should not route if no MQ client', async () => {
|
|
57
|
-
const noClientRouter = new DLQRouter({
|
|
58
|
-
dlqEnabled: true,
|
|
59
|
-
mqClient: null
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await noClientRouter.routeToDLQ({}, new Error('Test'), 'BUSINESS', {});
|
|
63
|
-
|
|
64
|
-
expect(mockMQClient.publish).not.toHaveBeenCalled();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('should handle DLQ routing failure gracefully', async () => {
|
|
68
|
-
mockMQClient.publish.mockRejectedValue(new Error('DLQ failed'));
|
|
69
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
70
|
-
|
|
71
|
-
await dlqRouter.routeToDLQ({}, new Error('Test'), 'BUSINESS', { queue: 'test' });
|
|
72
|
-
|
|
73
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
74
|
-
consoleSpy.mockRestore();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('isAvailable', () => {
|
|
79
|
-
test('should return true if enabled and client available', () => {
|
|
80
|
-
expect(dlqRouter.isAvailable()).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('should return false if disabled', () => {
|
|
84
|
-
const disabledRouter = new DLQRouter({ dlqEnabled: false });
|
|
85
|
-
expect(disabledRouter.isAvailable()).toBe(false);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('should return false if no client', () => {
|
|
89
|
-
const noClientRouter = new DLQRouter({ mqClient: null });
|
|
90
|
-
expect(noClientRouter.isAvailable()).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorClassifier Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { ErrorClassifier, ErrorTypes } = require('../../src/ErrorClassifier');
|
|
6
|
-
|
|
7
|
-
describe('ErrorClassifier @unit', () => {
|
|
8
|
-
let classifier;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
classifier = new ErrorClassifier();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('classify', () => {
|
|
15
|
-
test('should classify ECONNREFUSED as TRANSIENT', () => {
|
|
16
|
-
const error = new Error('Connection refused');
|
|
17
|
-
error.code = 'ECONNREFUSED';
|
|
18
|
-
|
|
19
|
-
expect(classifier.classify(error)).toBe(ErrorTypes.TRANSIENT);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('should classify HTTP 404 as BUSINESS', () => {
|
|
23
|
-
const error = new Error('Not Found');
|
|
24
|
-
error.statusCode = 404;
|
|
25
|
-
|
|
26
|
-
expect(classifier.classify(error)).toBe(ErrorTypes.BUSINESS);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('should classify timeout message as TIMEOUT', () => {
|
|
30
|
-
const error = new Error('Operation timeout');
|
|
31
|
-
|
|
32
|
-
expect(classifier.classify(error)).toBe(ErrorTypes.TIMEOUT);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('should classify explicit type', () => {
|
|
36
|
-
const error = new Error('Custom error');
|
|
37
|
-
error.type = 'BUSINESS';
|
|
38
|
-
|
|
39
|
-
expect(classifier.classify(error)).toBe(ErrorTypes.BUSINESS);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should return UNKNOWN for unclassified error', () => {
|
|
43
|
-
const error = new Error('Some random error');
|
|
44
|
-
|
|
45
|
-
expect(classifier.classify(error)).toBe(ErrorTypes.UNKNOWN);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('should handle null error', () => {
|
|
49
|
-
expect(classifier.classify(null)).toBe(ErrorTypes.UNKNOWN);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('shouldRetry', () => {
|
|
54
|
-
test('should retry TRANSIENT errors', () => {
|
|
55
|
-
const error = new Error('Connection refused');
|
|
56
|
-
error.code = 'ECONNREFUSED';
|
|
57
|
-
|
|
58
|
-
expect(classifier.shouldRetry(error, 0, 3)).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('should not retry after max attempts', () => {
|
|
62
|
-
const error = new Error('Connection refused');
|
|
63
|
-
error.code = 'ECONNREFUSED';
|
|
64
|
-
|
|
65
|
-
expect(classifier.shouldRetry(error, 3, 3)).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('should not retry BUSINESS errors', () => {
|
|
69
|
-
const error = new Error('Not Found');
|
|
70
|
-
error.statusCode = 404;
|
|
71
|
-
|
|
72
|
-
expect(classifier.shouldRetry(error, 0, 3)).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should retry TIMEOUT errors', () => {
|
|
76
|
-
const error = new Error('Operation timeout');
|
|
77
|
-
|
|
78
|
-
expect(classifier.shouldRetry(error, 0, 3)).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorHandlerCore Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const ErrorHandlerCore = require('../../src/ErrorHandlerCore');
|
|
6
|
-
const { ErrorTypes } = require('../../src/ErrorClassifier');
|
|
7
|
-
|
|
8
|
-
describe('ErrorHandlerCore @unit', () => {
|
|
9
|
-
let errorHandler;
|
|
10
|
-
let mockMonitoring;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
mockMonitoring = {
|
|
14
|
-
logger: {
|
|
15
|
-
error: jest.fn(),
|
|
16
|
-
warn: jest.fn(),
|
|
17
|
-
info: jest.fn()
|
|
18
|
-
},
|
|
19
|
-
// Fallback for conn-base-monitoring wrapper
|
|
20
|
-
error: jest.fn(),
|
|
21
|
-
warn: jest.fn(),
|
|
22
|
-
info: jest.fn()
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
errorHandler = new ErrorHandlerCore({
|
|
26
|
-
serviceName: 'test-service',
|
|
27
|
-
serviceVersion: '1.0.0',
|
|
28
|
-
environment: 'test',
|
|
29
|
-
monitoring: mockMonitoring,
|
|
30
|
-
handling: {
|
|
31
|
-
maxRetries: 3,
|
|
32
|
-
retryDelay: 100
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('constructor', () => {
|
|
38
|
-
test('should throw if serviceName missing', () => {
|
|
39
|
-
expect(() => {
|
|
40
|
-
new ErrorHandlerCore({
|
|
41
|
-
monitoring: mockMonitoring
|
|
42
|
-
});
|
|
43
|
-
}).toThrow('serviceName is required');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('should throw if monitoring missing', () => {
|
|
47
|
-
expect(() => {
|
|
48
|
-
new ErrorHandlerCore({
|
|
49
|
-
serviceName: 'test'
|
|
50
|
-
});
|
|
51
|
-
}).toThrow('monitoring instance is required');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('should initialize with valid config', () => {
|
|
55
|
-
expect(errorHandler.serviceName).toBe('test-service');
|
|
56
|
-
expect(errorHandler.classifier).toBeDefined();
|
|
57
|
-
expect(errorHandler.retryHandler).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('logError', () => {
|
|
62
|
-
test('should log error via monitoring', async () => {
|
|
63
|
-
const error = new Error('Test error');
|
|
64
|
-
|
|
65
|
-
const result = await errorHandler.logError({
|
|
66
|
-
moduleName: 'TestModule',
|
|
67
|
-
operation: 'testOperation',
|
|
68
|
-
error: error
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(mockMonitoring.logger.error).toHaveBeenCalled();
|
|
72
|
-
expect(result).toHaveProperty('timestamp');
|
|
73
|
-
expect(result).toHaveProperty('error');
|
|
74
|
-
expect(result.error.type).toBeDefined();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('should handle monitoring failure gracefully', async () => {
|
|
78
|
-
// Mock logger.error to throw
|
|
79
|
-
mockMonitoring.logger.error.mockImplementation(() => {
|
|
80
|
-
throw new Error('Monitoring failed');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
84
|
-
|
|
85
|
-
const error = new Error('Test error');
|
|
86
|
-
await errorHandler.logError({
|
|
87
|
-
moduleName: 'TestModule',
|
|
88
|
-
operation: 'testOperation',
|
|
89
|
-
error: error
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
93
|
-
consoleSpy.mockRestore();
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('handleError', () => {
|
|
98
|
-
test('should classify and log error', async () => {
|
|
99
|
-
const error = new Error('Connection refused');
|
|
100
|
-
error.code = 'ECONNREFUSED';
|
|
101
|
-
|
|
102
|
-
const result = await errorHandler.handleError({
|
|
103
|
-
moduleName: 'TestModule',
|
|
104
|
-
operation: 'testOperation',
|
|
105
|
-
error: error
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
expect(result.errorType).toBe(ErrorTypes.TRANSIENT);
|
|
109
|
-
expect(result.action).toBe('retry');
|
|
110
|
-
expect(result.shouldRetry).toBe(true);
|
|
111
|
-
expect(mockMonitoring.logger.error).toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('should route to DLQ for BUSINESS errors', async () => {
|
|
115
|
-
const error = new Error('Not Found');
|
|
116
|
-
error.statusCode = 404;
|
|
117
|
-
|
|
118
|
-
// Mock MQ client for DLQ
|
|
119
|
-
const mockMQClient = {
|
|
120
|
-
publish: jest.fn().mockResolvedValue()
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
errorHandler.dlqRouter.mqClient = mockMQClient;
|
|
124
|
-
|
|
125
|
-
const result = await errorHandler.handleError({
|
|
126
|
-
moduleName: 'TestModule',
|
|
127
|
-
operation: 'testOperation',
|
|
128
|
-
error: error,
|
|
129
|
-
context: {
|
|
130
|
-
message: { id: 'msg-123' },
|
|
131
|
-
queue: 'test.queue'
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
expect(result.errorType).toBe(ErrorTypes.BUSINESS);
|
|
136
|
-
expect(result.action).toBe('dlq');
|
|
137
|
-
expect(mockMQClient.publish).toHaveBeenCalled();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('should throw for FATAL errors', async () => {
|
|
141
|
-
const error = new Error('Fatal error');
|
|
142
|
-
error.type = 'FATAL';
|
|
143
|
-
|
|
144
|
-
const result = await errorHandler.handleError({
|
|
145
|
-
moduleName: 'TestModule',
|
|
146
|
-
operation: 'testOperation',
|
|
147
|
-
error: error
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
expect(result.errorType).toBe(ErrorTypes.FATAL);
|
|
151
|
-
expect(result.action).toBe('throw');
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('executeWithRetry', () => {
|
|
156
|
-
test('should retry on failure', async () => {
|
|
157
|
-
const fn = jest.fn()
|
|
158
|
-
.mockRejectedValueOnce(new Error('Fail 1'))
|
|
159
|
-
.mockResolvedValue('success');
|
|
160
|
-
|
|
161
|
-
const result = await errorHandler.executeWithRetry({
|
|
162
|
-
moduleName: 'TestModule',
|
|
163
|
-
operation: 'testOperation',
|
|
164
|
-
fn: fn
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
expect(result).toBe('success');
|
|
168
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
169
|
-
expect(mockMonitoring.logger.error).toHaveBeenCalled(); // Logs retry attempt
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('registerCompensation', () => {
|
|
174
|
-
test('should register compensation handler', () => {
|
|
175
|
-
const handler = jest.fn().mockResolvedValue();
|
|
176
|
-
|
|
177
|
-
errorHandler.registerCompensation('test-operation', handler);
|
|
178
|
-
|
|
179
|
-
expect(errorHandler.compensation.hasHandler('test-operation')).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RetryHandler Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const RetryHandler = require('../../src/RetryHandler');
|
|
6
|
-
|
|
7
|
-
describe('RetryHandler @unit', () => {
|
|
8
|
-
let retryHandler;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
retryHandler = new RetryHandler({
|
|
12
|
-
maxRetries: 3,
|
|
13
|
-
retryDelay: 1000,
|
|
14
|
-
retryMultiplier: 2
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('calculateBackoff', () => {
|
|
19
|
-
test('should calculate exponential backoff', () => {
|
|
20
|
-
expect(retryHandler.calculateBackoff(1)).toBe(1000);
|
|
21
|
-
expect(retryHandler.calculateBackoff(2)).toBe(2000);
|
|
22
|
-
expect(retryHandler.calculateBackoff(3)).toBe(4000);
|
|
23
|
-
expect(retryHandler.calculateBackoff(4)).toBe(8000);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('should cap at maxRetryDelay', () => {
|
|
27
|
-
const handler = new RetryHandler({
|
|
28
|
-
maxRetryDelay: 5000
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
expect(handler.calculateBackoff(10)).toBe(5000);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('executeWithRetry', () => {
|
|
36
|
-
test('should succeed on first attempt', async () => {
|
|
37
|
-
const fn = jest.fn().mockResolvedValue('success');
|
|
38
|
-
|
|
39
|
-
const result = await retryHandler.executeWithRetry(fn);
|
|
40
|
-
|
|
41
|
-
expect(result).toBe('success');
|
|
42
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('should retry on failure', async () => {
|
|
46
|
-
const fn = jest.fn()
|
|
47
|
-
.mockRejectedValueOnce(new Error('Fail 1'))
|
|
48
|
-
.mockRejectedValueOnce(new Error('Fail 2'))
|
|
49
|
-
.mockResolvedValue('success');
|
|
50
|
-
|
|
51
|
-
const result = await retryHandler.executeWithRetry(fn);
|
|
52
|
-
|
|
53
|
-
expect(result).toBe('success');
|
|
54
|
-
expect(fn).toHaveBeenCalledTimes(3);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('should throw after max retries', async () => {
|
|
58
|
-
const error = new Error('Always fails');
|
|
59
|
-
const fn = jest.fn().mockRejectedValue(error);
|
|
60
|
-
|
|
61
|
-
await expect(retryHandler.executeWithRetry(fn)).rejects.toThrow('Always fails');
|
|
62
|
-
expect(fn).toHaveBeenCalledTimes(3);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('should call onRetry callback', async () => {
|
|
66
|
-
const onRetry = jest.fn();
|
|
67
|
-
const fn = jest.fn()
|
|
68
|
-
.mockRejectedValueOnce(new Error('Fail'))
|
|
69
|
-
.mockResolvedValue('success');
|
|
70
|
-
|
|
71
|
-
await retryHandler.executeWithRetry(fn, { onRetry });
|
|
72
|
-
|
|
73
|
-
expect(onRetry).toHaveBeenCalledWith(
|
|
74
|
-
expect.any(Error),
|
|
75
|
-
1,
|
|
76
|
-
1000
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UnifiedLogSchema Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const UnifiedLogSchema = require('../../src/UnifiedLogSchema');
|
|
6
|
-
|
|
7
|
-
describe('UnifiedLogSchema @unit', () => {
|
|
8
|
-
let schema;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
schema = new UnifiedLogSchema();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('create', () => {
|
|
15
|
-
test('should create unified log entry', () => {
|
|
16
|
-
const error = new Error('Test error');
|
|
17
|
-
error.code = 'ETIMEDOUT';
|
|
18
|
-
|
|
19
|
-
const logEntry = schema.create({
|
|
20
|
-
moduleName: 'TestModule',
|
|
21
|
-
operation: 'testOperation',
|
|
22
|
-
error: error,
|
|
23
|
-
context: { userId: 123 },
|
|
24
|
-
serviceName: 'test-service',
|
|
25
|
-
serviceVersion: '1.0.0',
|
|
26
|
-
environment: 'test',
|
|
27
|
-
errorType: 'TRANSIENT'
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(logEntry).toHaveProperty('timestamp');
|
|
31
|
-
expect(logEntry).toHaveProperty('serviceName', 'test-service');
|
|
32
|
-
expect(logEntry).toHaveProperty('moduleName', 'TestModule');
|
|
33
|
-
expect(logEntry).toHaveProperty('operation', 'testOperation');
|
|
34
|
-
expect(logEntry).toHaveProperty('error');
|
|
35
|
-
expect(logEntry.error).toHaveProperty('message', 'Test error');
|
|
36
|
-
expect(logEntry.error).toHaveProperty('code', 'ETIMEDOUT');
|
|
37
|
-
expect(logEntry.error).toHaveProperty('type', 'TRANSIENT');
|
|
38
|
-
expect(logEntry).toHaveProperty('context');
|
|
39
|
-
expect(logEntry.context).toHaveProperty('userId', 123);
|
|
40
|
-
expect(logEntry).toHaveProperty('metadata');
|
|
41
|
-
expect(logEntry.metadata).toHaveProperty('severity');
|
|
42
|
-
expect(logEntry.metadata).toHaveProperty('environment', 'test');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('should determine severity from error type', () => {
|
|
46
|
-
const error = new Error('Fatal error');
|
|
47
|
-
|
|
48
|
-
const logEntry = schema.create({
|
|
49
|
-
moduleName: 'TestModule',
|
|
50
|
-
operation: 'testOperation',
|
|
51
|
-
error: error,
|
|
52
|
-
serviceName: 'test-service',
|
|
53
|
-
errorType: 'FATAL'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
expect(logEntry.metadata.severity).toBe('fatal');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('should handle missing optional fields', () => {
|
|
60
|
-
const error = new Error('Test error');
|
|
61
|
-
|
|
62
|
-
const logEntry = schema.create({
|
|
63
|
-
moduleName: 'TestModule',
|
|
64
|
-
operation: 'testOperation',
|
|
65
|
-
error: error,
|
|
66
|
-
serviceName: 'test-service'
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
expect(logEntry).toHaveProperty('timestamp');
|
|
70
|
-
expect(logEntry.serviceName).toBe('test-service');
|
|
71
|
-
expect(logEntry.error.type).toBe('UNKNOWN');
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|