// Sms tests cover status plugin behavior. import { describe, expect, it, vi } from "vitest"; import { formatSmsProbeLines, probeSmsAccount } from "./status.js"; import type { ResolvedSmsAccount } from "./types.js"; function createAccount(overrides: Partial = {}): ResolvedSmsAccount { return { accountId: "default", enabled: true, accountSid: "AC123", authToken: "secret", fromNumber: "+15557654321", messagingServiceSid: "", defaultTo: "", webhookPath: "/webhooks/sms", publicWebhookUrl: "https://gateway.example.com/webhooks/sms", dangerouslyDisableSignatureValidation: false, dmPolicy: "pairing", allowFrom: [], textChunkLimit: 1500, ...overrides, }; } function createFetch(responses: Array): typeof fetch { return vi.fn(async () => { const payload = responses.shift(); return new Response(JSON.stringify(payload), { status: 200, headers: { "content-type": "application/json" }, }); }) as unknown as typeof fetch; } describe("SMS status probe", () => { it("reports a healthy Twilio SMS webhook", async () => { const fetchImpl = createFetch([ { incoming_phone_numbers: [ { phone_number: "+15557654321", sms_url: "https://gateway.example.com/webhooks/sms", sms_method: "POST", voice_url: "https://gateway.example.com/voice/webhook", }, ], }, { messages: [ { sid: "SM123", direction: "inbound", status: "received", to: "+15557654321", from: "+15551234567", }, ], }, ]); await expect( probeSmsAccount({ account: createAccount(), timeoutMs: 1000, options: { fetchImpl }, }), ).resolves.toMatchObject({ ok: true, webhook: { status: "matches", configuredUrl: "https://gateway.example.com/webhooks/sms", voiceUrl: "https://gateway.example.com/voice/webhook", }, recentInbound: { sid: "SM123", status: "received", }, }); const result = await probeSmsAccount({ account: createAccount(), timeoutMs: 1000, options: { fetchImpl: createFetch([ { incoming_phone_numbers: [ { phone_number: "+15557654321", sms_url: "https://gateway.example.com/webhooks/sms", sms_method: "POST", }, ], }, { messages: [ { sid: "SM456", direction: "inbound", status: "received", to: "+15557654321", from: "+15551234567", }, ], }, ]), }, }); expect(result.recentInbound).not.toHaveProperty("from"); expect(result.recentInbound).not.toHaveProperty("to"); }); it("detects a Twilio SMS webhook URL mismatch", async () => { const fetchImpl = createFetch([ { incoming_phone_numbers: [ { phone_number: "+15557654321", sms_url: "https://old.example.com/webhooks/sms", sms_method: "POST", }, ], }, { messages: [] }, ]); await expect( probeSmsAccount({ account: createAccount(), timeoutMs: 1000, options: { fetchImpl }, }), ).resolves.toMatchObject({ ok: false, error: "Twilio number +15557654321 points SMS webhooks at https://old.example.com/webhooks/sms; expected https://gateway.example.com/webhooks/sms.", webhook: { status: "url-mismatch", }, }); }); it("surfaces Twilio 11200 recent inbound failures and Funnel hints", async () => { const fetchImpl = createFetch([ { incoming_phone_numbers: [ { phone_number: "+15557654321", sms_url: "https://mac-studio.example.ts.net/webhooks/sms", sms_method: "POST", }, ], }, { messages: [ { sid: "SM11200", direction: "inbound", status: "received", to: "+15557654321", from: "+15551234567", error_code: 11200, }, ], }, ]); const result = await probeSmsAccount({ account: createAccount({ publicWebhookUrl: "https://mac-studio.example.ts.net/webhooks/sms", }), timeoutMs: 1000, options: { fetchImpl }, }); expect(result).toMatchObject({ ok: false, error: "Recent inbound SMS SM11200 has Twilio error 11200.", recentInbound: { sid: "SM11200", errorCode: "11200", }, }); expect(result.hints).toEqual( expect.arrayContaining([ expect.stringContaining("Tailscale Funnel must expose the exact SMS path"), expect.stringContaining("Twilio error 11200 means Twilio could not reach"), ]), ); }); it("validates Twilio Messaging Service webhook settings", async () => { const fetchImpl = createFetch([ { sid: "MG123", inbound_request_url: "https://gateway.example.com/webhooks/sms", inbound_method: "POST", use_inbound_webhook_on_number: false, }, ]); await expect( probeSmsAccount({ account: createAccount({ fromNumber: "", messagingServiceSid: "MG123", }), timeoutMs: 1000, options: { fetchImpl }, }), ).resolves.toMatchObject({ ok: true, webhook: { status: "messaging-service-matches", serviceSid: "MG123", configuredUrl: "https://gateway.example.com/webhooks/sms", }, }); }); it("does not report Messaging Service defer-to-number probes as healthy", async () => { const fetchImpl = createFetch([ { sid: "MG123", inbound_request_url: "https://gateway.example.com/webhooks/sms", inbound_method: "POST", use_inbound_webhook_on_number: true, }, ]); await expect( probeSmsAccount({ account: createAccount({ fromNumber: "", messagingServiceSid: "MG123", }), timeoutMs: 1000, options: { fetchImpl }, }), ).resolves.toMatchObject({ ok: false, error: "Twilio Messaging Service defers inbound webhooks to sender phone numbers; configure fromNumber or disable defer-to-sender before probing.", webhook: { status: "unavailable", }, }); }); it("formats probe details for channel capability output", () => { expect( formatSmsProbeLines({ ok: false, error: "Recent inbound SMS SM11200 has Twilio error 11200.", webhook: { status: "matches", configuredUrl: "https://gateway.example.com/webhooks/sms" }, recentInbound: { sid: "SM11200", status: "received", errorCode: "11200" }, hints: ["Check the public route."], }), ).toEqual([ { text: "Probe: failed (Recent inbound SMS SM11200 has Twilio error 11200.)", tone: "error", }, { text: "Twilio SMS webhook: https://gateway.example.com/webhooks/sms" }, { text: "Recent inbound: received error=11200", tone: "warn" }, { text: "Check the public route.", tone: "warn" }, ]); }); });