import { describe, expect, it, vi } from "vitest"; import type { CliDeps } from "../cli/deps.js"; import type { RuntimeEnv } from "../runtime.js"; import { upCommand } from "./up.js"; const runtime: RuntimeEnv = { log: vi.fn(), error: vi.fn(), exit: vi.fn(() => { throw new Error("exit"); }), }; const makeDeps = (): CliDeps => ({ ensurePortAvailable: vi.fn().mockResolvedValue(undefined), readEnv: vi.fn().mockReturnValue({ whatsappFrom: "whatsapp:+1555", whatsappSenderSid: "WW", }), ensureBinary: vi.fn().mockResolvedValue(undefined), ensureFunnel: vi.fn().mockResolvedValue(undefined), getTailnetHostname: vi.fn().mockResolvedValue("tailnet-host"), startWebhook: vi.fn().mockResolvedValue({ server: true }), createClient: vi.fn().mockReturnValue({ client: true }), findWhatsappSenderSid: vi.fn().mockResolvedValue("SID123"), updateWebhook: vi.fn().mockResolvedValue(undefined), }); describe("upCommand", () => { it("throws on invalid port", async () => { await expect(() => upCommand({ port: "0", path: "/cb" }, makeDeps(), runtime), ).rejects.toThrow("Port must be between 1 and 65535"); }); it("performs dry run and returns mock data", async () => { runtime.log.mockClear(); const result = await upCommand( { port: "42873", path: "/cb", dryRun: true }, makeDeps(), runtime, ); expect(runtime.log).toHaveBeenCalledWith( "[dry-run] would enable funnel on port 42873", ); expect(result?.publicUrl).toBe("https://dry-run/cb"); expect(result?.senderSid).toBeUndefined(); }); it("enables funnel, starts webhook, and updates Twilio", async () => { const deps = makeDeps(); const res = await upCommand( { port: "42873", path: "/hook", verbose: true }, deps, runtime, ); expect(deps.ensureBinary).toHaveBeenCalledWith( "tailscale", undefined, runtime, ); expect(deps.ensureFunnel).toHaveBeenCalled(); expect(deps.startWebhook).toHaveBeenCalled(); expect(deps.updateWebhook).toHaveBeenCalledWith( expect.anything(), "SID123", "https://tailnet-host/hook", "POST", runtime, ); expect(res?.publicUrl).toBe("https://tailnet-host/hook"); // waiter is returned to keep the process alive in real use. expect(typeof res?.waiter).toBe("function"); }); });