diff --git a/src/infra/outbound/abort.test.ts b/src/infra/outbound/abort.test.ts new file mode 100644 index 00000000000..794615b2a28 --- /dev/null +++ b/src/infra/outbound/abort.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { throwIfAborted } from "./abort.js"; + +describe("throwIfAborted", () => { + it("does nothing when the signal is missing or not aborted", () => { + expect(() => throwIfAborted()).not.toThrow(); + expect(() => throwIfAborted(new AbortController().signal)).not.toThrow(); + }); + + it("throws a standard AbortError when the signal is aborted", () => { + const controller = new AbortController(); + controller.abort(); + + expect(() => throwIfAborted(controller.signal)).toThrowError( + expect.objectContaining({ + name: "AbortError", + message: "Operation aborted", + }), + ); + }); +}); diff --git a/src/infra/outbound/envelope.test.ts b/src/infra/outbound/envelope.test.ts new file mode 100644 index 00000000000..68b2aa28b96 --- /dev/null +++ b/src/infra/outbound/envelope.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest"; +import type { ReplyPayload } from "../../auto-reply/types.js"; +import { buildOutboundResultEnvelope } from "./envelope.js"; +import type { OutboundDeliveryJson } from "./format.js"; + +describe("buildOutboundResultEnvelope", () => { + const delivery: OutboundDeliveryJson = { + channel: "telegram", + via: "direct", + to: "123", + messageId: "m1", + mediaUrl: null, + chatId: "c1", + }; + + it("flattens delivery by default when nothing else is present", () => { + expect(buildOutboundResultEnvelope({ delivery })).toEqual(delivery); + }); + + it("keeps pre-normalized payload JSON entries but clones the array", () => { + const payloads = [{ text: "hi", mediaUrl: null, mediaUrls: undefined }]; + + const envelope = buildOutboundResultEnvelope({ + payloads, + meta: { ok: true }, + }); + + expect(envelope).toEqual({ + payloads: [{ text: "hi", mediaUrl: null, mediaUrls: undefined }], + meta: { ok: true }, + }); + expect((envelope as { payloads: unknown[] }).payloads).not.toBe(payloads); + }); + + it("normalizes reply payloads and keeps wrapped delivery when flattening is disabled", () => { + const payloads: ReplyPayload[] = [{ text: "hello" }]; + + expect( + buildOutboundResultEnvelope({ + payloads, + delivery, + flattenDelivery: false, + }), + ).toEqual({ + payloads: [ + { + text: "hello", + mediaUrl: null, + channelData: undefined, + }, + ], + delivery, + }); + }); +}); diff --git a/src/infra/outbound/outbound.test.ts b/src/infra/outbound/outbound.test.ts index 72ccf3e3c55..b04c0462e43 100644 --- a/src/infra/outbound/outbound.test.ts +++ b/src/infra/outbound/outbound.test.ts @@ -19,8 +19,6 @@ import { recoverPendingDeliveries, } from "./delivery-queue.js"; import { DirectoryCache } from "./directory-cache.js"; -import { buildOutboundResultEnvelope } from "./envelope.js"; -import type { OutboundDeliveryJson } from "./format.js"; import { buildOutboundDeliveryJson, formatGatewaySummary, @@ -670,73 +668,6 @@ describe("DirectoryCache", () => { }); }); -describe("buildOutboundResultEnvelope", () => { - it("formats envelope variants", () => { - const whatsappDelivery: OutboundDeliveryJson = { - channel: "whatsapp", - via: "gateway", - to: "+1", - messageId: "m1", - mediaUrl: null, - }; - const telegramDelivery: OutboundDeliveryJson = { - channel: "telegram", - via: "direct", - to: "123", - messageId: "m2", - mediaUrl: null, - chatId: "c1", - }; - const discordDelivery: OutboundDeliveryJson = { - channel: "discord", - via: "gateway", - to: "channel:C1", - messageId: "m3", - mediaUrl: null, - channelId: "C1", - }; - const cases = typedCases<{ - name: string; - input: Parameters[0]; - expected: unknown; - }>([ - { - name: "flatten delivery by default", - input: { delivery: whatsappDelivery }, - expected: whatsappDelivery, - }, - { - name: "keep payloads + meta", - input: { - payloads: [{ text: "hi", mediaUrl: null, mediaUrls: undefined }], - meta: { foo: "bar" }, - }, - expected: { - payloads: [{ text: "hi", mediaUrl: null, mediaUrls: undefined }], - meta: { foo: "bar" }, - }, - }, - { - name: "include delivery when payloads exist", - input: { payloads: [], delivery: telegramDelivery, meta: { ok: true } }, - expected: { - payloads: [], - meta: { ok: true }, - delivery: telegramDelivery, - }, - }, - { - name: "keep wrapped delivery when flatten disabled", - input: { delivery: discordDelivery, flattenDelivery: false }, - expected: { delivery: discordDelivery }, - }, - ]); - for (const testCase of cases) { - expect(buildOutboundResultEnvelope(testCase.input), testCase.name).toEqual(testCase.expected); - } - }); -}); - describe("formatOutboundDeliverySummary", () => { it("formats fallback and channel-specific detail variants", () => { const cases = [ diff --git a/src/infra/ws.test.ts b/src/infra/ws.test.ts new file mode 100644 index 00000000000..be7a98c2133 --- /dev/null +++ b/src/infra/ws.test.ts @@ -0,0 +1,19 @@ +import { Buffer } from "node:buffer"; +import { describe, expect, it } from "vitest"; +import { rawDataToString } from "./ws.js"; + +describe("rawDataToString", () => { + it("returns string input unchanged", () => { + expect(rawDataToString("hello")).toBe("hello"); + }); + + it("decodes Buffer, Buffer[] and ArrayBuffer inputs", () => { + expect(rawDataToString(Buffer.from("hello"))).toBe("hello"); + expect(rawDataToString([Buffer.from("he"), Buffer.from("llo")])).toBe("hello"); + expect(rawDataToString(Uint8Array.from([104, 101, 108, 108, 111]).buffer)).toBe("hello"); + }); + + it("falls back to string coercion for other raw data shapes", () => { + expect(rawDataToString(Uint8Array.from([1, 2, 3]) as never)).toBe("1,2,3"); + }); +});