| import fs from "node:fs/promises"; |
| import type { Server } from "node:http"; |
| import { beforeEach, describe, expect, it, vi } from "vitest"; |
|
|
| const mocks = vi.hoisted(() => ({ |
| saveMediaSource: vi.fn(), |
| getTailnetHostname: vi.fn(), |
| ensurePortAvailable: vi.fn(), |
| startMediaServer: vi.fn(), |
| logInfo: vi.fn(), |
| })); |
| const { saveMediaSource, getTailnetHostname, ensurePortAvailable, startMediaServer, logInfo } = |
| mocks; |
|
|
| vi.mock("./store.js", () => ({ saveMediaSource })); |
| vi.mock("../infra/tailscale.js", () => ({ getTailnetHostname })); |
| vi.mock("../infra/ports.js", async () => { |
| const actual = await vi.importActual<typeof import("../infra/ports.js")>("../infra/ports.js"); |
| return { ensurePortAvailable, PortInUseError: actual.PortInUseError }; |
| }); |
| vi.mock("./server.js", () => ({ startMediaServer })); |
| vi.mock("../logger.js", async () => { |
| const actual = await vi.importActual<typeof import("../logger.js")>("../logger.js"); |
| return { ...actual, 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"); |
| }); |
| }); |
|
|