import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { FastifyInstance } from 'fastify'; import { buildApp } from '../src/app.js'; import { loadConfig } from '../src/config.js'; describe('Prometheus Metrics', () => { let app: FastifyInstance; beforeAll(async () => { const config = loadConfig({ NODE_ENV: 'test', LOG_LEVEL: 'fatal', JWT_SECRET: 'test-secret-with-exactly-32chars', FEATURE_MESSAGING_ENABLED: 'false', // Disable WebSocket for metrics test }); app = await buildApp({ config }); }); afterAll(async () => { await app.close(); }); it('should expose /metrics endpoint', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.statusCode).toBe(200); expect(response.headers['content-type']).toContain('text/plain'); }); it('should expose agenthub_agents_connected metric', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_agents_connected'); expect(response.body).toContain('# TYPE agenthub_agents_connected gauge'); expect(response.body).toMatch(/agenthub_agents_connected \d+/); }); it('should expose agenthub_rooms_active metric', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_rooms_active'); expect(response.body).toContain('# TYPE agenthub_rooms_active gauge'); expect(response.body).toMatch(/agenthub_rooms_active \d+/); }); it('should expose agenthub_messages_total metric', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_messages_total'); expect(response.body).toContain('# TYPE agenthub_messages_total counter'); expect(response.body).toMatch(/agenthub_messages_total \d+/); }); it('should expose agenthub_websocket_latency_seconds histogram', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_websocket_latency_seconds'); expect(response.body).toContain('# TYPE agenthub_websocket_latency_seconds histogram'); expect(response.body).toContain('agenthub_websocket_latency_seconds_bucket'); expect(response.body).toContain('agenthub_websocket_latency_seconds_sum'); expect(response.body).toContain('agenthub_websocket_latency_seconds_count'); }); it('should expose agenthub_http_requests_total metric', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_http_requests_total'); expect(response.body).toContain('# TYPE agenthub_http_requests_total counter'); // Should have tracked the /metrics request itself expect(response.body).toMatch(/agenthub_http_requests_total\{.*route="\/metrics".*\}/); }); it('should expose agenthub_http_request_duration_seconds histogram', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); expect(response.body).toContain('# HELP agenthub_http_request_duration_seconds'); expect(response.body).toContain('# TYPE agenthub_http_request_duration_seconds histogram'); expect(response.body).toContain('agenthub_http_request_duration_seconds_bucket'); }); it('should expose default Node.js metrics', async () => { const response = await app.inject({ method: 'GET', url: '/metrics', }); // Check for some default metrics expect(response.body).toContain('agenthub_process_cpu_user_seconds_total'); expect(response.body).toContain('agenthub_process_resident_memory_bytes'); expect(response.body).toContain('agenthub_nodejs_eventloop_lag_seconds'); }); it('should increment HTTP request metrics on each call', async () => { // Make first call const response1 = await app.inject({ method: 'GET', url: '/healthz', }); expect(response1.statusCode).toBe(200); // Check metrics const metricsResponse = await app.inject({ method: 'GET', url: '/metrics', }); // Should have at least one request to /healthz expect(metricsResponse.body).toMatch(/agenthub_http_requests_total\{.*route="\/healthz".*status_code="200".*\} \d+/); }); });