@onlineapps/error-handler-core 1.1.14 → 1.1.15

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 CHANGED
@@ -105,7 +105,7 @@ const result = await errorHandler.executeWithCircuitBreaker({
105
105
 
106
106
  ## API Reference
107
107
 
108
- See [UNIFIED_ERROR_HANDLING.md](/docs/standards/UNIFIED_ERROR_HANDLING.md) for complete API documentation.
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.14",
3
+ "version": "1.1.15",
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,7 +20,7 @@
20
20
  "author": "OnlineApps",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@onlineapps/monitoring-core": "1.0.25",
23
+ "@onlineapps/monitoring-core": "1.0.26",
24
24
  "@onlineapps/runtime-config": "1.0.2",
25
25
  "opossum": "^7.0.0"
26
26
  },
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
-