feat: implement mcpd core server framework with Fastify
Add Fastify server with config validation (Zod), health/healthz endpoints, auth middleware (Bearer token + session lookup), security plugins (CORS, Helmet, rate limiting), error handler, audit logging, and graceful shutdown. 36 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
71
src/mcpd/tests/health.test.ts
Normal file
71
src/mcpd/tests/health.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import Fastify from 'fastify';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { registerHealthRoutes } from '../src/routes/health.js';
|
||||
|
||||
let app: FastifyInstance;
|
||||
|
||||
afterEach(async () => {
|
||||
if (app) await app.close();
|
||||
});
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns healthy when DB is up', async () => {
|
||||
app = Fastify({ logger: false });
|
||||
registerHealthRoutes(app, { checkDb: async () => true });
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/health' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = res.json<{ status: string; version: string; checks: { database: string } }>();
|
||||
expect(body.status).toBe('healthy');
|
||||
expect(body.version).toBeDefined();
|
||||
expect(body.checks.database).toBe('ok');
|
||||
});
|
||||
|
||||
it('returns degraded when DB is down', async () => {
|
||||
app = Fastify({ logger: false });
|
||||
registerHealthRoutes(app, { checkDb: async () => false });
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/health' });
|
||||
expect(res.statusCode).toBe(503);
|
||||
const body = res.json<{ status: string; checks: { database: string } }>();
|
||||
expect(body.status).toBe('degraded');
|
||||
expect(body.checks.database).toBe('error');
|
||||
});
|
||||
|
||||
it('returns degraded when DB check throws', async () => {
|
||||
app = Fastify({ logger: false });
|
||||
registerHealthRoutes(app, {
|
||||
checkDb: async () => { throw new Error('connection refused'); },
|
||||
});
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/health' });
|
||||
expect(res.statusCode).toBe(503);
|
||||
});
|
||||
|
||||
it('includes uptime and timestamp', async () => {
|
||||
app = Fastify({ logger: false });
|
||||
registerHealthRoutes(app, { checkDb: async () => true });
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/health' });
|
||||
const body = res.json<{ uptime: number; timestamp: string }>();
|
||||
expect(body.uptime).toBeGreaterThan(0);
|
||||
expect(body.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /healthz', () => {
|
||||
it('returns ok (liveness probe)', async () => {
|
||||
app = Fastify({ logger: false });
|
||||
registerHealthRoutes(app, { checkDb: async () => true });
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/healthz' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json<{ status: string }>().status).toBe('ok');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user