diff --git a/apps/api/package.json b/apps/api/package.json index c1dd783..fead9b8 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,7 +7,8 @@ "dev": "nitro dev", "dev:inspect": "workflow inspect runs --web", "build": "nitro build", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "bun test" }, "dependencies": { "@ai-sdk/openai": "^3.0.52", diff --git a/apps/api/src/__tests__/sendblue.test.ts b/apps/api/src/__tests__/sendblue.test.ts new file mode 100644 index 0000000..cabd6b1 --- /dev/null +++ b/apps/api/src/__tests__/sendblue.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "bun:test"; + +process.env.SENDBLUE_API_KEY = "test-key"; +process.env.SENDBLUE_API_SECRET = "test-secret"; +process.env.SENDBLUE_FROM_NUMBER = "+15555550100"; +process.env.SENDBLUE_WEBHOOK_SECRET = "expected-secret"; +process.env.UPSTASH_REDIS_REST_URL = "https://example.upstash.io"; +process.env.UPSTASH_REDIS_REST_TOKEN = "test-token"; +process.env.REDIS_URL = "rediss://default:test@example.upstash.io:6379"; +process.env.OPENAI_API_KEY = "sk-test"; +process.env.ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + +const { parseInbound } = await import("../sendblue"); + +const inboundBody = { + status: "RECEIVED", + is_outbound: false, + number: "+15555550123", + content: "hello", + media_url: "", + message_handle: "message-1", +}; + +describe("parseInbound", () => { + test("rejects webhook requests without a signing secret", () => { + expect(parseInbound(new Headers(), inboundBody)).toBeNull(); + }); + + test("rejects webhook requests with an invalid signing secret", () => { + const headers = new Headers({ "sb-signing-secret": "wrong-secret" }); + + expect(parseInbound(headers, inboundBody)).toBeNull(); + }); + + test("accepts Sendblue's documented signing secret header", () => { + const headers = new Headers({ "sb-signing-secret": "expected-secret" }); + + expect(parseInbound(headers, inboundBody)).toEqual({ + phone: "+15555550123", + text: "hello", + imageUrl: undefined, + messageId: "message-1", + }); + }); +}); diff --git a/apps/api/src/sendblue.ts b/apps/api/src/sendblue.ts index 9d6272f..e0e28c3 100644 --- a/apps/api/src/sendblue.ts +++ b/apps/api/src/sendblue.ts @@ -11,11 +11,16 @@ export interface InboundMessage { messageId: string | undefined; } -const SECRET_HEADERS = ["x-webhook-secret", "x-sendblue-signature", "sb-webhook-secret"]; +const SECRET_HEADERS = [ + "sb-signing-secret", + "x-webhook-secret", + "x-sendblue-signature", + "sb-webhook-secret", +]; export function parseInbound(headers: Headers, body: unknown): InboundMessage | null { const secret = SECRET_HEADERS.reduce((v, h) => v ?? headers.get(h), null); - if (secret && secret !== env.SENDBLUE_WEBHOOK_SECRET) return null; + if (!secret || secret !== env.SENDBLUE_WEBHOOK_SECRET) return null; const b = body as Record; if (b.is_outbound || b.status !== "RECEIVED") return null;