import { afterEach, describe, expect, it, vi } from "vitest"; // Reader health: push-only QR readers expose no TCP port, so liveness is an ICMP // ping of the (optional) configured IP. With no IP we must NOT claim "ready" (the old // stub did, hiding offline readers behind a green dot) — we report degraded instead. // icmpPing is mocked so the test is deterministic + offline. const icmpPing = vi.fn<(host: string, timeoutMs?: number) => Promise>(); vi.mock("./icmp.js", () => ({ icmpPing: (...a: [string, number?]) => icmpPing(...a) })); const { geeQrReaderDriver } = await import("./reader.js"); afterEach(() => { icmpPing.mockReset(); }); describe("QR reader healthCheck (ICMP liveness)", () => { it("with an IP that replies → ready", async () => { icmpPing.mockResolvedValue(true); const r = geeQrReaderDriver.create({ serial: "H05M2AFA", host: "10.0.10.7" }); expect(await r.healthCheck()).toEqual({ status: "ready", detail: "ping 10.0.10.7" }); expect(icmpPing).toHaveBeenCalledWith("10.0.10.7"); }); it("with an IP that does NOT reply → offline (this is the bug fix)", async () => { icmpPing.mockResolvedValue(false); const r = geeQrReaderDriver.create({ serial: "H05M2AFA", host: "10.0.10.7" }); expect(await r.healthCheck()).toEqual({ status: "offline", detail: "no ping reply from 10.0.10.7" }); }); it("with NO IP → degraded (never a false 'ready')", async () => { const r = geeQrReaderDriver.create({ serial: "H05M2AFA" }); const h = await r.healthCheck(); expect(h.status).toBe("degraded"); expect(icmpPing).not.toHaveBeenCalled(); // nothing to ping }); it("exposes an optional host field for monitoring", () => { const hostField = geeQrReaderDriver.configFields.find((f) => f.key === "host"); expect(hostField).toBeDefined(); expect(hostField!.required).toBe(false); // operation is push-by-serial; IP is monitor-only }); });