diff --git a/firebase_admin/messaging.py b/firebase_admin/messaging.py index 93ecfda1..14f5944f 100644 --- a/firebase_admin/messaging.py +++ b/firebase_admin/messaging.py @@ -136,7 +136,7 @@ def send_each( recipients. Instead, FCM performs all the usual validations and emulates the send operation. Args: - messages: A list of ``messaging.Message`` instances. + messages: A non-empty list of up to 500 ``messaging.Message`` instances. dry_run: A boolean indicating whether to run the operation in dry run mode (optional). app: An App instance (optional). @@ -160,7 +160,7 @@ async def send_each_async( recipients. Instead, FCM performs all the usual validations and emulates the send operation. Args: - messages: A list of ``messaging.Message`` instances. + messages: A non-empty list of up to 500 ``messaging.Message`` instances. dry_run: A boolean indicating whether to run the operation in dry run mode (optional). app: An App instance (optional). @@ -203,6 +203,8 @@ def _get_messages_from_multicast(multicast_message: MulticastMessage) -> List[Me fid=fid ) for fid in multicast_message.fids]) + if not messages: + raise ValueError('multicast_message must contain at least one token or fid.') return messages async def send_each_for_multicast_async( @@ -217,7 +219,8 @@ async def send_each_for_multicast_async( recipients. Instead, FCM performs all the usual validations and emulates the send operation. Args: - multicast_message: An instance of ``messaging.MulticastMessage``. + multicast_message: An instance of ``messaging.MulticastMessage`` with at least one + token or fid. dry_run: A boolean indicating whether to run the operation in dry run mode (optional). app: An App instance (optional). @@ -238,7 +241,8 @@ def send_each_for_multicast(multicast_message, dry_run=False, app=None): recipients. Instead, FCM performs all the usual validations and emulates the send operation. Args: - multicast_message: An instance of ``messaging.MulticastMessage``. + multicast_message: An instance of ``messaging.MulticastMessage`` with at least one + token or fid. dry_run: A boolean indicating whether to run the operation in dry run mode (optional). app: An App instance (optional). @@ -445,6 +449,8 @@ def send_each(self, messages: List[Message], dry_run: bool = False) -> BatchResp """Sends the given messages to FCM via the FCM v1 API.""" if not isinstance(messages, list): raise ValueError('messages must be a list of messaging.Message instances.') + if not messages: + raise ValueError('messages must not be empty.') if len(messages) > 500: raise ValueError('messages must not contain more than 500 elements.') @@ -473,6 +479,8 @@ async def send_each_async(self, messages: List[Message], dry_run: bool = True) - """Sends the given messages to FCM via the FCM v1 API.""" if not isinstance(messages, list): raise ValueError('messages must be a list of messaging.Message instances.') + if not messages: + raise ValueError('messages must not be empty.') if len(messages) > 500: raise ValueError('messages must not contain more than 500 elements.') diff --git a/tests/test_messaging.py b/tests/test_messaging.py index 749e5311..3730d9cb 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -1953,6 +1953,15 @@ def test_invalid_send_each(self, msg): expected = 'messages must be a list of messaging.Message instances.' assert str(excinfo.value) == expected + def test_send_each_empty_batch(self): + with pytest.raises(ValueError, match='messages must not be empty.'): + messaging.send_each([]) + + @pytest.mark.asyncio + async def test_send_each_async_empty_batch(self): + with pytest.raises(ValueError, match='messages must not be empty.'): + await messaging.send_each_async([]) + def test_invalid_over_500(self): msg = messaging.Message(topic='foo') with pytest.raises(ValueError) as excinfo: @@ -2273,6 +2282,23 @@ def test_invalid_send_each_for_multicast(self, msg): expected = 'Message must be an instance of messaging.MulticastMessage class.' assert str(excinfo.value) == expected + @pytest.mark.parametrize('recipients', [{'tokens': []}, {'fids': []}, + {'tokens': [], 'fids': []}]) + def test_send_each_for_multicast_empty_batch(self, recipients): + msg = messaging.MulticastMessage(**recipients) + with pytest.raises( + ValueError, match='multicast_message must contain at least one token or fid.'): + messaging.send_each_for_multicast(msg) + + @pytest.mark.asyncio + @pytest.mark.parametrize('recipients', [{'tokens': []}, {'fids': []}, + {'tokens': [], 'fids': []}]) + async def test_send_each_for_multicast_async_empty_batch(self, recipients): + msg = messaging.MulticastMessage(**recipients) + with pytest.raises( + ValueError, match='multicast_message must contain at least one token or fid.'): + await messaging.send_each_for_multicast_async(msg) + def test_send_each_for_multicast(self): payload1 = json.dumps({'name': 'message-id1'}) payload2 = json.dumps({'name': 'message-id2'})