import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../../utils/logger.js", () => ({
    logger: {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    },
}));
const mockProxyToOpenCode = vi.fn();
vi.mock("../proxy.js", () => ({
    proxyToOpenCode: (...args) => mockProxyToOpenCode(...args),
}));
import { startHealthCheck, stopHealthCheck, getHealthStatus, resetHealthCheck, } from "../health-check.js";
import { logger } from "../../utils/logger.js";
describe("OpenCode health check", () => {
    beforeEach(() => {
        vi.useFakeTimers();
        vi.clearAllMocks();
        resetHealthCheck();
    });
    afterEach(() => {
        resetHealthCheck();
        vi.useRealTimers();
    });
    it("should check health every 30 seconds", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        // First interval fires at 30s
        await vi.advanceTimersByTimeAsync(30_000);
        expect(mockProxyToOpenCode).toHaveBeenCalledTimes(1);
        expect(mockProxyToOpenCode).toHaveBeenCalledWith({ method: "GET", path: "/global/health" }, 5_000);
        // Second interval fires at 60s
        await vi.advanceTimersByTimeAsync(30_000);
        expect(mockProxyToOpenCode).toHaveBeenCalledTimes(2);
    });
    it("should update lastHealthCheck on success", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        // Before any check
        const statusBefore = getHealthStatus();
        expect(statusBefore.lastHealthCheck).toBeNull();
        expect(statusBefore.healthy).toBe(false);
        await vi.advanceTimersByTimeAsync(30_000);
        const statusAfter = getHealthStatus();
        expect(statusAfter.lastHealthCheck).not.toBeNull();
        expect(statusAfter.healthy).toBe(true);
        expect(statusAfter.consecutiveFailures).toBe(0);
    });
    it("should track consecutive failures", async () => {
        mockProxyToOpenCode.mockRejectedValue(new Error("OPENCODE_UNREACHABLE"));
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(getHealthStatus().consecutiveFailures).toBe(1);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(getHealthStatus().consecutiveFailures).toBe(2);
    });
    it("should trigger restart after 3 consecutive failures", async () => {
        mockProxyToOpenCode.mockRejectedValue(new Error("OPENCODE_UNREACHABLE"));
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        // 1st failure
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).not.toHaveBeenCalled();
        // 2nd failure
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).not.toHaveBeenCalled();
        // 3rd failure → triggers restart
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).toHaveBeenCalledOnce();
        expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("triggering restart"));
    });
    it("should reset consecutive failures on success", async () => {
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        // 2 failures
        mockProxyToOpenCode.mockRejectedValue(new Error("OPENCODE_UNREACHABLE"));
        await vi.advanceTimersByTimeAsync(30_000);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(getHealthStatus().consecutiveFailures).toBe(2);
        // 1 success
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        await vi.advanceTimersByTimeAsync(30_000);
        expect(getHealthStatus().consecutiveFailures).toBe(0);
        // No restart should have been triggered
        expect(restartFn).not.toHaveBeenCalled();
    });
    it("should stop checking on shutdown", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(mockProxyToOpenCode).toHaveBeenCalledTimes(1);
        stopHealthCheck();
        await vi.advanceTimersByTimeAsync(60_000);
        // Should not have made any more calls after stopping
        expect(mockProxyToOpenCode).toHaveBeenCalledTimes(1);
    });
    it("should handle non-2xx responses as failures", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 500, headers: {}, body: "error" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(getHealthStatus().consecutiveFailures).toBe(1);
        expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("HTTP 500"));
    });
    it("should be idempotent when starting multiple times", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        startHealthCheck(restartFn); // Second call should be a no-op
        await vi.advanceTimersByTimeAsync(30_000);
        // Should only have one interval, not two
        expect(mockProxyToOpenCode).toHaveBeenCalledTimes(1);
    });
    it("should reset consecutive failures counter after triggering restart", async () => {
        mockProxyToOpenCode.mockRejectedValue(new Error("OPENCODE_UNREACHABLE"));
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        // Trigger restart after 3 failures
        await vi.advanceTimersByTimeAsync(30_000);
        await vi.advanceTimersByTimeAsync(30_000);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).toHaveBeenCalledTimes(1);
        // Counter should be reset — another 3 failures needed for next restart
        await vi.advanceTimersByTimeAsync(30_000);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).toHaveBeenCalledTimes(1); // Still 1 — not triggered yet
        await vi.advanceTimersByTimeAsync(30_000);
        expect(restartFn).toHaveBeenCalledTimes(2); // Now triggered again
    });
    it("should log debug message on successful check", async () => {
        mockProxyToOpenCode.mockResolvedValue({ status: 200, headers: {}, body: "" });
        const restartFn = vi.fn().mockResolvedValue(undefined);
        startHealthCheck(restartFn);
        await vi.advanceTimersByTimeAsync(30_000);
        expect(logger.debug).toHaveBeenCalledWith("OpenCode health check passed");
    });
});
//# sourceMappingURL=health-check.test.js.map