import { beforeEach, describe, expect, it, vi } from "vitest"; import type { CameraRecord } from "./camera-registry"; const getCameraRecord = vi.fn<(id: string) => Promise>(); vi.mock("./camera-registry", async (importOriginal) => ({ ...(await importOriginal()), getCameraRecord, })); const { cameraTarget, cameraErrorResponse } = await import("./camera-route"); const { CameraAuthError, CameraInactiveError } = await import("./camera"); const { CredentialStoreError } = await import("./credential-store"); const ID = "11111111-2222-3333-4444-555555555555"; const record = (host: string): CameraRecord => ({ id: ID, urn: `urn:uuid:${ID}`, host, port: 8080, lastSeen: "2026-09-19T00:00:00.000Z", }); async function expectError(result: unknown, status: number, error: string) { expect(result).toBeInstanceOf(Response); const res = result as Response; expect(res.status).toBe(status); expect(await res.json()).toEqual({ error }); } describe("cameraTarget", () => { beforeEach(() => { getCameraRecord.mockReset(); }); it("resolves a known camera to its registry address", async () => { getCameraRecord.mockResolvedValue(record("192.168.1.10")); expect(await cameraTarget(Promise.resolve({ id: ID }))).toEqual({ id: ID, host: "192.168.1.10", port: 8080, }); }); it("rejects a malformed ID without touching the registry", async () => { await expectError(await cameraTarget(Promise.resolve({ id: "../etc" })), 400, "Invalid camera ID"); expect(getCameraRecord).not.toHaveBeenCalled(); }); it("returns 404 for an unknown camera", async () => { getCameraRecord.mockResolvedValue(null); await expectError( await cameraTarget(Promise.resolve({ id: ID })), 404, "Unknown camera; scan the network again", ); }); it.each(["8.8.8.8", "127.0.0.1", "camera.example.com"])( "refuses a registry entry pointing at non-private host %s", async (host) => { getCameraRecord.mockResolvedValue(record(host)); await expectError( await cameraTarget(Promise.resolve({ id: ID })), 400, "Camera address is not a private IPv4 address", ); }, ); }); describe("cameraErrorResponse", () => { it.each([ ["inactive camera", new CameraInactiveError("not activated"), 409, "inactive"], ["rejected login", new CameraAuthError("401"), 401, "auth"], ["credential store problem", new CredentialStoreError("no key"), 500, "store"], ])("maps a %s to status %i with code %s", async (_label, err, status, code) => { const res = cameraErrorResponse(err); expect(res.status).toBe(status); expect(await res.json()).toMatchObject({ code }); }); it("maps any other failure to 502 without a code", async () => { const res = cameraErrorResponse(new Error("socket hang up")); expect(res.status).toBe(502); expect(await res.json()).not.toHaveProperty("code"); }); it("accepts non-Error values", async () => { const res = cameraErrorResponse("boom"); expect(res.status).toBe(502); }); });