From 594fb125e678a16f44b5dd06ebd0fa9b16a6d939 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Nov 2025 03:50:18 +0100 Subject: [PATCH] test: add infra coverage and fix web logging --- src/infra/ports.test.ts | 32 +++++++++++++++++++ src/infra/tailscale.test.ts | 61 +++++++++++++++++++++++++++++++++++++ src/provider-web.ts | 5 +-- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/infra/ports.test.ts create mode 100644 src/infra/tailscale.test.ts diff --git a/src/infra/ports.test.ts b/src/infra/ports.test.ts new file mode 100644 index 000000000..f4c7985ec --- /dev/null +++ b/src/infra/ports.test.ts @@ -0,0 +1,32 @@ +import net from "node:net"; +import { describe, expect, it, vi } from "vitest"; + +import { ensurePortAvailable, handlePortError, PortInUseError } from "./ports.js"; + +describe("ports helpers", () => { + it("ensurePortAvailable rejects when port busy", async () => { + const server = net.createServer(); + await new Promise((resolve) => server.listen(0, resolve)); + const port = (server.address() as net.AddressInfo).port; + await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf( + PortInUseError, + ); + server.close(); + }); + + it("handlePortError exits nicely on EADDRINUSE", async () => { + const runtime = { + error: vi.fn(), + log: vi.fn(), + exit: vi.fn() as unknown as (code: number) => never, + }; + await handlePortError( + { code: "EADDRINUSE" }, + 1234, + "context", + runtime, + ).catch(() => {}); + expect(runtime.error).toHaveBeenCalled(); + expect(runtime.exit).toHaveBeenCalledWith(1); + }); +}); diff --git a/src/infra/tailscale.test.ts b/src/infra/tailscale.test.ts new file mode 100644 index 000000000..5be831725 --- /dev/null +++ b/src/infra/tailscale.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + getTailnetHostname, + ensureGoInstalled, + ensureTailscaledInstalled, +} from "./tailscale.js"; + +describe("tailscale helpers", () => { + it("parses DNS name from tailscale status", async () => { + const exec = vi.fn().mockResolvedValue({ + stdout: JSON.stringify({ + Self: { DNSName: "host.tailnet.ts.net.", TailscaleIPs: ["100.1.1.1"] }, + }), + }); + const host = await getTailnetHostname(exec); + expect(host).toBe("host.tailnet.ts.net"); + }); + + it("falls back to IP when DNS missing", async () => { + const exec = vi.fn().mockResolvedValue({ + stdout: JSON.stringify({ Self: { TailscaleIPs: ["100.2.2.2"] } }), + }); + const host = await getTailnetHostname(exec); + expect(host).toBe("100.2.2.2"); + }); + + it("ensureGoInstalled installs when missing and user agrees", async () => { + const exec = vi + .fn() + .mockRejectedValueOnce(new Error("no go")) + .mockResolvedValue({}); // brew install go + const prompt = vi.fn().mockResolvedValue(true); + const runtime = { + error: vi.fn(), + log: vi.fn(), + exit: ((code: number) => { + throw new Error(`exit ${code}`); + }) as (code: number) => never, + }; + await ensureGoInstalled(exec as never, prompt, runtime); + expect(exec).toHaveBeenCalledWith("brew", ["install", "go"]); + }); + + it("ensureTailscaledInstalled installs when missing and user agrees", async () => { + const exec = vi + .fn() + .mockRejectedValueOnce(new Error("missing")) + .mockResolvedValue({}); + const prompt = vi.fn().mockResolvedValue(true); + const runtime = { + error: vi.fn(), + log: vi.fn(), + exit: ((code: number) => { + throw new Error(`exit ${code}`); + }) as (code: number) => never, + }; + await ensureTailscaledInstalled(exec as never, prompt, runtime); + expect(exec).toHaveBeenCalledWith("brew", ["install", "tailscale"]); + }); +}); diff --git a/src/provider-web.ts b/src/provider-web.ts index d7bfa49cb..3e5391d9a 100644 --- a/src/provider-web.ts +++ b/src/provider-web.ts @@ -12,7 +12,7 @@ import { } from "@whiskeysockets/baileys"; import pino from "pino"; import qrcode from "qrcode-terminal"; -import { danger, isVerbose, logVerbose, success, warn } from "./globals.js"; +import { danger, info, isVerbose, logVerbose, success, warn } from "./globals.js"; import { ensureDir, jidToE164, toWhatsappJid } from "./utils.js"; import type { Provider } from "./utils.js"; import { waitForever } from "./cli/wait.js"; @@ -124,9 +124,10 @@ export async function sendMessageWeb( export async function loginWeb( verbose: boolean, waitForConnection: typeof waitForWaConnection = waitForWaConnection, + runtime: RuntimeEnv = defaultRuntime, ) { const sock = await createWaSocket(true, verbose); - console.log(info("Waiting for WhatsApp connection...")); + logInfo("Waiting for WhatsApp connection...", runtime); try { await waitForConnection(sock); console.log(success("✅ Linked! Credentials saved for future sends."));