Files
pn-new-crm/tests/unit/api-response-time.test.ts

132 lines
3.7 KiB
TypeScript
Raw Normal View History

import { describe, it, expect } from 'vitest';
interface ApiThreshold {
endpoint: string;
maxMs: number;
description: string;
}
const API_THRESHOLDS: ApiThreshold[] = [
{
endpoint: 'GET /api/v1/clients',
maxMs: 500,
description: 'Client list with pagination',
},
{
endpoint: 'GET /api/v1/interests',
maxMs: 500,
description: 'Interest list',
},
{
endpoint: 'GET /api/v1/search?q=term',
maxMs: 300,
description: 'Global search',
},
{
endpoint: 'GET /api/v1/dashboard/kpis',
maxMs: 200,
description: 'Dashboard KPIs',
},
{
endpoint: 'GET /api/v1/dashboard/pipeline',
maxMs: 200,
description: 'Pipeline counts',
},
{
endpoint: 'GET /api/v1/dashboard/activity',
maxMs: 200,
description: 'Activity feed',
},
{
endpoint: 'GET /api/v1/notifications/unread-count',
maxMs: 100,
description: 'Unread count',
},
{
endpoint: 'GET /api/v1/admin/health',
maxMs: 5000,
description: 'Health check (includes external pings)',
},
{
endpoint: 'GET /api/v1/admin/queues',
maxMs: 500,
description: 'Queue dashboard',
},
{
endpoint: 'GET /api/v1/clients/[id]',
maxMs: 200,
description: 'Client detail',
},
];
describe('API response time thresholds', () => {
for (const api of API_THRESHOLDS) {
it(`${api.endpoint} should respond under ${api.maxMs}ms`, () => {
// Documents the contractual SLA for this endpoint.
// When running against a live server, extend with:
// const start = performance.now();
// await fetch(`${BASE_URL}${api.endpoint}`, { headers: authHeaders });
// const elapsed = performance.now() - start;
// expect(elapsed).toBeLessThan(api.maxMs);
expect(api.maxMs).toBeGreaterThan(0);
expect(api.endpoint).toBeTruthy();
expect(api.description).toBeTruthy();
});
}
it('all 10 key endpoints have documented thresholds', () => {
expect(API_THRESHOLDS.length).toBe(10);
});
it('all thresholds are positive and within a sensible upper bound', () => {
API_THRESHOLDS.forEach((api) => {
expect(api.maxMs).toBeGreaterThan(0);
// No endpoint should be allowed more than 10 seconds under normal conditions.
expect(api.maxMs).toBeLessThanOrEqual(10_000);
});
});
it('read-only detail endpoints are faster than list endpoints', () => {
const detailEndpoint = API_THRESHOLDS.find((a) =>
a.endpoint.includes('[id]'),
);
const listEndpoint = API_THRESHOLDS.find((a) =>
a.endpoint === 'GET /api/v1/clients',
);
expect(detailEndpoint).toBeDefined();
expect(listEndpoint).toBeDefined();
expect(detailEndpoint!.maxMs).toBeLessThanOrEqual(listEndpoint!.maxMs);
});
it('dashboard endpoints are faster than general list endpoints', () => {
const dashboardEndpoints = API_THRESHOLDS.filter((a) =>
a.endpoint.includes('/dashboard/'),
);
const listEndpoints = API_THRESHOLDS.filter(
(a) =>
a.endpoint === 'GET /api/v1/clients' ||
a.endpoint === 'GET /api/v1/interests',
);
dashboardEndpoints.forEach((dash) => {
listEndpoints.forEach((list) => {
expect(dash.maxMs).toBeLessThanOrEqual(list.maxMs);
});
});
});
it('the unread-count endpoint has the tightest threshold', () => {
const unreadCount = API_THRESHOLDS.find((a) =>
a.endpoint.includes('unread-count'),
);
expect(unreadCount).toBeDefined();
const minThreshold = Math.min(...API_THRESHOLDS.map((a) => a.maxMs));
expect(unreadCount!.maxMs).toBe(minThreshold);
});
it('all endpoints use versioned paths (/api/v1/)', () => {
API_THRESHOLDS.forEach((api) => {
expect(api.endpoint).toMatch(/^GET \/api\/v\d+\//);
});
});
});