@onlineapps/conn-base-monitoring 1.0.0
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/coverage/clover.xml +64 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +116 -0
- package/coverage/lcov-report/index.js.html +625 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +147 -0
- package/onlineapps-conn-base-monitoring-1.0.0.tgz +0 -0
- package/package.json +34 -0
- package/src/index.js +181 -0
- package/test/component/integration.test.js +460 -0
- package/test/unit/monitoring.test.js +376 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for conn-base-monitoring integration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock monitoring-core for component tests
|
|
6
|
+
jest.mock('@onlineapps/monitoring-core', () => {
|
|
7
|
+
// Create a fresh Map for each test to avoid state pollution
|
|
8
|
+
let mockWorkflows;
|
|
9
|
+
|
|
10
|
+
const createMockCore = async (config) => {
|
|
11
|
+
mockWorkflows = new Map();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
config,
|
|
15
|
+
logger: {
|
|
16
|
+
info: jest.fn(),
|
|
17
|
+
error: jest.fn(),
|
|
18
|
+
warn: jest.fn(),
|
|
19
|
+
debug: jest.fn(),
|
|
20
|
+
log: jest.fn()
|
|
21
|
+
},
|
|
22
|
+
workflow: {
|
|
23
|
+
start: jest.fn((id, type) => {
|
|
24
|
+
mockWorkflows.set(id, { type, steps: [] });
|
|
25
|
+
return id;
|
|
26
|
+
}),
|
|
27
|
+
step: jest.fn((id, name) => {
|
|
28
|
+
const wf = mockWorkflows.get(id);
|
|
29
|
+
if (wf) wf.steps.push(name);
|
|
30
|
+
}),
|
|
31
|
+
end: jest.fn((id) => {
|
|
32
|
+
mockWorkflows.delete(id);
|
|
33
|
+
}),
|
|
34
|
+
error: jest.fn((id, error, attrs) => {
|
|
35
|
+
// Handle error workflow
|
|
36
|
+
}),
|
|
37
|
+
activeWorkflows: mockWorkflows,
|
|
38
|
+
getWorkflow: jest.fn((id) => mockWorkflows.get(id))
|
|
39
|
+
},
|
|
40
|
+
createCounter: jest.fn(() => ({ add: jest.fn() })),
|
|
41
|
+
createHistogram: jest.fn(() => ({ record: jest.fn() })),
|
|
42
|
+
createMetric: jest.fn(() => ({ add: jest.fn(), record: jest.fn() })),
|
|
43
|
+
shutdown: jest.fn()
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
init: jest.fn(createMockCore),
|
|
49
|
+
getInstance: jest.fn(() => null),
|
|
50
|
+
createLogger: jest.fn((config) => ({
|
|
51
|
+
info: jest.fn(),
|
|
52
|
+
error: jest.fn(),
|
|
53
|
+
warn: jest.fn(),
|
|
54
|
+
debug: jest.fn()
|
|
55
|
+
})),
|
|
56
|
+
WorkflowTracker: jest.fn().mockImplementation((name) => ({
|
|
57
|
+
serviceName: name,
|
|
58
|
+
start: jest.fn(),
|
|
59
|
+
activeWorkflows: new Map()
|
|
60
|
+
}))
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const { init } = require('../../src');
|
|
65
|
+
|
|
66
|
+
describe('conn-base-monitoring Component Tests', () => {
|
|
67
|
+
let monitoring;
|
|
68
|
+
|
|
69
|
+
beforeAll(() => {
|
|
70
|
+
process.env.MONITORING_MODE = 'off';
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(async () => {
|
|
74
|
+
if (monitoring && monitoring.shutdown) {
|
|
75
|
+
await monitoring.shutdown();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('Full Workflow Lifecycle', () => {
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
monitoring = await init({
|
|
82
|
+
serviceName: 'component-test',
|
|
83
|
+
serviceVersion: '1.0.0',
|
|
84
|
+
mode: 'off',
|
|
85
|
+
logLevel: 'DEBUG'
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should handle complete workflow with logging', () => {
|
|
90
|
+
const workflowId = 'comp-wf-1';
|
|
91
|
+
|
|
92
|
+
// Start workflow
|
|
93
|
+
monitoring.info(`Starting workflow ${workflowId}`);
|
|
94
|
+
monitoring.startWorkflow(workflowId, 'data-processing', {
|
|
95
|
+
source: 'api',
|
|
96
|
+
userId: 'user-123'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Process steps
|
|
100
|
+
monitoring.stepWorkflow(workflowId, 'validation', { status: 'validating' });
|
|
101
|
+
monitoring.debug('Validating input data', { workflowId, records: 100 });
|
|
102
|
+
|
|
103
|
+
monitoring.stepWorkflow(workflowId, 'transformation', { status: 'transforming' });
|
|
104
|
+
monitoring.info('Transforming data', { workflowId, format: 'json' });
|
|
105
|
+
|
|
106
|
+
monitoring.stepWorkflow(workflowId, 'persistence', { status: 'saving' });
|
|
107
|
+
monitoring.debug('Saving to database', { workflowId, table: 'results' });
|
|
108
|
+
|
|
109
|
+
// Complete workflow
|
|
110
|
+
monitoring.endWorkflow(workflowId, 'success', {
|
|
111
|
+
duration: 1500,
|
|
112
|
+
recordsProcessed: 100
|
|
113
|
+
});
|
|
114
|
+
monitoring.info(`Workflow ${workflowId} completed successfully`);
|
|
115
|
+
|
|
116
|
+
// Workflow completed successfully - we trust internal tracking
|
|
117
|
+
expect(monitoring.workflow.activeWorkflows.has(workflowId)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should handle workflow with errors and recovery', () => {
|
|
121
|
+
const workflowId = 'error-wf-1';
|
|
122
|
+
|
|
123
|
+
monitoring.startWorkflow(workflowId, 'risky-operation');
|
|
124
|
+
monitoring.info('Starting risky operation', { workflowId });
|
|
125
|
+
|
|
126
|
+
// Simulate error
|
|
127
|
+
const error = new Error('Database connection failed');
|
|
128
|
+
monitoring.errorWorkflow(workflowId, error, {
|
|
129
|
+
retryCount: 1,
|
|
130
|
+
maxRetries: 3
|
|
131
|
+
});
|
|
132
|
+
monitoring.error('Operation failed, retrying', {
|
|
133
|
+
workflowId,
|
|
134
|
+
error: error.message
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Retry
|
|
138
|
+
monitoring.stepWorkflow(workflowId, 'retry-1', { attempt: 2 });
|
|
139
|
+
monitoring.warn('Retrying operation', { workflowId, attempt: 2 });
|
|
140
|
+
|
|
141
|
+
// Success after retry
|
|
142
|
+
monitoring.endWorkflow(workflowId, 'recovered', {
|
|
143
|
+
totalAttempts: 2
|
|
144
|
+
});
|
|
145
|
+
monitoring.info('Operation recovered after retry', { workflowId });
|
|
146
|
+
|
|
147
|
+
expect(monitoring.workflow.activeWorkflows.has(workflowId)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('Metrics with Workflow Tracking', () => {
|
|
152
|
+
beforeEach(async () => {
|
|
153
|
+
monitoring = await init({
|
|
154
|
+
serviceName: 'metrics-workflow',
|
|
155
|
+
mode: 'light' // Enable metrics
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should track metrics during workflow execution', () => {
|
|
160
|
+
const requestCounter = monitoring.createCounter('api.requests', {
|
|
161
|
+
description: 'API request count'
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const latencyHistogram = monitoring.createHistogram('api.latency', {
|
|
165
|
+
description: 'API latency in ms',
|
|
166
|
+
unit: 'ms'
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Simulate multiple API calls
|
|
170
|
+
const workflows = [];
|
|
171
|
+
for (let i = 0; i < 5; i++) {
|
|
172
|
+
const wfId = `metric-wf-${i}`;
|
|
173
|
+
workflows.push(wfId);
|
|
174
|
+
|
|
175
|
+
monitoring.startWorkflow(wfId, 'api-call');
|
|
176
|
+
requestCounter.add(1, { endpoint: '/api/data', method: 'GET' });
|
|
177
|
+
|
|
178
|
+
// Simulate varying latencies
|
|
179
|
+
const latency = 100 + Math.random() * 200;
|
|
180
|
+
latencyHistogram.record(latency, { endpoint: '/api/data' });
|
|
181
|
+
|
|
182
|
+
monitoring.endWorkflow(wfId, 'success');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// All workflows should be completed
|
|
186
|
+
workflows.forEach(wfId => {
|
|
187
|
+
expect(monitoring.workflow.activeWorkflows.has(wfId)).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should handle error metrics', () => {
|
|
192
|
+
const errorCounter = monitoring.createCounter('errors.total', {
|
|
193
|
+
description: 'Total errors'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const errors = [
|
|
197
|
+
{ type: 'ValidationError', code: 400 },
|
|
198
|
+
{ type: 'DatabaseError', code: 500 },
|
|
199
|
+
{ type: 'TimeoutError', code: 408 }
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
errors.forEach((err, index) => {
|
|
203
|
+
const wfId = `error-metric-${index}`;
|
|
204
|
+
monitoring.startWorkflow(wfId, 'operation');
|
|
205
|
+
|
|
206
|
+
errorCounter.add(1, {
|
|
207
|
+
errorType: err.type,
|
|
208
|
+
statusCode: err.code
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
monitoring.error(`${err.type} occurred`, {
|
|
212
|
+
workflowId: wfId,
|
|
213
|
+
code: err.code
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
monitoring.errorWorkflow(wfId, new Error(err.type), {
|
|
217
|
+
statusCode: err.code
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
monitoring.endWorkflow(wfId, 'failed');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(monitoring.workflow.activeWorkflows.size).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Concurrent Operations', () => {
|
|
228
|
+
beforeEach(async () => {
|
|
229
|
+
monitoring = await init({
|
|
230
|
+
serviceName: 'concurrent-test',
|
|
231
|
+
mode: 'off'
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('should handle multiple concurrent workflows', () => {
|
|
236
|
+
const workflows = [];
|
|
237
|
+
const concurrentCount = 10;
|
|
238
|
+
|
|
239
|
+
// Start multiple workflows
|
|
240
|
+
for (let i = 0; i < concurrentCount; i++) {
|
|
241
|
+
const wfId = `concurrent-${i}`;
|
|
242
|
+
workflows.push(wfId);
|
|
243
|
+
monitoring.startWorkflow(wfId, 'parallel-process');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
expect(monitoring.workflow.activeWorkflows.size).toBe(concurrentCount);
|
|
247
|
+
|
|
248
|
+
// Process each workflow
|
|
249
|
+
workflows.forEach((wfId, index) => {
|
|
250
|
+
monitoring.stepWorkflow(wfId, 'processing', { index });
|
|
251
|
+
monitoring.info(`Processing workflow ${index}`, { workflowId: wfId });
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Complete half of them
|
|
255
|
+
const halfPoint = Math.floor(concurrentCount / 2);
|
|
256
|
+
workflows.slice(0, halfPoint).forEach(wfId => {
|
|
257
|
+
monitoring.endWorkflow(wfId, 'completed');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(monitoring.workflow.activeWorkflows.size).toBe(concurrentCount - halfPoint);
|
|
261
|
+
|
|
262
|
+
// Complete the rest
|
|
263
|
+
workflows.slice(halfPoint).forEach(wfId => {
|
|
264
|
+
monitoring.endWorkflow(wfId, 'completed');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(monitoring.workflow.activeWorkflows.size).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('should handle rapid fire logging', () => {
|
|
271
|
+
const messageCount = 100;
|
|
272
|
+
|
|
273
|
+
expect(() => {
|
|
274
|
+
for (let i = 0; i < messageCount; i++) {
|
|
275
|
+
const level = ['debug', 'info', 'warn', 'error'][i % 4];
|
|
276
|
+
monitoring[level](`Message ${i}`, { index: i, level });
|
|
277
|
+
}
|
|
278
|
+
}).not.toThrow();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('Configuration Modes', () => {
|
|
283
|
+
test('should work in off mode (no external connections)', async () => {
|
|
284
|
+
const offMode = await init({
|
|
285
|
+
serviceName: 'off-mode-test',
|
|
286
|
+
mode: 'off'
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(() => {
|
|
290
|
+
offMode.info('This should only console log');
|
|
291
|
+
offMode.startWorkflow('wf-off', 'test');
|
|
292
|
+
offMode.endWorkflow('wf-off', 'done');
|
|
293
|
+
}).not.toThrow();
|
|
294
|
+
|
|
295
|
+
await offMode.shutdown();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('should work in light mode (basic telemetry)', async () => {
|
|
299
|
+
const lightMode = await init({
|
|
300
|
+
serviceName: 'light-mode-test',
|
|
301
|
+
mode: 'light'
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const counter = lightMode.createCounter('light.counter');
|
|
305
|
+
expect(() => {
|
|
306
|
+
counter.add(1);
|
|
307
|
+
lightMode.info('Light mode active');
|
|
308
|
+
}).not.toThrow();
|
|
309
|
+
|
|
310
|
+
await lightMode.shutdown();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('should work in full mode (complete telemetry)', async () => {
|
|
314
|
+
const fullMode = await init({
|
|
315
|
+
serviceName: 'full-mode-test',
|
|
316
|
+
mode: 'full'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(() => {
|
|
320
|
+
fullMode.startWorkflow('full-wf', 'complete');
|
|
321
|
+
fullMode.stepWorkflow('full-wf', 'step1');
|
|
322
|
+
fullMode.endWorkflow('full-wf', 'done');
|
|
323
|
+
|
|
324
|
+
const histogram = fullMode.createHistogram('full.histogram');
|
|
325
|
+
histogram.record(123.45);
|
|
326
|
+
}).not.toThrow();
|
|
327
|
+
|
|
328
|
+
await fullMode.shutdown();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('should work in debug mode (verbose output)', async () => {
|
|
332
|
+
const debugMode = await init({
|
|
333
|
+
serviceName: 'debug-mode-test',
|
|
334
|
+
mode: 'debug',
|
|
335
|
+
logLevel: 'DEBUG'
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(() => {
|
|
339
|
+
debugMode.debug('Debug details', { verbose: true });
|
|
340
|
+
debugMode.info('Info message');
|
|
341
|
+
debugMode.warn('Warning');
|
|
342
|
+
debugMode.error('Error');
|
|
343
|
+
}).not.toThrow();
|
|
344
|
+
|
|
345
|
+
await debugMode.shutdown();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe('Error Handling and Edge Cases', () => {
|
|
350
|
+
beforeEach(async () => {
|
|
351
|
+
monitoring = await init({
|
|
352
|
+
serviceName: 'edge-cases',
|
|
353
|
+
mode: 'off'
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('should handle null and undefined values', () => {
|
|
358
|
+
expect(() => {
|
|
359
|
+
monitoring.info(null);
|
|
360
|
+
monitoring.info(undefined);
|
|
361
|
+
monitoring.info('Message', null);
|
|
362
|
+
monitoring.info('Message', undefined);
|
|
363
|
+
}).not.toThrow();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('should handle circular references', () => {
|
|
367
|
+
const obj = { name: 'circular' };
|
|
368
|
+
obj.self = obj;
|
|
369
|
+
|
|
370
|
+
expect(() => {
|
|
371
|
+
monitoring.info('Circular ref', obj);
|
|
372
|
+
}).not.toThrow();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('should handle very long messages', () => {
|
|
376
|
+
const longMessage = 'x'.repeat(10000);
|
|
377
|
+
const longData = {
|
|
378
|
+
field: 'y'.repeat(10000)
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
expect(() => {
|
|
382
|
+
monitoring.info(longMessage, longData);
|
|
383
|
+
}).not.toThrow();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('should handle special characters', () => {
|
|
387
|
+
expect(() => {
|
|
388
|
+
monitoring.info('Unicode: 你好 مرحبا שלום', {
|
|
389
|
+
emoji: '🚀🎉😀',
|
|
390
|
+
special: '\n\t\r\0'
|
|
391
|
+
});
|
|
392
|
+
}).not.toThrow();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('should handle workflow with missing steps', () => {
|
|
396
|
+
const wfId = 'incomplete-wf';
|
|
397
|
+
|
|
398
|
+
monitoring.startWorkflow(wfId, 'test');
|
|
399
|
+
// Skip steps, go directly to end
|
|
400
|
+
monitoring.endWorkflow(wfId, 'skipped');
|
|
401
|
+
|
|
402
|
+
expect(monitoring.workflow.activeWorkflows.has(wfId)).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('should handle double workflow start', () => {
|
|
406
|
+
const wfId = 'double-start';
|
|
407
|
+
|
|
408
|
+
monitoring.startWorkflow(wfId, 'first');
|
|
409
|
+
monitoring.startWorkflow(wfId, 'second'); // Should replace
|
|
410
|
+
|
|
411
|
+
const workflow = monitoring.workflow.getWorkflow(wfId);
|
|
412
|
+
expect(workflow.type).toBe('second');
|
|
413
|
+
|
|
414
|
+
monitoring.endWorkflow(wfId, 'done');
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('Memory and Resource Management', () => {
|
|
419
|
+
beforeEach(async () => {
|
|
420
|
+
// Create fresh monitoring instance for memory tests
|
|
421
|
+
monitoring = await init({
|
|
422
|
+
serviceName: 'memory-test',
|
|
423
|
+
mode: 'off'
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test('should not leak memory with many workflows', async () => {
|
|
428
|
+
const iterations = 100;
|
|
429
|
+
|
|
430
|
+
for (let i = 0; i < iterations; i++) {
|
|
431
|
+
const wfId = `mem-test-${i}`;
|
|
432
|
+
monitoring.startWorkflow(wfId, 'memory-test');
|
|
433
|
+
monitoring.stepWorkflow(wfId, 'process');
|
|
434
|
+
monitoring.endWorkflow(wfId, 'done');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
expect(monitoring.workflow.activeWorkflows.size).toBe(0);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('should handle rapid instance creation', async () => {
|
|
441
|
+
const instances = [];
|
|
442
|
+
|
|
443
|
+
for (let i = 0; i < 10; i++) {
|
|
444
|
+
const instance = await init({
|
|
445
|
+
serviceName: `rapid-${i}`,
|
|
446
|
+
mode: 'off'
|
|
447
|
+
});
|
|
448
|
+
instances.push(instance);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// All instances should be independent
|
|
452
|
+
expect(instances[0]).not.toBe(instances[1]);
|
|
453
|
+
|
|
454
|
+
// Cleanup
|
|
455
|
+
for (const instance of instances) {
|
|
456
|
+
await instance.shutdown();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
});
|