From 4b1029aeaebc8d5606c51502aa9e425840281acf Mon Sep 17 00:00:00 2001 From: WhangSangGun Date: Thu, 3 Sep 2026 15:10:07 +0900 Subject: [PATCH] Test enqueueMany() wrapped message shape WorkersMessageQueue.enqueueMany() uses sendBatch() while enqueue() uses send(), so nothing previously caught the two paths drifting apart in how they wrap a message. Add tests that check the wrapped shape enqueueMany() produces, compare it against what enqueue() produces for the same input, and confirm the ordering key is left undefined when none is given. Refs https://github.com/fedify-dev/fedify/issues/878 Assisted-by: Claude Code:claude-sonnet-5 --- packages/cfworkers/src/mod.test.ts | 95 ++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/packages/cfworkers/src/mod.test.ts b/packages/cfworkers/src/mod.test.ts index 0ed720606..c6a341d4b 100644 --- a/packages/cfworkers/src/mod.test.ts +++ b/packages/cfworkers/src/mod.test.ts @@ -4,6 +4,7 @@ import { WorkersKvStore, WorkersMessageQueue, } from "./mod.ts"; +import type { Queue } from "@cloudflare/workers-types"; // Mock Temporal.Duration for testing in Cloudflare Workers environment const mockDuration = (seconds: number) => ({ @@ -352,3 +353,97 @@ describe("WorkersMessageQueue", () => { expect(second.message).toEqual({ id: "second" }); }); }); + +interface MockWrappedMessage { + readonly __fedify_ordering_key__?: string; + readonly __fedify_payload__: unknown; +} + +interface MockSendBatchOptions { + readonly delaySeconds?: number; +} + +interface MockSendOptions { + readonly contentType?: string; + readonly delaySeconds?: number; +} + +interface MockMessageSendRequest { + readonly body: MockWrappedMessage; + readonly contentType?: string; +} + +class MockQueue { + sentSingles: { + wrapped: MockWrappedMessage; + options?: MockSendOptions; + }[] = []; + sentBatches: { + batch: MockMessageSendRequest[]; + options?: MockSendBatchOptions; + }[] = []; + + send(wrapped: MockWrappedMessage, options?: MockSendOptions) { + this.sentSingles.push({ wrapped, options }); + } + + sendBatch(batch: MockMessageSendRequest[], options?: MockSendBatchOptions) { + this.sentBatches.push({ batch, options }); + } +} + +describe("WorkersMessageQueue.enqueueMany() - wrapped message shape", () => { + it("enqueueMany() - wraps each message with body{} and contentType", async () => { + const sendingMockQueue = new MockQueue(); + const queue = new WorkersMessageQueue(sendingMockQueue as unknown as Queue); + + await queue.enqueueMany(["msg-1", "msg-2"], { orderingKey: "ferer" }); + + expect(sendingMockQueue.sentBatches).toHaveLength(1); + expect(sendingMockQueue.sentBatches[0].batch).toEqual([ + { + body: { + __fedify_ordering_key__: "ferer", + __fedify_payload__: "msg-1", + }, + contentType: "json", + }, + { + body: { + __fedify_ordering_key__: "ferer", + __fedify_payload__: "msg-2", + }, + contentType: "json", + }, + ]); + }); + + it("enqueue() and enqueueMany() - produce the same wrapped shape", async () => { + const sendingMockQueue = new MockQueue(); + const queue = new WorkersMessageQueue(sendingMockQueue as unknown as Queue); + + await queue.enqueue("msg-1", { orderingKey: "ferer" }); + await queue.enqueueMany(["msg-1"], { orderingKey: "ferer" }); + + // enqueueMany() wraps each body in a request ({ body, contentType }), so + // unwrap it before comparing against enqueue()'s bare wrapped message. + const singleWrapped = sendingMockQueue.sentSingles[0].wrapped; + const batchWrapped = sendingMockQueue.sentBatches[0].batch[0].body; + + expect(batchWrapped).toEqual(singleWrapped); + }); + + it("enqueueMany() - omits ordering key when not provided", async () => { + const sendingMockQueue = new MockQueue(); + const queue = new WorkersMessageQueue(sendingMockQueue as unknown as Queue); + + await queue.enqueueMany(["msg-1"]); + + expect(sendingMockQueue.sentBatches[0].batch[0].body).toEqual( + { + __fedify_ordering_key__: undefined, + __fedify_payload__: "msg-1", + }, + ); + }); +});