import { describe, it, expect, beforeEach, vi } from "vitest"; import { HttpException, HttpStatus } from "@nestjs/common"; import { HealthController } from "./health.controller"; import { HealthService } from "./health.service"; import { ValkeyService } from "../../valkey/valkey.service"; // Mock ValkeyService const mockValkeyService = { ping: vi.fn(), } as unknown as ValkeyService; describe("HealthController", () => { let controller: HealthController; let service: HealthService; beforeEach(() => { vi.clearAllMocks(); service = new HealthService(mockValkeyService); controller = new HealthController(service); }); describe("GET /health", () => { it("should return 200 OK with correct format", () => { const result = controller.check(); expect(result).toBeDefined(); expect(result).toHaveProperty("status"); expect(result).toHaveProperty("uptime"); expect(result).toHaveProperty("timestamp"); }); it('should return status as "healthy"', () => { const result = controller.check(); expect(result.status).toBe("healthy"); }); it("should return uptime as a positive number", () => { const result = controller.check(); expect(typeof result.uptime).toBe("number"); expect(result.uptime).toBeGreaterThanOrEqual(0); }); it("should return timestamp as valid ISO 8601 string", () => { const result = controller.check(); expect(typeof result.timestamp).toBe("string"); expect(() => new Date(result.timestamp)).not.toThrow(); // Verify it's a valid ISO 8601 format const date = new Date(result.timestamp); expect(date.toISOString()).toBe(result.timestamp); }); it("should return only required fields (status, uptime, timestamp)", () => { const result = controller.check(); const keys = Object.keys(result); expect(keys).toHaveLength(3); expect(keys).toContain("status"); expect(keys).toContain("uptime"); expect(keys).toContain("timestamp"); }); it("should increment uptime over time", async () => { const result1 = controller.check(); const uptime1 = result1.uptime; // Wait 1100ms to ensure at least 1 second has passed await new Promise((resolve) => setTimeout(resolve, 1100)); const result2 = controller.check(); const uptime2 = result2.uptime; // Uptime should be at least 1 second higher expect(uptime2).toBeGreaterThanOrEqual(uptime1 + 1); }); it("should return current timestamp", () => { const before = Date.now(); const result = controller.check(); const after = Date.now(); const resultTime = new Date(result.timestamp).getTime(); // Timestamp should be between before and after (within test execution time) expect(resultTime).toBeGreaterThanOrEqual(before); expect(resultTime).toBeLessThanOrEqual(after); }); }); describe("GET /health/ready", () => { it("should return ready status with checks when all dependencies are healthy", async () => { vi.mocked(mockValkeyService.ping).mockResolvedValue(true); const result = await controller.ready(); expect(result).toBeDefined(); expect(result).toHaveProperty("ready"); expect(result).toHaveProperty("checks"); expect(result.ready).toBe(true); expect(result.checks.valkey).toBe(true); }); it("should throw 503 when Valkey is unhealthy", async () => { vi.mocked(mockValkeyService.ping).mockResolvedValue(false); await expect(controller.ready()).rejects.toThrow(HttpException); try { await controller.ready(); } catch (error) { expect(error).toBeInstanceOf(HttpException); expect((error as HttpException).getStatus()).toBe(HttpStatus.SERVICE_UNAVAILABLE); const response = (error as HttpException).getResponse() as { ready: boolean }; expect(response.ready).toBe(false); } }); it("should return checks object with individual dependency status", async () => { vi.mocked(mockValkeyService.ping).mockResolvedValue(true); const result = await controller.ready(); expect(result.checks).toBeDefined(); expect(typeof result.checks.valkey).toBe("boolean"); }); it("should handle Valkey ping errors gracefully", async () => { vi.mocked(mockValkeyService.ping).mockRejectedValue(new Error("Connection refused")); await expect(controller.ready()).rejects.toThrow(HttpException); }); }); });