Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/firebase_functions/pubsub_fn.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,32 @@ class Message(_typing.Generic[T]):
Autogenerated ID that uniquely identifies this message.
"""

publish_time: str
publish_time: _dt.datetime
"""
Time the message was published.
Time the message was published, as a timezone-aware datetime.
"""

attributes: dict[str, str]
"""
User-defined attributes published with the message, if any.
"""

data: str
data: str | None = None
"""
The data payload of this message object as a base64-encoded string.
The data payload of this message object as a base64-encoded string,
or `None` if the message was published without a payload.
"""

ordering_key: str
ordering_key: str | None = None
"""
User-defined key used to ensure ordering amongst messages with the same key.
User-defined key used to ensure ordering amongst messages with the same key,
or `None` if the message was published without one.
"""

@property
def json(self) -> T | None:
try:
if self.data is not None:
if self.data:
return _json.loads(_base64.b64decode(self.data).decode("utf-8"))
else:
return None
Expand Down
139 changes: 85 additions & 54 deletions tests/test_pubsub_fn.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@
)


def _raw_event(message: dict, time: str = "2023-03-11T13:25:37.403Z") -> _CloudEvent:
return _CloudEvent(
attributes={
"id": "test-message",
"source": "https://example.com/pubsub",
"specversion": "1.0",
"time": time,
"type": "com.example.pubsub.message",
},
data={
"message": {
"message_id": "message-id-123",
"publish_time": time,
**message,
},
"subscription": "my-subscription",
},
)


def _handled_message(message: dict) -> Message:
func = MagicMock()
_message_handler(func, _raw_event(message))
func.assert_called_once()
return func.call_args.args[0].data.message


class TestPubSub(unittest.TestCase):
"""
PubSub function tests.
Expand All @@ -55,24 +82,12 @@ def test_message_handler(self):
formatted CloudEvent instance.
"""
func = MagicMock()
raw_event = _CloudEvent(
attributes={
"id": "test-message",
"source": "https://example.com/pubsub",
"specversion": "1.0",
"time": "2023-03-11T13:25:37.403Z",
"type": "com.example.pubsub.message",
},
data={
"message": {
"attributes": {"key": "value"},
# {"test": "value"}
"data": "eyJ0ZXN0IjogInZhbHVlIn0=",
"message_id": "message-id-123",
"publish_time": "2023-03-11T13:25:37.403Z",
},
"subscription": "my-subscription",
},
raw_event = _raw_event(
{
"attributes": {"key": "value"},
# {"test": "value"}
"data": "eyJ0ZXN0IjogInZhbHVlIn0=",
}
)

_message_handler(func, raw_event)
Expand Down Expand Up @@ -103,51 +118,67 @@ def init():
hello = "world"

func = MagicMock()
raw_event = _CloudEvent(
attributes={
"id": "test-message",
"source": "https://example.com/pubsub",
"specversion": "1.0",
"time": "2023-03-11T13:25:37.403Z",
"type": "com.example.pubsub.message",
},
data={
"message": {
"attributes": {"key": "value"},
"data": "eyJ0ZXN0IjogInZhbHVlIn0=",
"message_id": "message-id-123",
"publish_time": "2023-03-11T13:25:37.403Z",
},
"subscription": "my-subscription",
},
)
raw_event = _raw_event({"data": "eyJ0ZXN0IjogInZhbHVlIn0="})

_message_handler(func, raw_event)

self.assertEqual("world", hello)

def test_datetime_without_mircroseconds_doesnt_throw(self):
time = "2023-03-11T13:25:37Z"
raw_event = _CloudEvent(
attributes={
"id": "test-message",
"source": "https://example.com/pubsub",
"specversion": "1.0",
"time": time,
"type": "com.example.pubsub.message",
},
data={
"message": {
"attributes": {"key": "value"},
"data": "eyJ0ZXN0IjogInZhbHVlIn0=",
"message_id": "message-id-123",
"publish_time": time,
},
"subscription": "my-subscription",
},
)
raw_event = _raw_event({"data": "eyJ0ZXN0IjogInZhbHVlIn0="}, time=time)
try:
_message_handler(lambda _: None, raw_event)
# pylint: disable=broad-except
except Exception:
self.fail("Datetime without microseconds should not throw an exception")


class TestPubSubMessageData(unittest.TestCase):
def test_message_without_data_reaches_handler_with_none(self):
message = _handled_message({"attributes": {"key": "value"}})
self.assertIsNone(message.data)
self.assertIsNone(message.json)
self.assertEqual(message.attributes, {"key": "value"})
self.assertEqual(message.message_id, "message-id-123")

def test_message_with_data_decodes_json(self):
# base64 of '{"test": "value"}'
message = _handled_message({"data": "eyJ0ZXN0IjogInZhbHVlIn0="})
self.assertEqual(message.data, "eyJ0ZXN0IjogInZhbHVlIn0=")
self.assertEqual(message.json, {"test": "value"})
self.assertEqual(message.attributes, {})

def test_message_with_empty_string_data_returns_none_json(self):
message = _handled_message({"data": ""})
self.assertEqual(message.data, "")
self.assertIsNone(message.json)

def test_message_with_invalid_data_json_raises(self):
# base64 of "not json"
message = _handled_message({"data": "bm90IGpzb24="})
with self.assertRaisesRegex(ValueError, "^Unable to parse Pub/Sub message data as JSON: "):
_ = message.json

def test_message_with_ordering_key_keeps_it(self):
message = _handled_message({"data": "eyJ0ZXN0IjogInZhbHVlIn0=", "orderingKey": "abc"})
self.assertEqual(message.ordering_key, "abc")

def test_message_built_directly_defaults_data_and_ordering_key_to_none(self):
message = Message(
message_id="message-id-123",
publish_time=_dt.datetime(2023, 3, 11, tzinfo=_dt.timezone.utc),
attributes={},
)
self.assertIsNone(message.data)
self.assertIsNone(message.ordering_key)
self.assertIsNone(message.json)

def test_publish_time_is_timezone_aware_datetime(self):
message = _handled_message({"data": "eyJ0ZXN0IjogInZhbHVlIn0="})
self.assertIsInstance(message.publish_time, _dt.datetime)
self.assertEqual(
message.publish_time,
_dt.datetime(2023, 3, 11, 13, 25, 37, 403000, tzinfo=_dt.timezone.utc),
)
self.assertEqual(message.publish_time.utcoffset(), _dt.timedelta(0))
Loading