diff --git a/src/firebase_functions/pubsub_fn.py b/src/firebase_functions/pubsub_fn.py index 8733822..bce81f7 100644 --- a/src/firebase_functions/pubsub_fn.py +++ b/src/firebase_functions/pubsub_fn.py @@ -87,9 +87,9 @@ class MessagePublishedData(_typing.Generic[T]): Google Cloud Pub/Sub message. """ - subscription: str + subscription: str | None """ - A subscription resource. + A subscription resource. ``None`` when the request had no CloudEvent headers. """ @@ -107,6 +107,12 @@ def _message_handler( data = event_dict["data"] message_dict = data["message"] + # When a request has no CloudEvent headers, functions-framework builds the + # message itself with camelCase keys only, an "@type" key and no subscription. + message_dict.setdefault("message_id", message_dict.get("messageId")) + message_dict.setdefault("publish_time", message_dict.get("publishTime")) + message_dict.pop("@type", None) + # if no microseconds are present, we should set them to 0 to prevent parsing from failing if "." not in event_dict["time"]: event_dict["time"] = event_dict["time"].replace("Z", ".000000Z") @@ -147,7 +153,7 @@ def _message_handler( **message_dict, ordering_key=ordering_key, ), - subscription=data["subscription"], + subscription=data.get("subscription"), ) event_dict["data"] = message diff --git a/tests/test_pubsub_fn.py b/tests/test_pubsub_fn.py index d1c4c56..7feba0e 100644 --- a/tests/test_pubsub_fn.py +++ b/tests/test_pubsub_fn.py @@ -18,6 +18,8 @@ from unittest.mock import MagicMock from cloudevents.http import CloudEvent as _CloudEvent +from flask import Flask, request +from functions_framework import event_conversion from firebase_functions import core from firebase_functions.pubsub_fn import ( @@ -182,3 +184,29 @@ def test_publish_time_is_timezone_aware_datetime(self): _dt.datetime(2023, 3, 11, 13, 25, 37, 403000, tzinfo=_dt.timezone.utc), ) self.assertEqual(message.publish_time.utcoffset(), _dt.timedelta(0)) + + def test_push_body_without_cloudevent_headers_reaches_handler(self): + push_body = { + "message": { + "attributes": {"key": "value"}, + "data": "eyJ0ZXN0IjogInZhbHVlIn0=", + "messageId": "message-id-123", + "publishTime": "2023-03-11T13:25:37.403Z", + }, + "subscription": "projects/p/subscriptions/s", + } + with Flask(__name__).test_request_context("/", method="POST", json=push_body): + raw = event_conversion.background_event_to_cloud_event(request) + + func = MagicMock() + _message_handler(func, raw) + + data = func.call_args.args[0].data + self.assertEqual(data.message.message_id, "message-id-123") + self.assertEqual( + data.message.publish_time, + _dt.datetime(2023, 3, 11, 13, 25, 37, 403000, tzinfo=_dt.timezone.utc), + ) + self.assertEqual(data.message.attributes, {"key": "value"}) + self.assertEqual(data.message.json, {"test": "value"}) + self.assertIsNone(data.subscription)