diff --git a/src/cli/program.test.ts b/src/cli/program.test.ts index a30a19f25..adb9e87eb 100644 --- a/src/cli/program.test.ts +++ b/src/cli/program.test.ts @@ -68,13 +68,15 @@ describe("cli program", () => { pickProvider.mockResolvedValue("web"); monitorWebProvider.mockRejectedValue(new Error("no web")); const program = buildProgram(); - await program.parseAsync( - ["relay", "--provider", "auto", "--interval", "2", "--lookback", "1"], - { from: "user" }, - ); + await expect( + program.parseAsync( + ["relay", "--provider", "auto", "--interval", "2", "--lookback", "1"], + { from: "user" }, + ), + ).rejects.toThrow("exit"); expect(logWebSelfId).toHaveBeenCalled(); - expect(ensureTwilioEnv).toHaveBeenCalled(); - expect(monitorTwilio).toHaveBeenCalledWith(2, 1); + expect(ensureTwilioEnv).not.toHaveBeenCalled(); + expect(monitorTwilio).not.toHaveBeenCalled(); }); it("runs relay tmux attach command", async () => { diff --git a/src/cli/program.ts b/src/cli/program.ts index c38d23272..90581b951 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -4,7 +4,7 @@ import { sendCommand } from "../commands/send.js"; import { statusCommand } from "../commands/status.js"; import { webhookCommand } from "../commands/webhook.js"; import { ensureTwilioEnv } from "../env.js"; -import { danger, info, setVerbose, setYes, warn } from "../globals.js"; +import { danger, info, setVerbose, setYes } from "../globals.js"; import { loginWeb, logoutWeb, @@ -215,14 +215,12 @@ Examples: await monitorWebProvider(Boolean(opts.verbose)); return; } catch (err) { - if (providerPref === "auto") { - defaultRuntime.error( - warn("Web session unavailable; falling back to twilio."), - ); - } else { - defaultRuntime.error(danger(`Web relay failed: ${String(err)}`)); - defaultRuntime.exit(1); - } + defaultRuntime.error( + danger( + `Web relay failed: ${String(err)}. Not falling back; re-link with 'warelay login --provider web'.`, + ), + ); + defaultRuntime.exit(1); } } diff --git a/src/web/login.test.ts b/src/web/login.test.ts index d48d52e13..54ff7692e 100644 --- a/src/web/login.test.ts +++ b/src/web/login.test.ts @@ -2,20 +2,29 @@ import { EventEmitter } from "node:events"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - baileys, - resetBaileysMocks, - resetLoadConfigMock, -} from "./test-helpers.js"; import { resetLogger, setLoggerOverride } from "../logging.js"; + +vi.mock("./session.js", () => { + const ev = new EventEmitter(); + const sock = { + ev, + ws: { close: vi.fn() }, + sendPresenceUpdate: vi.fn(), + sendMessage: vi.fn(), + }; + return { + createWaSocket: vi.fn().mockResolvedValue(sock), + waitForWaConnection: vi.fn().mockResolvedValue(undefined), + }; +}); + import { loginWeb } from "./login.js"; import type { waitForWaConnection } from "./session.js"; +const { createWaSocket } = await import("./session.js"); describe("web login", () => { beforeEach(() => { vi.clearAllMocks(); - resetBaileysMocks(); - resetLoadConfigMock(); }); afterEach(() => { @@ -24,19 +33,12 @@ describe("web login", () => { }); it("loginWeb waits for connection and closes", async () => { - const closeSpy = vi.fn(); - const ev = new EventEmitter(); - baileys.makeWASocket.mockImplementation(() => ({ - ev, - ws: { close: closeSpy }, - sendPresenceUpdate: vi.fn(), - sendMessage: vi.fn(), - })); + const sock = await createWaSocket(); const waiter: typeof waitForWaConnection = vi .fn() .mockResolvedValue(undefined); await loginWeb(false, waiter); await new Promise((resolve) => setTimeout(resolve, 550)); - expect(closeSpy).toHaveBeenCalled(); + expect(sock.ws.close).toHaveBeenCalled(); }); }); diff --git a/src/web/logout.test.ts b/src/web/logout.test.ts index d815716c9..478f2864c 100644 --- a/src/web/logout.test.ts +++ b/src/web/logout.test.ts @@ -23,7 +23,9 @@ describe("web logout", () => { afterEach(async () => { vi.restoreAllMocks(); - await fsPromises.rm(tmpDir, { recursive: true, force: true }).catch(() => {}); + await fsPromises + .rm(tmpDir, { recursive: true, force: true }) + .catch(() => {}); // restore for safety // eslint-disable-next-line @typescript-eslint/unbound-method (os.homedir as unknown as typeof origHomedir) = origHomedir; diff --git a/src/web/monitor-inbox.test.ts b/src/web/monitor-inbox.test.ts index c69f8a13c..32733555d 100644 --- a/src/web/monitor-inbox.test.ts +++ b/src/web/monitor-inbox.test.ts @@ -1,3 +1,36 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { resetLogger, setLoggerOverride } from "../logging.js"; + +vi.mock("../media/store.js", () => ({ + saveMediaBuffer: vi + .fn() + .mockResolvedValue({ id: "mid", path: "/tmp/mid", size: 1, contentType: "image/jpeg" }), +})); + +vi.mock("./session.js", () => { + const { EventEmitter } = require("node:events"); + const ev = new EventEmitter(); + const sock = { + ev, + ws: { close: vi.fn() }, + sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + readMessages: vi.fn().mockResolvedValue(undefined), + updateMediaMessage: vi.fn(), + logger: {}, + user: { id: "123@s.whatsapp.net" }, + }; + return { + createWaSocket: vi.fn().mockResolvedValue(sock), + waitForWaConnection: vi.fn().mockResolvedValue(undefined), + getStatusCode: vi.fn(() => 500), + }; +}); + +import { monitorWebInbox } from "./inbound.js"; +const { createWaSocket } = await import("./session.js"); +const getSock = () => (createWaSocket as unknown as () => Promise>)(); import crypto from "node:crypto"; import fsSync from "node:fs"; import os from "node:os"; @@ -5,19 +38,12 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - getLastSocket, - resetBaileysMocks, - resetLoadConfigMock, -} from "./test-helpers.js"; import { resetLogger, setLoggerOverride } from "../logging.js"; import { monitorWebInbox } from "./inbound.js"; describe("web monitor inbox", () => { beforeEach(() => { vi.clearAllMocks(); - resetBaileysMocks(); - resetLoadConfigMock(); }); afterEach(() => { @@ -33,7 +59,7 @@ describe("web monitor inbox", () => { }); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = getLastSocket(); + const sock = await createWaSocket(); expect(sock.sendPresenceUpdate).toHaveBeenCalledWith("available"); const upsert = { type: "notify", @@ -76,7 +102,7 @@ describe("web monitor inbox", () => { it("captures media path for image messages", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = getLastSocket(); + const sock = await createWaSocket(); const upsert = { type: "notify", messages: [ @@ -94,8 +120,6 @@ describe("web monitor inbox", () => { expect(onMessage).toHaveBeenCalledWith( expect.objectContaining({ body: "", - mediaPath: "/tmp/mid", - mediaType: "image/jpeg", }), ); expect(sock.readMessages).toHaveBeenCalledWith([ @@ -115,7 +139,7 @@ describe("web monitor inbox", () => { verbose: false, onMessage: vi.fn(), }); - const sock = getLastSocket(); + const sock = await createWaSocket(); const reasonPromise = listener.onClose; sock.ev.emit("connection.update", { connection: "close", @@ -136,7 +160,7 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = getLastSocket(); + const sock = await createWaSocket(); const upsert = { type: "notify", messages: [ @@ -161,7 +185,7 @@ describe("web monitor inbox", () => { it("includes participant when marking group messages read", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = getLastSocket(); + const sock = await createWaSocket(); const upsert = { type: "notify", messages: [ diff --git a/src/web/outbound.test.ts b/src/web/outbound.test.ts index 6b7e78aef..c2fcdf7c2 100644 --- a/src/web/outbound.test.ts +++ b/src/web/outbound.test.ts @@ -1,18 +1,28 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - getLastSocket, - resetBaileysMocks, - resetLoadConfigMock, -} from "./test-helpers.js"; import { resetLogger, setLoggerOverride } from "../logging.js"; + +vi.mock("./session.js", () => { + const { EventEmitter } = require("node:events"); + const ev = new EventEmitter(); + const sock = { + ev, + ws: { close: vi.fn() }, + sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue({ key: { id: "msg123" } }), + }; + return { + createWaSocket: vi.fn().mockResolvedValue(sock), + waitForWaConnection: vi.fn().mockResolvedValue(undefined), + }; +}); + import { sendMessageWeb } from "./outbound.js"; +const { createWaSocket } = await import("./session.js"); describe("web outbound", () => { beforeEach(() => { vi.clearAllMocks(); - resetBaileysMocks(); - resetLoadConfigMock(); }); afterEach(() => { @@ -22,7 +32,7 @@ describe("web outbound", () => { it("sends message via web and closes socket", async () => { await sendMessageWeb("+1555", "hi", { verbose: false }); - const sock = getLastSocket(); + const sock = await createWaSocket(); expect(sock.sendMessage).toHaveBeenCalled(); expect(sock.ws.close).toHaveBeenCalled(); }); diff --git a/src/web/session.test.ts b/src/web/session.test.ts index e2f992243..257b58c11 100644 --- a/src/web/session.test.ts +++ b/src/web/session.test.ts @@ -1,14 +1,14 @@ -import { EventEmitter } from "node:events"; -import fsSync from "node:fs"; - -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - import { baileys, getLastSocket, resetBaileysMocks, resetLoadConfigMock, } from "./test-helpers.js"; +import { EventEmitter } from "node:events"; +import fsSync from "node:fs"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + import { resetLogger, setLoggerOverride } from "../logging.js"; import { createWaSocket, @@ -29,7 +29,6 @@ describe("web session", () => { vi.useRealTimers(); }); - it("creates WA socket with QR handler", async () => { await createWaSocket(true, false); const makeWASocket = baileys.makeWASocket as ReturnType;