- Add ping() method to ValkeyClient and ValkeyService for health checks - Update HealthService to check Valkey connectivity before reporting ready - /health/ready now returns 503 if dependencies are unhealthy - Add detailed checks object showing individual dependency status - Update tests with ValkeyService mock Refs #339 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|