nexstream-api / backend /tests /api /metrics.test.ts
nexstream-deploy
deploy: nexstream backend (experiment)
2276d59
Raw
History Blame Contribute Delete
3.37 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import request from 'supertest';
import app from '../../src/app.js';
import {
recordRequest,
recordFailure,
getMetrics,
resetMetrics,
} from '../../src/utils/infra/metrics.util.js';
const REMOTE = '203.0.113.5';
describe('metrics module', () => {
beforeEach(() => {
resetMetrics();
});
it('aggregates count, average, and max latency per route', () => {
recordRequest('GET /info', 200, 100);
recordRequest('GET /info', 200, 300);
const { routes } = getMetrics();
expect(routes['GET /info'].count).toBe(2);
expect(routes['GET /info'].avgMs).toBe(200);
expect(routes['GET /info'].maxMs).toBe(300);
expect(routes['GET /info'].errors).toBe(0);
});
it('counts 5xx responses as errors', () => {
recordRequest('GET /info', 500, 10);
recordRequest('GET /info', 200, 10);
const { routes, totalErrors } = getMetrics();
expect(routes['GET /info'].errors).toBe(1);
expect(totalErrors).toBe(1);
});
it('tallies failure reasons', () => {
recordFailure('TimeoutError');
recordFailure('TimeoutError');
recordFailure('SsrfError');
expect(getMetrics().failures).toEqual({ TimeoutError: 2, SsrfError: 1 });
});
it('folds overflow labels into "other" to bound memory', () => {
for (let idx = 0; idx < 250; idx += 1) {
recordFailure(`reason-${idx}`);
}
const { failures } = getMetrics();
expect(Object.keys(failures).length).toBe(201);
expect(failures.other).toBe(50);
});
it('reset clears all counters', () => {
recordRequest('GET /info', 200, 10);
recordFailure('X');
resetMetrics();
const snap = getMetrics();
expect(snap.totalRequests).toBe(0);
expect(snap.failures).toEqual({});
});
});
describe('GET /metrics endpoint', () => {
beforeEach(() => {
resetMetrics();
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('returns the snapshot shape to localhost', async () => {
recordRequest('GET /seed', 200, 42);
const res = await request(app).get('/metrics');
expect(res.status).toBe(200);
expect(res.body).toMatchObject({
uptimeSec: expect.any(Number),
totalRequests: expect.any(Number),
totalErrors: expect.any(Number),
});
expect(res.body.routes['GET /seed'].count).toBe(1);
});
it('records real traffic through the middleware', async () => {
await request(app).get('/ping');
const res = await request(app).get('/metrics');
expect(res.body.routes['GET /ping']?.count).toBeGreaterThanOrEqual(1);
});
it('forbids remote access when no API_KEY is set', async () => {
vi.stubEnv('API_KEY', '');
const res = await request(app)
.get('/metrics')
.set('X-Forwarded-For', REMOTE);
expect(res.status).toBe(403);
});
it('allows remote access with the correct key', async () => {
vi.stubEnv('API_KEY', 'secret');
const res = await request(app)
.get('/metrics')
.set('X-Forwarded-For', REMOTE)
.set('x-api-key', 'secret');
expect(res.status).toBe(200);
});
it('forbids remote access with a wrong key', async () => {
vi.stubEnv('API_KEY', 'secret');
const res = await request(app)
.get('/metrics')
.set('X-Forwarded-For', REMOTE)
.set('x-api-key', 'nope');
expect(res.status).toBe(403);
});
});