import fs from "node:fs/promises"; import type { Server } from "node:http"; import { beforeEach, describe, expect, it, vi } from "vitest"; const saveMediaSource = vi.fn(); const getTailnetHostname = vi.fn(); const ensurePortAvailable = vi.fn(); const startMediaServer = vi.fn(); const logInfo = vi.fn(); vi.mock("./store.js", () => ({ saveMediaSource })); vi.mock("../infra/tailscale.js", () => ({ getTailnetHostname })); vi.mock("../infra/ports.js", async () => { const actual = await vi.importActual( "../infra/ports.js", ); return { ensurePortAvailable, PortInUseError: actual.PortInUseError }; }); vi.mock("./server.js", () => ({ startMediaServer })); vi.mock("../logger.js", () => ({ logInfo })); const { ensureMediaHosted } = await import("./host.js"); const { PortInUseError } = await import("../infra/ports.js"); describe("ensureMediaHosted", () => { beforeEach(() => { vi.clearAllMocks(); }); it("throws and cleans up when server not allowed to start", async () => { saveMediaSource.mockResolvedValue({ id: "id1", path: "/tmp/file1", size: 5, }); getTailnetHostname.mockResolvedValue("tailnet-host"); ensurePortAvailable.mockResolvedValue(undefined); const rmSpy = vi.spyOn(fs, "rm").mockResolvedValue(undefined); await expect( ensureMediaHosted("/tmp/file1", { startServer: false }), ).rejects.toThrow("requires the webhook/Funnel server"); expect(rmSpy).toHaveBeenCalledWith("/tmp/file1"); rmSpy.mockRestore(); }); it("starts media server when allowed", async () => { saveMediaSource.mockResolvedValue({ id: "id2", path: "/tmp/file2", size: 9, }); getTailnetHostname.mockResolvedValue("tail.net"); ensurePortAvailable.mockResolvedValue(undefined); const fakeServer = { unref: vi.fn() } as unknown as Server; startMediaServer.mockResolvedValue(fakeServer); const result = await ensureMediaHosted("/tmp/file2", { startServer: true, port: 1234, }); expect(startMediaServer).toHaveBeenCalledWith( 1234, expect.any(Number), expect.anything(), ); expect(logInfo).toHaveBeenCalled(); expect(result).toEqual({ url: "https://tail.net/media/id2", id: "id2", size: 9, }); }); it("skips server start when port already in use", async () => { saveMediaSource.mockResolvedValue({ id: "id3", path: "/tmp/file3", size: 7, }); getTailnetHostname.mockResolvedValue("tail.net"); ensurePortAvailable.mockRejectedValue(new PortInUseError(3000, "proc")); const result = await ensureMediaHosted("/tmp/file3", { startServer: false, port: 3000, }); expect(startMediaServer).not.toHaveBeenCalled(); expect(result.url).toBe("https://tail.net/media/id3"); }); });