chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Unit Tests for PerformanceMonitor
|
||||
* Tests performance monitoring and metrics collection
|
||||
*/
|
||||
|
||||
const PerformanceMonitor = require('../../src/analytics/utils/PerformanceMonitor');
|
||||
|
||||
describe('PerformanceMonitor', () => {
|
||||
let performanceMonitor;
|
||||
|
||||
beforeEach(() => {
|
||||
performanceMonitor = new PerformanceMonitor({
|
||||
enabled: true,
|
||||
logInterval: 1000,
|
||||
memoryThreshold: 50 * 1024 * 1024 // 50MB for testing
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (performanceMonitor) {
|
||||
performanceMonitor.stopMonitoring();
|
||||
}
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with default options', () => {
|
||||
const monitor = new PerformanceMonitor();
|
||||
|
||||
expect(monitor.options.enabled).toBe(true);
|
||||
expect(monitor.options.logInterval).toBe(60000);
|
||||
expect(monitor.options.memoryThreshold).toBe(100 * 1024 * 1024);
|
||||
expect(monitor.metrics).toBeDefined();
|
||||
expect(monitor.timers).toEqual({});
|
||||
expect(monitor.counters).toEqual({});
|
||||
});
|
||||
|
||||
it('should merge custom options', () => {
|
||||
const customOptions = {
|
||||
enabled: false,
|
||||
logInterval: 5000,
|
||||
memoryThreshold: 200 * 1024 * 1024
|
||||
};
|
||||
|
||||
const monitor = new PerformanceMonitor(customOptions);
|
||||
|
||||
expect(monitor.options.enabled).toBe(false);
|
||||
expect(monitor.options.logInterval).toBe(5000);
|
||||
expect(monitor.options.memoryThreshold).toBe(200 * 1024 * 1024);
|
||||
});
|
||||
});
|
||||
|
||||
describe('timer operations', () => {
|
||||
it('should start and end timers correctly', () => {
|
||||
const timerName = 'test_operation';
|
||||
|
||||
performanceMonitor.startTimer(timerName);
|
||||
expect(performanceMonitor.timers[timerName]).toBeDefined();
|
||||
expect(performanceMonitor.timers[timerName].start).toBeDefined();
|
||||
|
||||
// Wait a small amount of time
|
||||
setTimeout(() => {
|
||||
const duration = performanceMonitor.endTimer(timerName);
|
||||
|
||||
expect(duration).toBeGreaterThan(0);
|
||||
expect(performanceMonitor.timers[timerName]).toBeUndefined();
|
||||
expect(performanceMonitor.metrics.performance).toBeDefined();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
it('should handle ending non-existent timer', () => {
|
||||
const duration = performanceMonitor.endTimer('non_existent');
|
||||
expect(duration).toBe(0);
|
||||
});
|
||||
|
||||
it('should record timer metadata', () => {
|
||||
const timerName = 'test_with_metadata';
|
||||
const metadata = { userId: '123', action: 'save' };
|
||||
|
||||
performanceMonitor.startTimer(timerName);
|
||||
performanceMonitor.endTimer(timerName, metadata);
|
||||
|
||||
const performanceMetrics = performanceMonitor.metrics.performance;
|
||||
expect(performanceMetrics).toBeDefined();
|
||||
expect(performanceMetrics.length).toBeGreaterThan(0);
|
||||
|
||||
const lastMetric = performanceMetrics[performanceMetrics.length - 1];
|
||||
expect(lastMetric.operation).toBe(timerName);
|
||||
expect(lastMetric.userId).toBe('123');
|
||||
expect(lastMetric.action).toBe('save');
|
||||
});
|
||||
});
|
||||
|
||||
describe('counter operations', () => {
|
||||
it('should increment counters', () => {
|
||||
const counterName = 'test_counter';
|
||||
|
||||
performanceMonitor.incrementCounter(counterName);
|
||||
expect(performanceMonitor.counters[counterName]).toBe(1);
|
||||
|
||||
performanceMonitor.incrementCounter(counterName, 5);
|
||||
expect(performanceMonitor.counters[counterName]).toBe(6);
|
||||
});
|
||||
|
||||
it('should handle initial counter value', () => {
|
||||
const counterName = 'new_counter';
|
||||
|
||||
performanceMonitor.incrementCounter(counterName, 10);
|
||||
expect(performanceMonitor.counters[counterName]).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metric recording', () => {
|
||||
it('should record metrics in categories', () => {
|
||||
const category = 'test_category';
|
||||
const data = { value: 42, timestamp: Date.now() };
|
||||
|
||||
performanceMonitor.recordMetric(category, data);
|
||||
|
||||
expect(performanceMonitor.metrics[category]).toBeDefined();
|
||||
expect(performanceMonitor.metrics[category]).toHaveLength(1);
|
||||
expect(performanceMonitor.metrics[category][0]).toMatchObject(data);
|
||||
});
|
||||
|
||||
it('should add timestamp if not provided', () => {
|
||||
const category = 'test_category';
|
||||
const data = { value: 42 };
|
||||
|
||||
performanceMonitor.recordMetric(category, data);
|
||||
|
||||
const metric = performanceMonitor.metrics[category][0];
|
||||
expect(metric.timestamp).toBeDefined();
|
||||
expect(typeof metric.timestamp).toBe('number');
|
||||
});
|
||||
|
||||
it('should limit metric array size', () => {
|
||||
const category = 'test_category';
|
||||
|
||||
// Add more than 1000 metrics
|
||||
for (let i = 0; i < 1005; i++) {
|
||||
performanceMonitor.recordMetric(category, { value: i });
|
||||
}
|
||||
|
||||
expect(performanceMonitor.metrics[category]).toHaveLength(1000);
|
||||
});
|
||||
|
||||
it('should skip recording when disabled', () => {
|
||||
performanceMonitor.options.enabled = false;
|
||||
|
||||
performanceMonitor.recordMetric('test', { value: 1 });
|
||||
|
||||
expect(performanceMonitor.metrics.test).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('error recording', () => {
|
||||
it('should record errors', () => {
|
||||
const errorType = 'validation_error';
|
||||
const errorMessage = 'Invalid input data';
|
||||
const metadata = { field: 'email' };
|
||||
|
||||
performanceMonitor.recordError(errorType, errorMessage, metadata);
|
||||
|
||||
expect(performanceMonitor.metrics.errors).toBeDefined();
|
||||
expect(performanceMonitor.metrics.errors).toHaveLength(1);
|
||||
|
||||
const error = performanceMonitor.metrics.errors[0];
|
||||
expect(error.type).toBe(errorType);
|
||||
expect(error.message).toBe(errorMessage);
|
||||
expect(error.field).toBe('email');
|
||||
});
|
||||
});
|
||||
|
||||
describe('request recording', () => {
|
||||
it('should record successful requests', () => {
|
||||
const endpoint = '/api/data';
|
||||
const duration = 150;
|
||||
const statusCode = 200;
|
||||
|
||||
performanceMonitor.recordRequest(endpoint, duration, statusCode);
|
||||
|
||||
expect(performanceMonitor.metrics.requests).toHaveLength(1);
|
||||
expect(performanceMonitor.counters.total_requests).toBe(1);
|
||||
expect(performanceMonitor.counters.error_requests).toBeUndefined();
|
||||
|
||||
const request = performanceMonitor.metrics.requests[0];
|
||||
expect(request.endpoint).toBe(endpoint);
|
||||
expect(request.duration).toBe(duration);
|
||||
expect(request.statusCode).toBe(statusCode);
|
||||
expect(request.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should record error requests', () => {
|
||||
const endpoint = '/api/data';
|
||||
const duration = 500;
|
||||
const statusCode = 500;
|
||||
|
||||
performanceMonitor.recordRequest(endpoint, duration, statusCode);
|
||||
|
||||
expect(performanceMonitor.counters.total_requests).toBe(1);
|
||||
expect(performanceMonitor.counters.error_requests).toBe(1);
|
||||
|
||||
const request = performanceMonitor.metrics.requests[0];
|
||||
expect(request.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache recording', () => {
|
||||
it('should record cache operations', () => {
|
||||
const operation = 'hit';
|
||||
const key = 'user:123';
|
||||
const duration = 5;
|
||||
|
||||
performanceMonitor.recordCache(operation, key, duration);
|
||||
|
||||
expect(performanceMonitor.metrics.cache).toHaveLength(1);
|
||||
expect(performanceMonitor.counters[`cache_${operation}`]).toBe(1);
|
||||
|
||||
const cacheMetric = performanceMonitor.metrics.cache[0];
|
||||
expect(cacheMetric.operation).toBe(operation);
|
||||
expect(cacheMetric.key).toBe(key);
|
||||
expect(cacheMetric.duration).toBe(duration);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebSocket recording', () => {
|
||||
it('should record WebSocket events', () => {
|
||||
const event = 'connection';
|
||||
const data = { clientId: 'client_123' };
|
||||
|
||||
performanceMonitor.recordWebSocket(event, data);
|
||||
|
||||
expect(performanceMonitor.metrics.websocket).toHaveLength(1);
|
||||
expect(performanceMonitor.counters[`websocket_${event}`]).toBe(1);
|
||||
|
||||
const wsMetric = performanceMonitor.metrics.websocket[0];
|
||||
expect(wsMetric.event).toBe(event);
|
||||
expect(wsMetric.clientId).toBe('client_123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('system metrics collection', () => {
|
||||
it('should collect memory and CPU metrics', () => {
|
||||
performanceMonitor.collectSystemMetrics();
|
||||
|
||||
expect(performanceMonitor.metrics.memory).toHaveLength(1);
|
||||
expect(performanceMonitor.metrics.cpu).toHaveLength(1);
|
||||
|
||||
const memMetric = performanceMonitor.metrics.memory[0];
|
||||
expect(memMetric.rss).toBeDefined();
|
||||
expect(memMetric.heapUsed).toBeDefined();
|
||||
expect(memMetric.heapTotal).toBeDefined();
|
||||
|
||||
const cpuMetric = performanceMonitor.metrics.cpu[0];
|
||||
expect(cpuMetric.user).toBeDefined();
|
||||
expect(cpuMetric.system).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('statistics calculation', () => {
|
||||
beforeEach(() => {
|
||||
// Add some test data
|
||||
performanceMonitor.recordRequest('/api/test1', 100, 200);
|
||||
performanceMonitor.recordRequest('/api/test2', 200, 404);
|
||||
performanceMonitor.recordRequest('/api/test1', 150, 200);
|
||||
|
||||
performanceMonitor.recordCache('hit', 'key1', 5);
|
||||
performanceMonitor.recordCache('miss', 'key2', 10);
|
||||
performanceMonitor.recordCache('hit', 'key3', 3);
|
||||
|
||||
performanceMonitor.recordError('test_error', 'Test message');
|
||||
|
||||
performanceMonitor.collectSystemMetrics();
|
||||
});
|
||||
|
||||
it('should calculate request statistics', () => {
|
||||
const stats = performanceMonitor.getStats();
|
||||
|
||||
expect(stats.requests.total).toBe(3);
|
||||
expect(stats.requests.successful).toBe(2);
|
||||
expect(stats.requests.errors).toBe(1);
|
||||
expect(stats.requests.averageResponseTime).toBe(150); // (100 + 200 + 150) / 3
|
||||
});
|
||||
|
||||
it('should calculate cache hit rate', () => {
|
||||
const stats = performanceMonitor.getStats();
|
||||
|
||||
expect(stats.cache.operations).toBe(3);
|
||||
expect(stats.cache.hitRate).toBe(66.67); // 2 hits out of 3 operations
|
||||
});
|
||||
|
||||
it('should include error count', () => {
|
||||
const stats = performanceMonitor.getStats();
|
||||
|
||||
expect(stats.errors.total).toBe(1);
|
||||
});
|
||||
|
||||
it('should include memory statistics', () => {
|
||||
const stats = performanceMonitor.getStats();
|
||||
|
||||
expect(stats.memory.current).toBeDefined();
|
||||
expect(stats.memory.average).toBeDefined();
|
||||
expect(stats.memory.peak).toBeDefined();
|
||||
});
|
||||
|
||||
it('should filter by timeframe', () => {
|
||||
// Add old data (simulate by manually setting old timestamp)
|
||||
const oldTimestamp = Date.now() - 10 * 60 * 1000; // 10 minutes ago
|
||||
performanceMonitor.metrics.requests.push({
|
||||
endpoint: '/api/old',
|
||||
duration: 999,
|
||||
timestamp: oldTimestamp
|
||||
});
|
||||
|
||||
// Get stats for last 5 minutes
|
||||
const stats = performanceMonitor.getStats(5 * 60 * 1000);
|
||||
|
||||
// Should not include the old request
|
||||
expect(stats.requests.total).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Express middleware', () => {
|
||||
it('should create Express middleware function', () => {
|
||||
const middleware = performanceMonitor.createExpressMiddleware();
|
||||
|
||||
expect(typeof middleware).toBe('function');
|
||||
expect(middleware.length).toBe(3); // (req, res, next)
|
||||
});
|
||||
|
||||
it('should track request performance', (done) => {
|
||||
const middleware = performanceMonitor.createExpressMiddleware();
|
||||
|
||||
// Mock Express req, res, next
|
||||
const req = {
|
||||
method: 'GET',
|
||||
url: '/api/test',
|
||||
route: { path: '/api/test' },
|
||||
get: jest.fn().mockReturnValue('test-agent'),
|
||||
ip: '127.0.0.1'
|
||||
};
|
||||
|
||||
const res = {
|
||||
on: jest.fn((event, callback) => {
|
||||
if (event === 'finish') {
|
||||
// Simulate response finishing
|
||||
setTimeout(() => {
|
||||
res.statusCode = 200;
|
||||
callback();
|
||||
|
||||
// Check that request was recorded
|
||||
expect(performanceMonitor.metrics.requests).toHaveLength(1);
|
||||
expect(performanceMonitor.counters.total_requests).toBe(1);
|
||||
|
||||
done();
|
||||
}, 10);
|
||||
}
|
||||
}),
|
||||
statusCode: 200
|
||||
};
|
||||
|
||||
const next = jest.fn();
|
||||
|
||||
middleware(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanup and memory management', () => {
|
||||
it('should clean up old metrics', () => {
|
||||
// Add metrics with old timestamps
|
||||
const oldTimestamp = Date.now() - 2 * 60 * 60 * 1000; // 2 hours ago
|
||||
performanceMonitor.metrics.requests = [
|
||||
{ endpoint: '/api/old', timestamp: oldTimestamp },
|
||||
{ endpoint: '/api/new', timestamp: Date.now() }
|
||||
];
|
||||
|
||||
performanceMonitor.cleanupOldMetrics();
|
||||
|
||||
expect(performanceMonitor.metrics.requests).toHaveLength(1);
|
||||
expect(performanceMonitor.metrics.requests[0].endpoint).toBe('/api/new');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test utilities
|
||||
const testUtils = {
|
||||
async waitFor(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
};
|
||||
|
||||
// Make testUtils available for cachedFetch test
|
||||
global.testUtils = testUtils;
|
||||
Reference in New Issue
Block a user