Skip to main content
Glama
health-routes.test.ts19.1 kB
/** * Health Routes Unit Tests * * Tests for the health routes including full health check, readiness probe, * and liveness probe endpoints. * Requirements: 13.1, 13.2, 13.3 */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { healthChecker } from "../../../monitoring/health-checker.js"; import type { CognitiveCore } from "../../../server/cognitive-core.js"; import { createHealthRoutes } from "../../../server/routes/health.js"; // Mock the performance middleware vi.mock("../../../server/middleware/performance.js", () => ({ getPerformanceStats: vi.fn(() => ({ totalRequests: 100, avgDurationMs: 50, p95DurationMs: 150, })), })); // Mock the response cache middleware vi.mock("../../../server/middleware/response-cache.js", () => ({ getResponseCacheMetrics: vi.fn(() => ({ hits: 50, misses: 50, hitRate: 0.5, size: 10, })), })); // Mock cognitive core with all required methods const createMockCognitiveCore = (): CognitiveCore => { return { memoryRepository: {} as CognitiveCore["memoryRepository"], reasoningOrchestrator: {} as CognitiveCore["reasoningOrchestrator"], frameworkSelector: {} as CognitiveCore["frameworkSelector"], confidenceAssessor: {} as CognitiveCore["confidenceAssessor"], biasDetector: {} as CognitiveCore["biasDetector"], emotionAnalyzer: {} as CognitiveCore["emotionAnalyzer"], problemDecomposer: {} as CognitiveCore["problemDecomposer"], memoryAugmentedReasoning: {} as CognitiveCore["memoryAugmentedReasoning"], }; }; /** * Helper to invoke a route handler and capture the response */ async function invokeHandler( router: ReturnType<typeof createHealthRoutes>, path: string ): Promise<{ status: number; body: unknown }> { return new Promise((resolve) => { const layer = router.stack.find((l: any) => l.route?.path === path); if (!layer?.route?.stack?.[0]?.handle) { resolve({ status: 404, body: { error: "Not found" } }); return; } const mockReq = { method: "GET", url: path, headers: {}, requestId: "test-request-id", } as any; let responseStatus = 200; let responseBody: unknown = null; let resolved = false; const mockRes = { status(code: number) { responseStatus = code; return this; }, json(data: unknown) { responseBody = data; if (!resolved) { resolved = true; resolve({ status: responseStatus, body: responseBody }); } return this; }, } as any; const mockNext = ((err?: unknown) => { if (err && !resolved) { resolved = true; const errorMessage = err instanceof Error ? err.message : String(err); resolve({ status: 500, body: { error: errorMessage } }); } }) as any; // The handler is wrapped by asyncHandler which returns a sync function // that internally handles the promise layer.route.stack[0].handle(mockReq, mockRes, mockNext); // Give the async handler time to complete setTimeout(() => { if (!resolved) { resolved = true; resolve({ status: responseStatus, body: responseBody }); } }, 100); }); } describe("Health Routes", () => { let mockCore: CognitiveCore; beforeEach(() => { mockCore = createMockCognitiveCore(); }); describe("createHealthRoutes", () => { it("should create a router with routes", () => { const router = createHealthRoutes(mockCore); expect(router).toBeDefined(); expect(router.stack).toBeDefined(); expect(router.stack.length).toBeGreaterThan(0); }); it("should have GET route for root health endpoint", () => { const router = createHealthRoutes(mockCore); const hasHealthRoute = router.stack.some((layer: any) => { return layer.route?.path === "/" && layer.route?.methods?.get; }); expect(hasHealthRoute).toBe(true); }); it("should have GET route for readiness endpoint", () => { const router = createHealthRoutes(mockCore); const hasReadyRoute = router.stack.some((layer: any) => { return layer.route?.path === "/ready" && layer.route?.methods?.get; }); expect(hasReadyRoute).toBe(true); }); it("should have GET route for liveness endpoint", () => { const router = createHealthRoutes(mockCore); const hasLiveRoute = router.stack.some((layer: any) => { return layer.route?.path === "/live" && layer.route?.methods?.get; }); expect(hasLiveRoute).toBe(true); }); }); describe("Health Checker Integration", () => { it("should have health checker available", () => { createHealthRoutes(mockCore); expect(healthChecker).toBeDefined(); expect(healthChecker.generateReport).toBeDefined(); }); it("should generate health report", async () => { createHealthRoutes(mockCore); const report = await healthChecker.generateReport(); expect(report).toBeDefined(); expect(report.status).toBeDefined(); expect(["healthy", "unhealthy", "degraded"]).toContain(report.status); }); it("should include uptime in health report", async () => { createHealthRoutes(mockCore); const report = await healthChecker.generateReport(); expect(report.uptimeMs).toBeGreaterThanOrEqual(0); }); }); describe("Component Health Checks", () => { it("should check database health via memory repository", () => { const router = createHealthRoutes(mockCore); expect(router).toBeDefined(); expect(mockCore.memoryRepository).toBeDefined(); }); it("should check reasoning health via reasoning orchestrator", () => { const router = createHealthRoutes(mockCore); expect(router).toBeDefined(); expect(mockCore.reasoningOrchestrator).toBeDefined(); }); }); describe("System Metrics", () => { it("should have access to process uptime", () => { createHealthRoutes(mockCore); const uptime = process.uptime(); expect(uptime).toBeGreaterThanOrEqual(0); }); it("should have access to process memory usage", () => { createHealthRoutes(mockCore); const memUsage = process.memoryUsage(); expect(memUsage.heapUsed).toBeGreaterThanOrEqual(0); expect(memUsage.heapTotal).toBeGreaterThanOrEqual(0); }); }); describe("Health Component Coverage Property", () => { it("should have cognitive core with all required components", () => { createHealthRoutes(mockCore); expect(mockCore.memoryRepository).toBeDefined(); expect(mockCore.reasoningOrchestrator).toBeDefined(); }); it("should have health checker with report generation capability", async () => { createHealthRoutes(mockCore); const report = await healthChecker.generateReport(); expect(report).toBeDefined(); expect(report.status).toBeDefined(); expect(report.components).toBeDefined(); expect(Array.isArray(report.components)).toBe(true); }); it("should report overall status as one of healthy, degraded, or unhealthy", async () => { createHealthRoutes(mockCore); const report = await healthChecker.generateReport(); expect(["healthy", "degraded", "unhealthy"]).toContain(report.status); }); it("should include metrics in health report", async () => { createHealthRoutes(mockCore); const report = await healthChecker.generateReport(); expect(report.metrics).toBeDefined(); expect(report.metrics.memoryUsed).toBeGreaterThanOrEqual(0); expect(report.metrics.heapUsed).toBeGreaterThanOrEqual(0); }); }); describe("Readiness Check Logic", () => { it("should consider system ready when critical components are available", () => { createHealthRoutes(mockCore); expect(mockCore.memoryRepository).toBeDefined(); }); it("should consider system not ready when memory repository is missing", () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); expect(router).toBeDefined(); }); }); describe("Liveness Check Logic", () => { it("should always report alive when process is running", () => { createHealthRoutes(mockCore); const uptime = process.uptime(); expect(uptime).toBeGreaterThanOrEqual(0); }); it("should report uptime in seconds", () => { createHealthRoutes(mockCore); const uptimeSeconds = Math.floor(process.uptime()); expect(uptimeSeconds).toBeGreaterThanOrEqual(0); }); }); describe("Status Determination Logic", () => { type HealthStatus = "healthy" | "degraded" | "unhealthy"; it("should determine healthy status when all components are healthy", () => { const components: Record<string, { status: HealthStatus; lastCheck: string }> = { database: { status: "healthy", lastCheck: new Date().toISOString() }, embeddingEngine: { status: "healthy", lastCheck: new Date().toISOString() }, reasoning: { status: "healthy", lastCheck: new Date().toISOString() }, memory: { status: "healthy", lastCheck: new Date().toISOString() }, }; const statuses = Object.values(components).map((c) => c.status); const hasUnhealthy = statuses.some((s) => s === "unhealthy"); const hasDegraded = statuses.some((s) => s === "degraded"); expect(hasUnhealthy).toBe(false); expect(hasDegraded).toBe(false); }); it("should determine degraded status when any component is degraded", () => { const components: Record<string, { status: HealthStatus; lastCheck: string }> = { database: { status: "healthy", lastCheck: new Date().toISOString() }, embeddingEngine: { status: "degraded", lastCheck: new Date().toISOString() }, reasoning: { status: "healthy", lastCheck: new Date().toISOString() }, memory: { status: "healthy", lastCheck: new Date().toISOString() }, }; const statuses = Object.values(components).map((c) => c.status); const hasUnhealthy = statuses.some((s) => s === "unhealthy"); const hasDegraded = statuses.some((s) => s === "degraded"); expect(hasUnhealthy).toBe(false); expect(hasDegraded).toBe(true); }); it("should determine unhealthy status when any component is unhealthy", () => { const components: Record<string, { status: HealthStatus; lastCheck: string }> = { database: { status: "unhealthy", lastCheck: new Date().toISOString() }, embeddingEngine: { status: "healthy", lastCheck: new Date().toISOString() }, reasoning: { status: "healthy", lastCheck: new Date().toISOString() }, memory: { status: "healthy", lastCheck: new Date().toISOString() }, }; const statuses = Object.values(components).map((c) => c.status); const hasUnhealthy = statuses.some((s) => s === "unhealthy"); expect(hasUnhealthy).toBe(true); }); }); describe("Route Handler Execution", () => { describe("GET /health - Full Health Check Handler", () => { it("should return health response with all components when core is healthy", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); expect(response.status).toBe(200); expect(response.body).toBeDefined(); const body = response.body as any; expect(body.success).toBe(true); expect(body.data).toBeDefined(); expect(body.data.status).toBe("healthy"); expect(body.data.components).toBeDefined(); expect(body.data.components.database).toBeDefined(); expect(body.data.components.embeddingEngine).toBeDefined(); expect(body.data.components.reasoning).toBeDefined(); expect(body.data.components.memory).toBeDefined(); expect(body.data.metrics).toBeDefined(); expect(body.data.metrics.uptime).toBeGreaterThanOrEqual(0); expect(body.data.metrics.memoryUsage).toBeGreaterThan(0); }); it("should return unhealthy status when memory repository is missing", async () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); const response = await invokeHandler(router, "/"); expect(response.status).toBe(503); const body = response.body as any; expect(body.data.status).toBe("unhealthy"); expect(body.data.components.database.status).toBe("unhealthy"); expect(body.data.components.memory.status).toBe("unhealthy"); }); it("should return unhealthy status when reasoning orchestrator is missing", async () => { const coreWithoutReasoning = { ...mockCore, reasoningOrchestrator: null as unknown as CognitiveCore["reasoningOrchestrator"], }; const router = createHealthRoutes(coreWithoutReasoning); const response = await invokeHandler(router, "/"); expect(response.status).toBe(503); const body = response.body as any; expect(body.data.status).toBe("unhealthy"); expect(body.data.components.reasoning.status).toBe("unhealthy"); }); it("should include cache metrics in response", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.metrics.cache).toBeDefined(); expect(body.data.metrics.cache.hits).toBe(50); expect(body.data.metrics.cache.misses).toBe(50); expect(body.data.metrics.cache.hitRate).toBe(0.5); }); it("should include performance stats in response", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.metrics.requestCount).toBe(100); expect(body.data.metrics.avgResponseTime).toBe(50); expect(body.data.metrics.p95ResponseTime).toBe(150); }); }); describe("GET /health/ready - Readiness Probe Handler", () => { it("should return ready=true when critical components are available", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/ready"); expect(response.status).toBe(200); const body = response.body as any; expect(body.data.ready).toBe(true); expect(body.data.reason).toBeUndefined(); }); it("should return ready=false when database is unhealthy", async () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); const response = await invokeHandler(router, "/ready"); expect(response.status).toBe(503); const body = response.body as any; expect(body.data.ready).toBe(false); expect(body.data.reason).toBeDefined(); expect(body.data.reason).toContain("Critical components unhealthy"); }); it("should include reason with specific unhealthy components", async () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); const response = await invokeHandler(router, "/ready"); const body = response.body as any; expect(body.data.reason).toContain("database"); expect(body.data.reason).toContain("memory"); }); }); describe("GET /health/live - Liveness Probe Handler", () => { it("should return alive=true with uptime", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/live"); expect(response.status).toBe(200); const body = response.body as any; expect(body.data.alive).toBe(true); expect(body.data.uptime).toBeGreaterThanOrEqual(0); }); it("should always return 200 status for liveness check", async () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); const response = await invokeHandler(router, "/live"); expect(response.status).toBe(200); }); }); describe("Component Health Check Functions", () => { it("should report database healthy when memory repository exists", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.components.database.status).toBe("healthy"); expect(body.data.components.database.latency).toBeGreaterThanOrEqual(0); expect(body.data.components.database.lastCheck).toBeDefined(); }); it("should report embedding engine healthy by default", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.components.embeddingEngine.status).toBe("healthy"); expect(body.data.components.embeddingEngine.lastCheck).toBeDefined(); }); it("should report reasoning healthy when orchestrator exists", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.components.reasoning.status).toBe("healthy"); expect(body.data.components.reasoning.latency).toBeGreaterThanOrEqual(0); }); it("should report memory healthy when repository exists", async () => { const router = createHealthRoutes(mockCore); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.components.memory.status).toBe("healthy"); expect(body.data.components.memory.latency).toBeGreaterThanOrEqual(0); }); it("should include error message when component is unhealthy", async () => { const coreWithoutMemory = { ...mockCore, memoryRepository: null as unknown as CognitiveCore["memoryRepository"], }; const router = createHealthRoutes(coreWithoutMemory); const response = await invokeHandler(router, "/"); const body = response.body as any; expect(body.data.components.database.message).toBeDefined(); expect(body.data.components.database.message).toContain("not available"); }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server