/** * Unit tests for src/metrics/registry.ts * * Verifies that all Prometheus metrics are registered on the shared * metricsRegistry (not the default global registry), have the correct * names, and carry the correct label names. */ import { metricsRegistry, tokensIssuedTotal, agentsRegisteredTotal, httpRequestsTotal, httpRequestDurationSeconds, dbQueryDurationSeconds, redisCommandDurationSeconds, credentialsExpiringSoonTotal, auditChainIntegrity, rateLimitHitsTotal, dbPoolActiveConnections, dbPoolWaitingRequests, } from '../../../src/metrics/registry'; describe('metricsRegistry', () => { // ────────────────────────────────────────────────────────────────── // Registry isolation // ────────────────────────────────────────────────────────────────── it('uses a non-default registry instance', async () => { // prom-client default registry is accessed via Registry.default or // by calling register.metrics(). The shared registry must NOT be // the same reference as the default one. const { register } = await import('prom-client'); expect(metricsRegistry).not.toBe(register); }); it('contains exactly 12 metric entries', async () => { const entries = await metricsRegistry.getMetricsAsJSON(); expect(entries).toHaveLength(12); }); // ────────────────────────────────────────────────────────────────── // Metric names // ────────────────────────────────────────────────────────────────── it.each([ 'agentidp_tokens_issued_total', 'agentidp_agents_registered_total', 'agentidp_http_requests_total', 'agentidp_http_request_duration_seconds', 'agentidp_db_query_duration_seconds', 'agentidp_redis_command_duration_seconds', 'agentidp_webhook_dead_letters_total', 'agentidp_credentials_expiring_soon_total', 'agentidp_audit_chain_integrity', 'agentidp_rate_limit_hits_total', 'agentidp_db_pool_active_connections', 'agentidp_db_pool_waiting_requests', ])('registers metric "%s"', async (metricName) => { const entries = await metricsRegistry.getMetricsAsJSON(); const names = entries.map((e) => e.name); expect(names).toContain(metricName); }); // ────────────────────────────────────────────────────────────────── // Label names per metric // ────────────────────────────────────────────────────────────────── describe('tokensIssuedTotal', () => { it('has name agentidp_tokens_issued_total', () => { // Access the internal name via the metric object const metric = tokensIssuedTotal as unknown as { name: string }; expect(metric.name).toBe('agentidp_tokens_issued_total'); }); it('has label "scope"', async () => { const entries = await metricsRegistry.getMetricsAsJSON(); const entry = entries.find((e) => e.name === 'agentidp_tokens_issued_total'); expect(entry).toBeDefined(); // Counter with no observations has an empty values array but the metric exists expect(entry!.type).toBe('counter'); }); }); describe('agentsRegisteredTotal', () => { it('has name agentidp_agents_registered_total', () => { const metric = agentsRegisteredTotal as unknown as { name: string }; expect(metric.name).toBe('agentidp_agents_registered_total'); }); }); describe('httpRequestsTotal', () => { it('has name agentidp_http_requests_total', () => { const metric = httpRequestsTotal as unknown as { name: string }; expect(metric.name).toBe('agentidp_http_requests_total'); }); it('increments with method, route, status_code labels without throwing', () => { expect(() => httpRequestsTotal.inc({ method: 'GET', route: '/test', status_code: '200' }), ).not.toThrow(); }); }); describe('httpRequestDurationSeconds', () => { it('has name agentidp_http_request_duration_seconds', () => { const metric = httpRequestDurationSeconds as unknown as { name: string }; expect(metric.name).toBe('agentidp_http_request_duration_seconds'); }); it('observes with method, route, status_code labels without throwing', () => { expect(() => httpRequestDurationSeconds.observe({ method: 'GET', route: '/test', status_code: '200' }, 0.05), ).not.toThrow(); }); }); describe('dbQueryDurationSeconds', () => { it('has name agentidp_db_query_duration_seconds', () => { const metric = dbQueryDurationSeconds as unknown as { name: string }; expect(metric.name).toBe('agentidp_db_query_duration_seconds'); }); it('observes with operation label without throwing', () => { expect(() => dbQueryDurationSeconds.observe({ operation: 'query' }, 0.002), ).not.toThrow(); }); }); describe('redisCommandDurationSeconds', () => { it('has name agentidp_redis_command_duration_seconds', () => { const metric = redisCommandDurationSeconds as unknown as { name: string }; expect(metric.name).toBe('agentidp_redis_command_duration_seconds'); }); it('observes with command label without throwing', () => { expect(() => redisCommandDurationSeconds.observe({ command: 'get' }, 0.001), ).not.toThrow(); }); }); describe('credentialsExpiringSoonTotal', () => { it('has name agentidp_credentials_expiring_soon_total', () => { const metric = credentialsExpiringSoonTotal as unknown as { name: string }; expect(metric.name).toBe('agentidp_credentials_expiring_soon_total'); }); it('increments with agent_id label without throwing', () => { expect(() => credentialsExpiringSoonTotal.inc({ agent_id: 'agent-test-001' }), ).not.toThrow(); }); }); describe('auditChainIntegrity', () => { it('has name agentidp_audit_chain_integrity', () => { const metric = auditChainIntegrity as unknown as { name: string }; expect(metric.name).toBe('agentidp_audit_chain_integrity'); }); it('can be set to 1 (passing) without throwing', () => { expect(() => auditChainIntegrity.set(1)).not.toThrow(); }); it('can be set to 0 (failing) without throwing', () => { expect(() => auditChainIntegrity.set(0)).not.toThrow(); }); }); describe('rateLimitHitsTotal', () => { it('has name agentidp_rate_limit_hits_total', () => { const metric = rateLimitHitsTotal as unknown as { name: string }; expect(metric.name).toBe('agentidp_rate_limit_hits_total'); }); it('increments with endpoint label without throwing', () => { expect(() => rateLimitHitsTotal.inc({ endpoint: '/api/v1/agents' }), ).not.toThrow(); }); }); describe('dbPoolActiveConnections', () => { it('has name agentidp_db_pool_active_connections', () => { const metric = dbPoolActiveConnections as unknown as { name: string }; expect(metric.name).toBe('agentidp_db_pool_active_connections'); }); it('can be set without throwing', () => { expect(() => dbPoolActiveConnections.set(5)).not.toThrow(); }); }); describe('dbPoolWaitingRequests', () => { it('has name agentidp_db_pool_waiting_requests', () => { const metric = dbPoolWaitingRequests as unknown as { name: string }; expect(metric.name).toBe('agentidp_db_pool_waiting_requests'); }); it('can be set without throwing', () => { expect(() => dbPoolWaitingRequests.set(2)).not.toThrow(); }); }); });