diff --git a/src/infra/outbound/message-action-params.attachments.test.ts b/src/infra/outbound/message-action-params.attachments.test.ts index 2d539f0ab82c..264926efe193 100644 --- a/src/infra/outbound/message-action-params.attachments.test.ts +++ b/src/infra/outbound/message-action-params.attachments.test.ts @@ -29,6 +29,7 @@ const onePixelPng = Buffer.from( const onePixelPngBase64 = onePixelPng.toString("base64"); const wrappedOnePixelPngBase64 = onePixelPngBase64.match(/.{1,24}/g)?.join("\r\n") ?? ""; const parameterizedPngDataUrl = `data:image/png;charset=utf-8;name=../../ignored.svg;base64,${wrappedOnePixelPngBase64}`; +const csvBase64 = Buffer.from("name,value\nexample,1\n").toString("base64"); function firstMockArg( mock: { mock: { calls: readonly unknown[][] } }, @@ -106,10 +107,29 @@ describe("runMessageAction media behavior", () => { await resetMessageActionMediaMocks(); }); - it.each(["send", "sendAttachment", "reply", "upload-file", "setGroupIcon"] as const)( - "normalizes parameterized, line-wrapped image data URLs for %s", - async (action) => { - const args: Record = { buffer: parameterizedPngDataUrl }; + it.each( + (["send", "sendAttachment", "reply", "upload-file", "setGroupIcon"] as const).flatMap( + (action) => [ + { + action, + buffer: parameterizedPngDataUrl, + base64: onePixelPngBase64, + contentType: "image/png", + filename: "attachment.png", + }, + { + action, + buffer: `data:text/csv;base64,${csvBase64}`, + base64: csvBase64, + contentType: "text/csv", + filename: "attachment.csv", + }, + ], + ), + )( + "normalizes $contentType data URLs and infers filenames for $action", + async ({ action, buffer, base64, contentType, filename }) => { + const args: Record = { buffer }; await hydrateAttachmentParamsForAction({ cfg: {}, @@ -120,14 +140,13 @@ describe("runMessageAction media behavior", () => { mediaPolicy: { mode: "host" }, }); - expect(args.contentType).toBe("image/png"); + expect(args.contentType).toBe(contentType); + expect(args.filename).toBe(filename); if (action === "send") { expect(args.media).toBe("buffer://message-send/attachment"); - expect(args.filename).toBe("attachment.png"); } else { - expect(canonicalizeBase64(String(args.buffer))).toBe(onePixelPngBase64); + expect(canonicalizeBase64(String(args.buffer))).toBe(base64); } - expect(args.filename).not.toBe("../../ignored.svg"); }, ); @@ -329,6 +348,7 @@ describe("runMessageAction media behavior", () => { const payload = requireActionPayload(result); expect(payload.contentType).toBe("image/png"); + expect(payload.filename).toBe("attachment.png"); expect(canonicalizeBase64(String(payload.buffer))).toBe(onePixelPngBase64); }, ); @@ -617,6 +637,7 @@ describe("runMessageAction media behavior", () => { const handlerParams = firstMockArg(handleActionMock, "handleAction"); expect(handlerParams.contentType).toBe("image/png"); + expect(handlerParams.filename).toBe("attachment.png"); expect(canonicalizeBase64(String(handlerParams.buffer))).toBe(onePixelPngBase64); }); diff --git a/src/infra/outbound/message-action-params.ts b/src/infra/outbound/message-action-params.ts index 920ea77d6b1d..e19135cf293e 100644 --- a/src/infra/outbound/message-action-params.ts +++ b/src/infra/outbound/message-action-params.ts @@ -538,7 +538,7 @@ async function hydrateAttachmentPayload(params: { } else if (!filename) { params.args.filename = inferAttachmentFilename({ mediaHint: mediaSource, - contentType: contentTypeParam ?? undefined, + contentType: normalized.contentType, }); } }