import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import { PerformanceMonitor } from '../../lib/utils/performance-monitor.js';
describe('PerformanceMonitor', () => {
let monitor;
let originalProcess;
beforeEach(() => {
// Mock console methods to prevent test output noise
vi.spyOn(console, 'warn').mockImplementation(() => {});
// Mock process.memoryUsage for memory testing
originalProcess = global.process;
global.process = {
...originalProcess,
memoryUsage: vi.fn(() => ({
heapUsed: 100 * 1024 * 1024 // 100 MB
}))
};
// Mock Math.random for predictable IDs
vi.spyOn(Math, 'random').mockReturnValue(0.123456789);
// Mock Date.now for predictable timestamps
vi.spyOn(Date, 'now')
.mockReturnValueOnce(1000) // Constructor startTime
.mockReturnValue(2000); // Default for other calls
});
afterEach(() => {
global.process = originalProcess;
vi.clearAllMocks();
});
describe('Constructor and Configuration', () => {
test('should initialize with default configuration', () => {
monitor = new PerformanceMonitor();
expect(monitor.config).toEqual({
enabled: true,
maxMetricsHistory: 1000,
slowQueryThreshold: 5000,
trackPoolMetrics: true,
samplingRate: 1.0
});
expect(monitor.metrics.queries).toEqual([]);
expect(monitor.metrics.connections).toEqual([]);
expect(monitor.metrics.poolStats).toEqual({
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
pendingRequests: 0,
errors: 0
});
expect(monitor.startTime).toBe(1000);
});
test('should initialize with custom configuration', () => {
const config = {
enabled: false,
maxMetricsHistory: 500,
slowQueryThreshold: 2000,
trackPoolMetrics: false,
samplingRate: 0.5,
customOption: 'test'
};
monitor = new PerformanceMonitor(config);
expect(monitor.config).toEqual(config);
});
test('should initialize aggregates with correct defaults', () => {
monitor = new PerformanceMonitor();
expect(monitor.metrics.aggregates).toEqual({
totalQueries: 0,
slowQueries: 0,
avgQueryTime: 0,
maxQueryTime: 0,
minQueryTime: Number.MAX_SAFE_INTEGER,
totalQueryTime: 0,
errorRate: 0
});
});
});
describe('Query Tracking', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should start query tracking successfully', () => {
const queryId = monitor.startQuery('execute_query', 'SELECT * FROM users', {
database: 'testdb'
});
expect(queryId).toMatch(/^q_\d+_[a-z0-9]+$/);
expect(monitor.metrics.queries).toHaveLength(1);
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric).toEqual({
id: expect.any(String),
tool: 'execute_query',
query: 'SELECT * FROM users',
database: 'testdb',
startTime: 2000,
startMemory: 100,
status: 'running'
});
});
test('should truncate long queries', () => {
const longQuery =
'SELECT * FROM users WHERE ' + 'condition AND '.repeat(50) + 'final_condition';
monitor.startQuery('execute_query', longQuery, {});
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric.query).toHaveLength(203); // 200 chars + '...'
expect(queryMetric.query.endsWith('...')).toBe(true);
});
test('should use default database when not specified', () => {
monitor.startQuery('list_tables', 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES');
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric.database).toBe('default');
});
test('should not track query when disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
const queryId = monitor.startQuery('execute_query', 'SELECT 1');
expect(queryId).toBeNull();
expect(monitor.metrics.queries).toHaveLength(0);
});
test('should respect sampling rate', () => {
monitor = new PerformanceMonitor({ samplingRate: 0.1 });
Math.random.mockReturnValue(0.5); // > 0.1, should not sample
const queryId = monitor.startQuery('execute_query', 'SELECT 1');
expect(queryId).toBeNull();
expect(monitor.metrics.queries).toHaveLength(0);
});
test('should sample when within sampling rate', () => {
monitor = new PerformanceMonitor({ samplingRate: 0.5 });
Math.random.mockReturnValue(0.3); // < 0.5, should sample
const queryId = monitor.startQuery('execute_query', 'SELECT 1');
expect(queryId).not.toBeNull();
expect(monitor.metrics.queries).toHaveLength(1);
});
});
describe('Query Completion', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
Date.now
.mockReturnValueOnce(1000) // Constructor
.mockReturnValueOnce(2000) // startQuery
.mockReturnValue(3500); // endQuery - 1500ms duration
});
test('should complete query successfully', () => {
const queryId = monitor.startQuery('execute_query', 'SELECT * FROM users');
const result = {
recordset: [{ id: 1 }, { id: 2 }, { id: 3 }],
rowsAffected: [3]
};
monitor.endQuery(queryId, result);
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric).toEqual({
id: queryId,
tool: 'execute_query',
query: 'SELECT * FROM users',
database: 'default',
startTime: 2000,
startMemory: 100,
status: 'completed',
endTime: 3500,
duration: 1500,
endMemory: 100,
memoryDelta: 0,
error: null,
rowsAffected: [3],
rowCount: 3,
streaming: false
});
});
test('should handle query with error', () => {
const queryId = monitor.startQuery('execute_query', 'INVALID SQL');
const error = new Error('Syntax error near INVALID');
monitor.endQuery(queryId, {}, error);
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric.status).toBe('error');
expect(queryMetric.error).toBe('Syntax error near INVALID');
expect(queryMetric.rowsAffected).toBe(0);
expect(queryMetric.rowCount).toBe(0);
});
test('should handle streaming result', () => {
const queryId = monitor.startQuery('export_table_csv', 'SELECT * FROM large_table');
const result = { streaming: true, rowsAffected: [10000] };
monitor.endQuery(queryId, result);
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric.streaming).toBe(true);
expect(queryMetric.rowsAffected).toEqual([10000]);
});
test('should not complete query when disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
monitor.endQuery('fake-id', {});
expect(monitor.metrics.queries).toHaveLength(0);
});
test('should not complete query with invalid ID', () => {
const _queryId = monitor.startQuery('execute_query', 'SELECT 1');
monitor.endQuery('invalid-id', {});
const queryMetric = monitor.metrics.queries[0];
expect(queryMetric.status).toBe('running'); // Should remain running
});
// Note: Slow query logging functionality is tested indirectly through
// aggregate statistics and other tests
});
describe('Aggregate Statistics', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should update aggregates for successful query', () => {
const queryId = monitor.startQuery('execute_query', 'SELECT 1');
Date.now.mockReturnValue(3000); // 1000ms duration
monitor.endQuery(queryId, { recordset: [] });
const agg = monitor.metrics.aggregates;
expect(agg.totalQueries).toBe(1);
expect(agg.totalQueryTime).toBe(1000);
expect(agg.avgQueryTime).toBe(1000);
expect(agg.maxQueryTime).toBe(1000);
expect(agg.minQueryTime).toBe(1000);
expect(agg.errorRate).toBe(0);
expect(agg.slowQueries).toBe(0);
});
test('should update aggregates for error query', () => {
const queryId = monitor.startQuery('execute_query', 'INVALID');
Date.now.mockReturnValue(2500); // 500ms duration
monitor.endQuery(queryId, {}, new Error('SQL Error'));
const agg = monitor.metrics.aggregates;
expect(agg.totalQueries).toBe(1);
expect(agg.errorRate).toBe(1); // 100% error rate
});
test('should update aggregates for slow query', () => {
monitor = new PerformanceMonitor({ slowQueryThreshold: 800 });
const queryId = monitor.startQuery('execute_query', 'SLOW QUERY');
Date.now.mockReturnValue(3000); // 1000ms duration > 800ms threshold
monitor.endQuery(queryId, { recordset: [] });
const agg = monitor.metrics.aggregates;
expect(agg.slowQueries).toBe(1);
});
test('should calculate correct averages with multiple queries', () => {
// First query: 1000ms
const queryId1 = monitor.startQuery('execute_query', 'SELECT 1');
Date.now.mockReturnValue(3000);
monitor.endQuery(queryId1, { recordset: [] });
// Second query: 2000ms
Date.now.mockReturnValue(4000);
const queryId2 = monitor.startQuery('list_tables', 'SHOW TABLES');
Date.now.mockReturnValue(6000);
monitor.endQuery(queryId2, { recordset: [] });
const agg = monitor.metrics.aggregates;
expect(agg.totalQueries).toBe(2);
expect(agg.totalQueryTime).toBe(3000); // 1000 + 2000
expect(agg.avgQueryTime).toBe(1500); // 3000 / 2
expect(agg.maxQueryTime).toBe(2000);
expect(agg.minQueryTime).toBe(1000);
});
test('should calculate error rate correctly', () => {
// Success
const queryId1 = monitor.startQuery('execute_query', 'SELECT 1');
monitor.endQuery(queryId1, { recordset: [] });
// Error
const queryId2 = monitor.startQuery('execute_query', 'INVALID');
monitor.endQuery(queryId2, {}, new Error('Error'));
// Success
const queryId3 = monitor.startQuery('execute_query', 'SELECT 2');
monitor.endQuery(queryId3, { recordset: [] });
const agg = monitor.metrics.aggregates;
expect(agg.totalQueries).toBe(3);
expect(Math.round(agg.errorRate * 100) / 100).toBe(0.33); // 1/3 ≈ 0.33
});
});
describe('Connection Pool Metrics', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should record pool metrics', () => {
const poolStats = {
totalConnections: 10,
activeConnections: 3,
idleConnections: 7,
pendingRequests: 2,
errors: 1
};
monitor.recordPoolMetrics(poolStats);
expect(monitor.metrics.poolStats).toEqual({
...poolStats,
timestamp: 2000,
uptime: 1000 // 2000 - 1000 (startTime)
});
expect(monitor.metrics.connections).toHaveLength(1);
expect(monitor.metrics.connections[0]).toEqual({
timestamp: 2000,
...poolStats
});
});
test('should not record pool metrics when disabled', () => {
monitor = new PerformanceMonitor({ trackPoolMetrics: false });
monitor.recordPoolMetrics({ totalConnections: 5 });
expect(monitor.metrics.connections).toHaveLength(0);
});
test('should not record pool metrics when monitor disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
monitor.recordPoolMetrics({ totalConnections: 5 });
expect(monitor.metrics.connections).toHaveLength(0);
});
});
describe('Connection Events', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should record connection event', () => {
monitor.recordConnectionEvent('connect', {
host: 'localhost',
port: 1433,
duration: 150
});
expect(monitor.metrics.connections).toHaveLength(1);
expect(monitor.metrics.connections[0]).toEqual({
id: expect.any(String),
event: 'connect',
timestamp: 2000,
host: 'localhost',
port: 1433,
duration: 150
});
// Should update pool stats
expect(monitor.metrics.poolStats.totalConnections).toBe(1);
expect(monitor.metrics.poolStats.activeConnections).toBe(1);
});
test('should handle disconnect event', () => {
monitor.recordConnectionEvent('disconnect', { reason: 'timeout' });
expect(monitor.metrics.poolStats.activeConnections).toBe(-1);
expect(monitor.metrics.connections[0]).toEqual({
id: expect.any(String),
event: 'disconnect',
timestamp: 2000,
reason: 'timeout'
});
});
test('should handle error event', () => {
monitor.recordConnectionEvent('error', { message: 'Connection lost' });
expect(monitor.metrics.poolStats.errors).toBe(1);
expect(monitor.metrics.connections[0]).toEqual({
id: expect.any(String),
event: 'error',
timestamp: 2000,
message: 'Connection lost'
});
});
test('should not record connection events when disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
monitor.recordConnectionEvent('connect');
expect(monitor.metrics.connections).toHaveLength(0);
expect(monitor.metrics.poolStats.totalConnections).toBe(0);
});
});
describe('Statistics Retrieval', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
// Add some test data
const queryId = monitor.startQuery('execute_query', 'SELECT * FROM test');
Date.now.mockReturnValue(4000); // 2000ms duration
monitor.endQuery(queryId, { recordset: [1, 2, 3] });
monitor.recordPoolMetrics({
totalConnections: 5,
activeConnections: 2,
idleConnections: 3,
pendingRequests: 0,
errors: 0
});
});
test('should get overall statistics', () => {
const stats = monitor.getStats();
expect(stats).toEqual({
enabled: true,
uptime: 3000, // Mock Date.now() - startTime
overall: monitor.metrics.aggregates,
recent: expect.objectContaining({
count: 1,
avgDuration: 2000,
maxDuration: 2000,
minDuration: 2000,
errorRate: 0,
slowQueryRate: 0
}),
pool: monitor.metrics.poolStats,
monitoring: {
totalQueriesTracked: 1,
totalConnectionEvents: 1,
samplingRate: 1.0,
slowQueryThreshold: 5000
}
});
});
test('should return disabled status when monitoring disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
const stats = monitor.getStats();
expect(stats).toEqual({ enabled: false });
});
test('should filter recent queries correctly', () => {
// Add an old query (older than 5 minutes)
Date.now.mockReturnValue(1000); // Very old timestamp
const oldQueryId = monitor.startQuery('old_query', 'SELECT OLD');
Date.now.mockReturnValue(1100);
monitor.endQuery(oldQueryId, { recordset: [] });
// Current time is much later
Date.now.mockReturnValue(600000); // 10 minutes later
const stats = monitor.getStats();
expect(stats.recent.count).toBe(0); // Old query should be filtered out
});
});
describe('Query Statistics', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
// Add test queries
Date.now.mockReturnValue(2000);
const query1 = monitor.startQuery('execute_query', 'SELECT 1');
Date.now.mockReturnValue(3000); // 1000ms duration
monitor.endQuery(query1, { recordset: [1] });
Date.now.mockReturnValue(4000);
const query2 = monitor.startQuery('list_tables', 'SHOW TABLES');
Date.now.mockReturnValue(5500); // 1500ms duration
monitor.endQuery(query2, { recordset: [1, 2] });
Date.now.mockReturnValue(6000);
const query3 = monitor.startQuery('execute_query', 'INVALID SQL');
Date.now.mockReturnValue(6200); // 200ms duration
monitor.endQuery(query3, {}, new Error('SQL Error'));
});
test('should get query statistics', () => {
const queryStats = monitor.getQueryStats(10);
expect(queryStats.enabled).toBe(true);
expect(queryStats.queries).toHaveLength(3);
expect(queryStats.byTool).toEqual({
execute_query: {
count: 2,
totalTime: 1200, // 1000 + 200
errors: 1,
slowQueries: 0,
avgTime: 600,
errorRate: 50,
slowQueryRate: 0
},
list_tables: {
count: 1,
totalTime: 1500,
errors: 0,
slowQueries: 0,
avgTime: 1500,
errorRate: 0,
slowQueryRate: 0
}
});
});
test('should limit query results', () => {
const queryStats = monitor.getQueryStats(1);
expect(queryStats.queries).toHaveLength(1);
// Should return the most recent query
expect(queryStats.queries[0].tool).toBe('execute_query');
});
test('should identify slow queries', () => {
monitor = new PerformanceMonitor({ slowQueryThreshold: 1200 });
// Re-run with slow query threshold
Date.now.mockReturnValue(2000);
const query1 = monitor.startQuery('execute_query', 'SELECT 1');
Date.now.mockReturnValue(4000); // 2000ms duration > 1200ms threshold
monitor.endQuery(query1, { recordset: [1] });
const queryStats = monitor.getQueryStats(10);
expect(queryStats.byTool.execute_query.slowQueries).toBe(1);
expect(queryStats.byTool.execute_query.slowQueryRate).toBe(100);
expect(queryStats.slowQueries).toHaveLength(1);
});
test('should return disabled status when monitoring disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
const queryStats = monitor.getQueryStats();
expect(queryStats).toEqual({ enabled: false });
});
});
describe('Pool Health Assessment', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should assess healthy pool', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 3,
idleConnections: 7,
pendingRequests: 1,
errors: 0
};
const health = monitor.assessPoolHealth();
expect(health.status).toBe('healthy');
expect(health.issues).toHaveLength(0);
expect(health.score).toBe(100);
});
test('should detect high error count', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 3,
idleConnections: 7,
pendingRequests: 1,
errors: 15
};
const health = monitor.assessPoolHealth();
expect(health.status).toBe('warning');
expect(health.issues).toContain('High error count detected');
expect(health.score).toBeLessThan(100);
});
test('should detect connection pool near capacity', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 10, // 100% utilization (>= 95% threshold)
idleConnections: 0,
pendingRequests: 1,
errors: 0
};
const health = monitor.assessPoolHealth();
expect(health.status).toBe('warning');
expect(health.issues).toContain('Connection pool near capacity');
});
test('should detect high pending requests', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 5,
idleConnections: 5,
pendingRequests: 10,
errors: 0
};
const health = monitor.assessPoolHealth();
expect(health.status).toBe('warning');
expect(health.issues).toContain('High number of pending requests');
});
test('should detect critical no active connections', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 0,
idleConnections: 0,
pendingRequests: 5,
errors: 0
};
const health = monitor.assessPoolHealth();
expect(health.status).toBe('critical');
expect(health.issues).toContain('No active connections available');
});
test('should calculate health score correctly', () => {
monitor.metrics.poolStats = {
totalConnections: 10,
activeConnections: 10, // 100% utilization (>= 95% threshold)
idleConnections: 0,
pendingRequests: 8, // High pending requests
errors: 3 // Some errors
};
const health = monitor.assessPoolHealth();
// Calculate the actual score with 95% threshold:
// Starting at 100
// - 20 * 2 (issues: 'Connection pool near capacity' + 'High number of pending requests') = -40
// - 10 (utilization > 0.8) = -10
// - 10 (utilization > 0.9) = -10
// - min(3 * 2, 30) = -6 (errors)
// Total: 100 - 40 - 10 - 10 - 6 = 34
expect(health.score).toBe(34);
});
});
describe('Pool Statistics', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
// Add connection events
Date.now.mockReturnValue(1000);
monitor.recordConnectionEvent('connect');
Date.now.mockReturnValue(2000);
monitor.recordConnectionEvent('error', { message: 'Connection timeout' });
Date.now.mockReturnValue(3000);
monitor.recordConnectionEvent('retry');
Date.now.mockReturnValue(700000); // 10+ minutes later for filtering
});
test('should get pool statistics', () => {
const poolStats = monitor.getPoolStats();
expect(poolStats.enabled).toBe(true);
expect(poolStats.current).toBeDefined();
expect(poolStats.recent).toEqual({
connectionRate: 0, // Events are from too long ago (700000 - 3000 > 10 minutes)
errorRate: 0,
retryRate: 0,
totalEvents: 0
});
expect(poolStats.health).toBeDefined();
});
test('should filter old connection events', () => {
// All events are older than 10 minutes, so should be filtered out
const poolStats = monitor.getPoolStats();
expect(poolStats.recent.totalEvents).toBe(0);
expect(poolStats.recent.connectionRate).toBe(0);
});
test('should return disabled status when tracking disabled', () => {
monitor = new PerformanceMonitor({ trackPoolMetrics: false });
const poolStats = monitor.getPoolStats();
expect(poolStats).toEqual({ enabled: false });
});
test('should return disabled status when monitoring disabled', () => {
monitor = new PerformanceMonitor({ enabled: false });
const poolStats = monitor.getPoolStats();
expect(poolStats).toEqual({ enabled: false });
});
});
describe('Memory Management', () => {
beforeEach(() => {
monitor = new PerformanceMonitor({ maxMetricsHistory: 5 });
});
test('should trim metrics history when limit exceeded', () => {
// Add 7 queries (exceeds limit of 5)
for (let i = 0; i < 7; i++) {
Date.now.mockReturnValue(1000 + i * 100);
const queryId = monitor.startQuery('execute_query', `SELECT ${i}`);
Date.now.mockReturnValue(1000 + i * 100 + 50);
monitor.endQuery(queryId, { recordset: [] });
}
expect(monitor.metrics.queries.length).toBeLessThanOrEqual(5);
});
test('should preserve slow queries when trimming', () => {
monitor = new PerformanceMonitor({
maxMetricsHistory: 3,
slowQueryThreshold: 100
});
// Add regular queries
for (let i = 0; i < 3; i++) {
Date.now.mockReturnValue(1000 + i * 100);
const queryId = monitor.startQuery('execute_query', `SELECT ${i}`);
Date.now.mockReturnValue(1000 + i * 100 + 50); // 50ms - fast
monitor.endQuery(queryId, { recordset: [] });
}
// Add slow query
Date.now.mockReturnValue(2000);
const slowQueryId = monitor.startQuery('execute_query', 'SLOW SELECT');
Date.now.mockReturnValue(2500); // 500ms - slow
monitor.endQuery(slowQueryId, { recordset: [] });
// Add more regular queries to trigger trimming
for (let i = 0; i < 2; i++) {
Date.now.mockReturnValue(3000 + i * 100);
const queryId = monitor.startQuery('execute_query', `SELECT fast ${i}`);
Date.now.mockReturnValue(3000 + i * 100 + 50); // 50ms - fast
monitor.endQuery(queryId, { recordset: [] });
}
expect(monitor.metrics.queries.length).toBeLessThanOrEqual(3);
// Should preserve the slow query
const slowQuery = monitor.metrics.queries.find(q => q.query === 'SLOW SELECT');
expect(slowQuery).toBeDefined();
});
test('should trim connection history when limit exceeded', () => {
// Add connection events exceeding limit
for (let i = 0; i < 7; i++) {
monitor.recordConnectionEvent('connect', { attempt: i });
}
expect(monitor.metrics.connections.length).toBeLessThanOrEqual(5);
});
});
describe('Memory Usage Tracking', () => {
test('should get memory usage when process is available', () => {
const memory = monitor.getMemoryUsage();
expect(memory).toBe(100); // Mock returns 100MB
});
test('should return 0 when process is not available', () => {
global.process = undefined;
monitor = new PerformanceMonitor();
const memory = monitor.getMemoryUsage();
expect(memory).toBe(0);
});
});
describe('Configuration Management', () => {
beforeEach(() => {
monitor = new PerformanceMonitor({ enabled: true, slowQueryThreshold: 1000 });
});
test('should update configuration', () => {
const newConfig = {
slowQueryThreshold: 2000,
samplingRate: 0.5,
newOption: 'test'
};
monitor.updateConfig(newConfig);
expect(monitor.config).toEqual({
enabled: true,
maxMetricsHistory: 1000,
slowQueryThreshold: 2000,
trackPoolMetrics: true,
samplingRate: 0.5,
newOption: 'test'
});
});
test('should get current configuration', () => {
const config = monitor.getConfig();
expect(config).toEqual(monitor.config);
expect(config).not.toBe(monitor.config); // Should return a copy
});
});
describe('Reset Functionality', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
// Add some data
const queryId = monitor.startQuery('execute_query', 'SELECT 1');
monitor.endQuery(queryId, { recordset: [] });
monitor.recordConnectionEvent('connect');
});
test('should reset all metrics', () => {
Date.now.mockReturnValue(5000); // New start time
monitor.reset();
expect(monitor.metrics.queries).toHaveLength(0);
expect(monitor.metrics.connections).toHaveLength(0);
expect(monitor.metrics.poolStats).toEqual({
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
pendingRequests: 0,
errors: 0
});
expect(monitor.metrics.aggregates).toEqual({
totalQueries: 0,
slowQueries: 0,
avgQueryTime: 0,
maxQueryTime: 0,
minQueryTime: Number.MAX_SAFE_INTEGER,
totalQueryTime: 0,
errorRate: 0
});
expect(monitor.startTime).toBe(5000);
});
});
describe('Report Generation', () => {
beforeEach(() => {
monitor = new PerformanceMonitor({ slowQueryThreshold: 1000 });
// Add test data
Date.now.mockReturnValue(2000);
const queryId = monitor.startQuery('execute_query', 'SELECT * FROM users');
Date.now.mockReturnValue(4000); // 2000ms duration (slow)
monitor.endQuery(queryId, { recordset: [1, 2, 3] });
monitor.recordPoolMetrics({
totalConnections: 10,
activeConnections: 8,
errors: 2
});
});
test('should generate comprehensive report', () => {
const report = monitor.generateReport();
expect(report).toEqual({
timestamp: 4000,
uptime: 3000,
summary: {
totalQueries: 1,
avgQueryTime: 2000,
slowQueries: 1,
errorRate: 0,
poolHealth: 'healthy' // Pool health calculation is different
},
detailed: {
overall: monitor.metrics.aggregates,
recent: expect.any(Object),
pool: expect.any(Object),
queries: expect.any(Object)
},
recommendations: expect.arrayContaining([
expect.objectContaining({
type: 'performance',
priority: 'high',
message: 'Average query time is high. Consider query optimization or indexing.'
})
])
});
});
test('should generate performance recommendations', () => {
// Test high error rate
Date.now.mockReturnValue(3000);
const errorQuery = monitor.startQuery('execute_query', 'INVALID');
monitor.endQuery(errorQuery, {}, new Error('SQL Error'));
const report = monitor.generateReport();
expect(report.recommendations).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'performance',
priority: 'high',
message: 'Average query time is high. Consider query optimization or indexing.'
})
])
);
// Pool health might not be warning if error count is low
});
test('should generate tool-specific recommendations', () => {
// Add a slow tool
Date.now.mockReturnValue(5000);
const slowToolQuery = monitor.startQuery('export_table_csv', 'SELECT * FROM huge_table');
Date.now.mockReturnValue(8000); // 3000ms duration
monitor.endQuery(slowToolQuery, { recordset: [], streaming: true });
const report = monitor.generateReport();
expect(report.recommendations).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'optimization',
priority: 'medium',
message: "Tool 'export_table_csv' has high average execution time.",
tool: 'export_table_csv'
})
])
);
});
});
describe('Edge Cases and Error Handling', () => {
beforeEach(() => {
monitor = new PerformanceMonitor();
});
test('should handle empty query statistics calculation', () => {
const stats = monitor.calculateQueryStats([]);
expect(stats).toEqual({
count: 0,
avgDuration: 0,
maxDuration: 0,
minDuration: 0,
errorRate: 0,
slowQueryRate: 0
});
});
test('should handle queries without duration', () => {
const queries = [
{ duration: null, status: 'error' },
{ duration: undefined, status: 'completed' }
];
const stats = monitor.calculateQueryStats(queries);
expect(stats.count).toBe(2);
expect(stats.avgDuration).toBe(0);
expect(stats.errorRate).toBe(50);
});
test('should generate unique query IDs', () => {
Math.random.mockReturnValueOnce(0.123).mockReturnValueOnce(0.456).mockReturnValueOnce(0.789);
const id1 = monitor.generateQueryId();
const id2 = monitor.generateQueryId();
expect(id1).not.toBe(id2);
expect(id1).toMatch(/^q_\d+_[a-z0-9]+$/);
});
test('should generate unique event IDs', () => {
Math.random.mockReturnValueOnce(0.123).mockReturnValueOnce(0.456);
const id1 = monitor.generateEventId();
const id2 = monitor.generateEventId();
expect(id1).not.toBe(id2);
expect(id1).toMatch(/^e_\d+_[a-z0-9]+$/);
});
});
});