From 7ef48bf81adc784b420d4ed6d47c9b1c7eb05fb7 Mon Sep 17 00:00:00 2001 From: winklemad Date: Wed, 22 Jul 2026 08:23:27 +0530 Subject: [PATCH] fix: marshal attribute-only Pub/Sub messages --- src/pubsub_middleware.ts | 10 +++++---- test/integration/cloud_event.ts | 32 +++++++++++++++++++++++++++ test/integration/legacy_event.ts | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/pubsub_middleware.ts b/src/pubsub_middleware.ts index 5ba433137..eb0c312fa 100644 --- a/src/pubsub_middleware.ts +++ b/src/pubsub_middleware.ts @@ -46,7 +46,7 @@ export interface RawPubSubBody { * Base64 encoded message data. If this field is empty, the message must contain at least one * attribute. */ - data: string; + data?: string; /** * ID of this message, assigned by the server when the message is published. Guaranteed to be * unique within the topic. @@ -82,7 +82,7 @@ export interface MarshalledPubSubBody { }; data: { '@type': typeof PUBSUB_MESSAGE_TYPE; - data: string; + data?: string; attributes: {[key: string]: string}; }; } @@ -99,7 +99,9 @@ const isRawPubSubRequestBody = (body: any): body is RawPubSubBody => { !body.context && body.subscription && body.message && - body.message.data && + (body.message.data || + (body.message.attributes && + Object.keys(body.message.attributes).length > 0)) && body.message.messageId ); }; @@ -145,7 +147,7 @@ const marshalPubSubRequestBody = ( }, data: { '@type': PUBSUB_MESSAGE_TYPE, - data: body.message.data, + ...(body.message.data !== undefined && {data: body.message.data}), attributes: body.message.attributes || {}, }, }); diff --git a/test/integration/cloud_event.ts b/test/integration/cloud_event.ts index 679ef8d6c..b1dd61eb9 100644 --- a/test/integration/cloud_event.ts +++ b/test/integration/cloud_event.ts @@ -291,6 +291,38 @@ describe('CloudEvent Function', () => { }); }); + it('marshals attribute-only Pub/Sub emulator requests', async () => { + const server = getTestServer('testCloudEventFunction'); + await supertest(server) + .post('/projects/FOO/topics/BAR_TOPIC') + .send({ + subscription: 'projects/FOO/subscriptions/BAR_SUB', + message: { + messageId: 'attribute-only-message', + publishTime: '2026-07-22T00:00:00.000Z', + attributes: {attribute1: 'value1'}, + }, + }) + .expect(204); + + assert.deepStrictEqual(receivedCloudEvent, { + specversion: '1.0', + type: 'google.cloud.pubsub.topic.v1.messagePublished', + source: '//pubsub.googleapis.com/projects/FOO/topics/BAR_TOPIC', + id: 'attribute-only-message', + time: '2026-07-22T00:00:00.000Z', + datacontenttype: 'application/json', + data: { + message: { + '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', + attributes: {attribute1: 'value1'}, + messageId: 'attribute-only-message', + publishTime: '2026-07-22T00:00:00.000Z', + }, + }, + }); + }); + it('allows customers to provide a type parameter for the data payload', async () => { const testPayload = 'a test string'; diff --git a/test/integration/legacy_event.ts b/test/integration/legacy_event.ts index 231d0f62e..fa82ea7f7 100644 --- a/test/integration/legacy_event.ts +++ b/test/integration/legacy_event.ts @@ -211,6 +211,43 @@ describe('Event Function', () => { }); }); + it('marshals attribute-only Pub/Sub emulator requests', async () => { + let receivedData: {} | null = null; + let receivedContext: functions.CloudFunctionsContext | null = null; + const server = getServer((data: {}, context: functions.Context) => { + receivedData = data; + receivedContext = context as functions.CloudFunctionsContext; + }, testOptions); + + await supertest(server) + .post('/projects/FOO/topics/BAR_TOPIC') + .send({ + subscription: 'projects/FOO/subscriptions/BAR_SUB', + message: { + messageId: 'attribute-only-message', + publishTime: '2026-07-22T00:00:00.000Z', + attributes: {attribute1: 'value1'}, + }, + }) + .set({'Content-Type': 'application/json'}) + .expect(204); + + assert.deepStrictEqual(receivedData, { + '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', + attributes: {attribute1: 'value1'}, + }); + assert.deepStrictEqual(receivedContext, { + eventId: 'attribute-only-message', + timestamp: '2026-07-22T00:00:00.000Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + type: 'type.googleapis.com/google.pubsub.v1.PubsubMessage', + name: 'projects/FOO/topics/BAR_TOPIC', + }, + }); + }); + it('returns a 500 if the function throws an exception', async () => { const server = getServer(() => { throw 'I crashed';