fix(SEC-ORCH-16): Implement real health and readiness checks

- 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>
This commit is contained in:
Jason Woltje
2026-02-05 19:20:07 -06:00
parent 22446acd8a
commit 89bb24493a
6 changed files with 121 additions and 14 deletions

View File

@@ -1,13 +1,21 @@
import { describe, it, expect, beforeEach } from "vitest";
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(() => {
service = new HealthService();
vi.clearAllMocks();
service = new HealthService(mockValkeyService);
controller = new HealthController(service);
});
@@ -83,17 +91,46 @@ describe("HealthController", () => {
});
describe("GET /health/ready", () => {
it("should return ready status", () => {
const result = controller.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 return ready as true", () => {
const result = controller.ready();
it("should throw 503 when Valkey is unhealthy", async () => {
vi.mocked(mockValkeyService.ping).mockResolvedValue(false);
expect(result.ready).toBe(true);
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);
});
});
});