Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
monitoring.test.ts18.9 kB
/** * Unit tests for monitoring.ts * Tests monitoring, analytics, and health check functionality */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { MonitoringManager, MetricType, MetricCategory, monitoring, trackOperation, } from '../../src/utils/monitoring.js'; describe('Monitoring and Analytics', () => { let monitor: MonitoringManager; beforeEach(() => { monitor = new MonitoringManager({ enabled: true, metricsRetentionMs: 60000, aggregationIntervalMs: 10000, maxMetricsInMemory: 1000, enableHealthChecks: false, // Disable for tests }); }); afterEach(() => { monitor.stop(); monitor.reset(); }); describe('Counter Metrics', () => { it('should record counter metric', () => { monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1); const metrics = monitor.getMetrics(); expect(metrics).toHaveLength(1); expect(metrics[0].name).toBe('test.counter'); expect(metrics[0].type).toBe(MetricType.COUNTER); expect(metrics[0].value).toBe(1); }); it('should record multiple counter increments', () => { monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1); monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1); monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1); const metrics = monitor.getMetrics(); expect(metrics).toHaveLength(3); }); it('should record counter with tags', () => { monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1, { endpoint: '/api/test', method: 'GET', }); const metrics = monitor.getMetrics(); expect(metrics[0].tags).toEqual({ endpoint: '/api/test', method: 'GET', }); }); it('should record counter with metadata', () => { monitor.recordCounter('test.counter', MetricCategory.REQUEST, 1, undefined, { userId: '123', sessionId: 'abc', }); const metrics = monitor.getMetrics(); expect(metrics[0].metadata).toEqual({ userId: '123', sessionId: 'abc', }); }); }); describe('Gauge Metrics', () => { it('should record gauge metric', () => { monitor.recordGauge('memory.usage', MetricCategory.SYSTEM, 1024); const metrics = monitor.getMetrics(); expect(metrics).toHaveLength(1); expect(metrics[0].name).toBe('memory.usage'); expect(metrics[0].type).toBe(MetricType.GAUGE); expect(metrics[0].value).toBe(1024); }); it('should update gauge value', () => { monitor.recordGauge('cpu.usage', MetricCategory.SYSTEM, 50); monitor.recordGauge('cpu.usage', MetricCategory.SYSTEM, 75); const metrics = monitor.getMetrics(); expect(metrics).toHaveLength(2); expect(metrics[0].value).toBe(50); expect(metrics[1].value).toBe(75); }); }); describe('Histogram Metrics', () => { it('should record histogram values', () => { monitor.recordHistogram('response.time', MetricCategory.PERFORMANCE, 100); monitor.recordHistogram('response.time', MetricCategory.PERFORMANCE, 150); monitor.recordHistogram('response.time', MetricCategory.PERFORMANCE, 200); const metrics = monitor.getMetricsByCategory(MetricCategory.PERFORMANCE); expect(metrics).toHaveLength(3); }); it('should calculate histogram statistics', () => { const values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; values.forEach(v => { monitor.recordHistogram('test.histogram', MetricCategory.PERFORMANCE, v); }); const stats = monitor.getMetricStats('test.histogram', MetricCategory.PERFORMANCE); expect(stats).not.toBeNull(); expect(stats!.count).toBe(10); expect(stats!.min).toBe(10); expect(stats!.max).toBe(100); expect(stats!.avg).toBe(55); }); }); describe('Request Tracking', () => { it('should track request lifecycle', () => { const requestId = 'req-123'; monitor.startRequest(requestId, 'tool', 'test-tool'); monitor.completeRequest(requestId); const requests = monitor.getRecentRequests(10); expect(requests).toHaveLength(1); expect(requests[0].requestId).toBe(requestId); expect(requests[0].status).toBe('success'); expect(requests[0].duration).toBeGreaterThanOrEqual(0); }); it('should track failed requests', () => { const requestId = 'req-456'; monitor.startRequest(requestId, 'tool', 'test-tool'); monitor.failRequest(requestId, 'Connection timeout'); const requests = monitor.getRecentRequests(10); expect(requests[0].status).toBe('error'); expect(requests[0].error).toBe('Connection timeout'); }); it('should track request metadata', () => { const requestId = 'req-789'; monitor.startRequest(requestId, 'resource', 'test-resource', { cacheKey: 'test:key', }); monitor.completeRequest(requestId, { resultSize: 1024 }); const requests = monitor.getRecentRequests(10); expect(requests[0].metadata).toEqual({ cacheKey: 'test:key', resultSize: 1024, }); }); it('should record request metrics', () => { const requestId = 'req-metrics'; monitor.startRequest(requestId, 'tool', 'analytics-tool'); monitor.completeRequest(requestId); const metrics = monitor.getMetrics(); const startedMetrics = metrics.filter(m => m.name === 'request.started'); const completedMetrics = metrics.filter(m => m.name === 'request.completed'); const durationMetrics = metrics.filter(m => m.name === 'request.duration'); expect(startedMetrics).toHaveLength(1); expect(completedMetrics).toHaveLength(1); expect(durationMetrics).toHaveLength(1); }); }); describe('Cache Metrics', () => { it('should record cache hits', () => { monitor.recordCacheHit('resource:123'); monitor.recordCacheHit('resource:456'); const metrics = monitor.getMetrics(); const cacheHits = metrics.filter(m => m.name === 'cache.hit'); expect(cacheHits).toHaveLength(2); }); it('should record cache misses', () => { monitor.recordCacheMiss('resource:789'); const metrics = monitor.getMetrics(); const cacheMisses = metrics.filter(m => m.name === 'cache.miss'); expect(cacheMisses).toHaveLength(1); }); it('should include cache metrics in performance snapshot', () => { monitor.recordCacheHit('key1'); monitor.recordCacheHit('key2'); monitor.recordCacheHit('key3'); monitor.recordCacheMiss('key4'); const snapshot = monitor.getPerformanceSnapshot(); expect(snapshot.cache.totalHits).toBe(3); expect(snapshot.cache.totalMisses).toBe(1); expect(snapshot.cache.hitRate).toBeCloseTo(0.75, 2); }); }); describe('Health Checks', () => { it('should run health check successfully', async () => { const healthCheck = await monitor.runHealthCheck('test-service', async () => ({ status: 'healthy', message: 'Service is operational', })); expect(healthCheck.name).toBe('test-service'); expect(healthCheck.status).toBe('healthy'); expect(healthCheck.message).toBe('Service is operational'); expect(healthCheck.duration).toBeGreaterThanOrEqual(0); }); it('should detect unhealthy service', async () => { const healthCheck = await monitor.runHealthCheck('failing-service', async () => ({ status: 'unhealthy', message: 'Service unavailable', })); expect(healthCheck.status).toBe('unhealthy'); expect(healthCheck.message).toBe('Service unavailable'); }); it('should handle health check errors', async () => { const healthCheck = await monitor.runHealthCheck('error-service', async () => { throw new Error('Health check failed'); }); expect(healthCheck.status).toBe('unhealthy'); expect(healthCheck.message).toBe('Health check failed'); }); it('should aggregate health status', async () => { await monitor.runHealthCheck('service1', async () => ({ status: 'healthy' })); await monitor.runHealthCheck('service2', async () => ({ status: 'healthy' })); const status = monitor.getHealthStatus(); expect(status).toBe('healthy'); }); it('should detect degraded status', async () => { await monitor.runHealthCheck('service1', async () => ({ status: 'healthy' })); await monitor.runHealthCheck('service2', async () => ({ status: 'degraded' })); const status = monitor.getHealthStatus(); expect(status).toBe('degraded'); }); it('should detect unhealthy status', async () => { await monitor.runHealthCheck('service1', async () => ({ status: 'healthy' })); await monitor.runHealthCheck('service2', async () => ({ status: 'unhealthy' })); const status = monitor.getHealthStatus(); expect(status).toBe('unhealthy'); }); }); describe('Performance Snapshot', () => { it('should generate performance snapshot', () => { monitor.startRequest('req1', 'tool', 'tool1'); monitor.completeRequest('req1'); monitor.startRequest('req2', 'resource', 'resource1'); monitor.failRequest('req2', 'Error'); const snapshot = monitor.getPerformanceSnapshot(); expect(snapshot.requests.total).toBe(2); expect(snapshot.requests.success).toBe(1); expect(snapshot.requests.error).toBe(1); expect(snapshot.requests.pending).toBe(0); }); it('should calculate latency percentiles', async () => { // Use smaller delays to speed up test (1-5ms range) const durations = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]; for (let i = 0; i < durations.length; i++) { const duration = durations[i]; const id = `req-${i}`; monitor.startRequest(id, 'tool', 'perf-tool'); // Simulate actual duration with small delay await new Promise(resolve => setTimeout(resolve, duration)); monitor.completeRequest(id); } const snapshot = monitor.getPerformanceSnapshot(); expect(snapshot.latency.p50).toBeGreaterThan(0); expect(snapshot.latency.p95).toBeGreaterThan(0); expect(snapshot.latency.p99).toBeGreaterThan(0); expect(snapshot.latency.avg).toBeGreaterThan(0); }); it('should calculate throughput', async () => { // Create some requests for (let i = 0; i < 10; i++) { const id = `req-${i}`; monitor.startRequest(id, 'tool', 'throughput-test'); monitor.completeRequest(id); } const snapshot = monitor.getPerformanceSnapshot(); expect(snapshot.throughput.requestsPerMinute).toBe(10); expect(snapshot.throughput.requestsPerSecond).toBeCloseTo(10 / 60, 2); }); it('should track errors by type', () => { monitor.startRequest('req1', 'tool', 'tool1'); monitor.failRequest('req1', 'TimeoutError'); monitor.startRequest('req2', 'tool', 'tool2'); monitor.failRequest('req2', 'ValidationError'); monitor.startRequest('req3', 'tool', 'tool3'); monitor.failRequest('req3', 'TimeoutError'); const snapshot = monitor.getPerformanceSnapshot(); expect(snapshot.errors.total).toBe(3); expect(snapshot.errors.byType.TimeoutError).toBe(2); expect(snapshot.errors.byType.ValidationError).toBe(1); }); }); describe('Metrics Management', () => { it('should get metrics by category', () => { monitor.recordCounter('test1', MetricCategory.REQUEST, 1); monitor.recordCounter('test2', MetricCategory.CACHE, 1); monitor.recordCounter('test3', MetricCategory.REQUEST, 1); const requestMetrics = monitor.getMetricsByCategory(MetricCategory.REQUEST); expect(requestMetrics).toHaveLength(2); const cacheMetrics = monitor.getMetricsByCategory(MetricCategory.CACHE); expect(cacheMetrics).toHaveLength(1); }); it('should enforce max metrics limit', () => { const smallMonitor = new MonitoringManager({ maxMetricsInMemory: 10, }); for (let i = 0; i < 20; i++) { smallMonitor.recordCounter('test', MetricCategory.REQUEST, 1); } const metrics = smallMonitor.getMetrics(); expect(metrics).toHaveLength(10); smallMonitor.stop(); }); it('should cleanup old metrics', async () => { const shortRetention = new MonitoringManager({ metricsRetentionMs: 100, // 100ms retention }); shortRetention.recordCounter('old', MetricCategory.REQUEST, 1); // Wait for metrics to expire await new Promise(resolve => setTimeout(resolve, 150)); const removed = shortRetention.cleanup(); expect(removed).toBeGreaterThan(0); const metrics = shortRetention.getMetrics(); expect(metrics).toHaveLength(0); shortRetention.stop(); }); it('should reset all metrics', () => { monitor.recordCounter('test1', MetricCategory.REQUEST, 1); monitor.startRequest('req1', 'tool', 'tool1'); monitor.reset(); expect(monitor.getMetrics()).toHaveLength(0); expect(monitor.getRecentRequests()).toHaveLength(0); }); }); describe('Metric Statistics', () => { it('should calculate metric statistics', () => { const values = [10, 20, 30, 40, 50]; values.forEach(v => { monitor.recordHistogram('test.metric', MetricCategory.PERFORMANCE, v); }); const stats = monitor.getMetricStats('test.metric', MetricCategory.PERFORMANCE); expect(stats).not.toBeNull(); expect(stats!.count).toBe(5); expect(stats!.sum).toBe(150); expect(stats!.min).toBe(10); expect(stats!.max).toBe(50); expect(stats!.avg).toBe(30); }); it('should return null for non-existent metrics', () => { const stats = monitor.getMetricStats('non.existent', MetricCategory.PERFORMANCE); expect(stats).toBeNull(); }); it('should calculate percentiles correctly', () => { const values = Array.from({ length: 100 }, (_, i) => i + 1); // 1-100 values.forEach(v => { monitor.recordHistogram('percentile.test', MetricCategory.PERFORMANCE, v); }); const stats = monitor.getMetricStats('percentile.test', MetricCategory.PERFORMANCE); expect(stats).not.toBeNull(); expect(stats!.p50).toBeCloseTo(50, 0); expect(stats!.p95).toBeCloseTo(95, 0); expect(stats!.p99).toBeCloseTo(99, 0); }); }); describe('Recent Requests', () => { it('should return recent requests sorted by time', async () => { monitor.startRequest('req1', 'tool', 'tool1'); await new Promise(resolve => setTimeout(resolve, 2)); monitor.startRequest('req2', 'tool', 'tool2'); await new Promise(resolve => setTimeout(resolve, 2)); monitor.startRequest('req3', 'tool', 'tool3'); const recent = monitor.getRecentRequests(10); expect(recent).toHaveLength(3); // Most recent should be first expect(recent[0].requestId).toBe('req3'); expect(recent[1].requestId).toBe('req2'); expect(recent[2].requestId).toBe('req1'); }); it('should limit recent requests', () => { for (let i = 0; i < 20; i++) { monitor.startRequest(`req-${i}`, 'tool', 'tool'); } const recent = monitor.getRecentRequests(5); expect(recent).toHaveLength(5); }); }); describe('Uptime Tracking', () => { it('should track uptime', async () => { await new Promise(resolve => setTimeout(resolve, 10)); const uptime = monitor.getUptime(); expect(uptime).toBeGreaterThanOrEqual(10); }); }); describe('Metrics Export', () => { it('should export metrics to JSON', () => { monitor.recordCounter('test', MetricCategory.REQUEST, 1); monitor.startRequest('req1', 'tool', 'tool1'); const exported = monitor.exportMetrics(); const data = JSON.parse(exported); expect(data).toHaveProperty('metrics'); expect(data).toHaveProperty('requests'); expect(data).toHaveProperty('healthChecks'); expect(data).toHaveProperty('performance'); expect(data).toHaveProperty('uptime'); expect(data).toHaveProperty('timestamp'); }); it('should export valid JSON structure', () => { monitor.recordCounter('counter', MetricCategory.REQUEST, 5); monitor.recordGauge('gauge', MetricCategory.SYSTEM, 100); const exported = monitor.exportMetrics(); expect(() => JSON.parse(exported)).not.toThrow(); const data = JSON.parse(exported); expect(Array.isArray(data.metrics)).toBe(true); expect(Array.isArray(data.requests)).toBe(true); }); }); describe('Disabled Monitoring', () => { it('should not record metrics when disabled', () => { const disabled = new MonitoringManager({ enabled: false }); disabled.recordCounter('test', MetricCategory.REQUEST, 1); disabled.startRequest('req1', 'tool', 'tool1'); expect(disabled.getMetrics()).toHaveLength(0); expect(disabled.getRecentRequests()).toHaveLength(0); disabled.stop(); }); }); describe('Track Operation Helper', () => { it('should track successful operation', async () => { const result = await trackOperation( async () => { await new Promise(resolve => setTimeout(resolve, 10)); return 'success'; }, 'test.operation', MetricCategory.PERFORMANCE ); expect(result).toBe('success'); const metrics = monitoring.getMetrics(); const successMetrics = metrics.filter(m => m.name === 'test.operation.success'); const durationMetrics = metrics.filter(m => m.name === 'test.operation.duration'); expect(successMetrics.length).toBeGreaterThan(0); expect(durationMetrics.length).toBeGreaterThan(0); }); it('should track failed operation', async () => { await expect( trackOperation( async () => { throw new Error('Operation failed'); }, 'test.failing', MetricCategory.PERFORMANCE ) ).rejects.toThrow('Operation failed'); const metrics = monitoring.getMetrics(); const errorMetrics = metrics.filter(m => m.name === 'test.failing.error'); expect(errorMetrics.length).toBeGreaterThan(0); }); it('should track operation with tags', async () => { await trackOperation(async () => 'done', 'tagged.operation', MetricCategory.TOOL, { environment: 'test', }); const metrics = monitoring.getMetrics(); const taggedMetrics = metrics.filter( m => m.name === 'tagged.operation.success' && m.tags?.environment === 'test' ); expect(taggedMetrics.length).toBeGreaterThan(0); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/mcp-adr-analysis-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server