From f75af608117522f56740a4cad1341904e5f0fbd8 Mon Sep 17 00:00:00 2001 From: prsdx Date: Sun, 25 Oct 2026 10:31:40 +0530 Subject: [PATCH] zulip: Exclude message_id from request body in update_message and reactions. In update_message, add_reaction, and remove_reaction, message_id is embedded directly in the endpoint URL path. Forwarding message_id in the request payload dictionary causes the Zulip server to return an ignored_parameters_unsupported warning. Strip message_id from the forwarded payload dictionary without mutating the caller's input dictionary. Fixes: #789. Signed-off-by: prsdx --- zulip/tests/test_message_endpoints.py | 92 +++++++++++++++++++++++++++ zulip/zulip/__init__.py | 9 ++- 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 zulip/tests/test_message_endpoints.py diff --git a/zulip/tests/test_message_endpoints.py b/zulip/tests/test_message_endpoints.py new file mode 100644 index 000000000..57f00387d --- /dev/null +++ b/zulip/tests/test_message_endpoints.py @@ -0,0 +1,92 @@ +from unittest import TestCase +from unittest.mock import patch + +from typing_extensions import override + +import zulip + + +class TestMessageEndpoints(TestCase): + @override + def setUp(self) -> None: + with patch.object( + zulip.Client, + "get_server_settings", + return_value={"zulip_version": "8.0", "zulip_feature_level": 250}, + ): + self.client = zulip.Client( + email="test@example.com", + api_key="1234567890abcdef", + site="https://zulip.example.com", + ) + + def test_update_message_strips_message_id_from_request_payload(self) -> None: + payload = { + "message_id": 42, + "topic": "new topic", + "content": "updated content", + } + with patch.object( + self.client, "call_endpoint", return_value={"result": "success"} + ) as mock_call: + res = self.client.update_message(payload) + self.assertEqual(res, {"result": "success"}) + + # URL path includes message_id, but request body excludes it + mock_call.assert_called_once_with( + url="messages/42", + method="PATCH", + request={"topic": "new topic", "content": "updated content"}, + ) + + # Original dictionary should remain unmutated + self.assertIn("message_id", payload) + self.assertEqual(payload["message_id"], 42) + + def test_add_reaction_strips_message_id_from_request_payload(self) -> None: + payload = { + "message_id": 100, + "emoji_name": "joy", + "emoji_code": "1f602", + "reaction_type": "unicode_emoji", + } + with patch.object( + self.client, "call_endpoint", return_value={"result": "success"} + ) as mock_call: + res = self.client.add_reaction(payload) + self.assertEqual(res, {"result": "success"}) + + mock_call.assert_called_once_with( + url="messages/100/reactions", + method="POST", + request={ + "emoji_name": "joy", + "emoji_code": "1f602", + "reaction_type": "unicode_emoji", + }, + ) + self.assertIn("message_id", payload) + + def test_remove_reaction_strips_message_id_from_request_payload(self) -> None: + payload = { + "message_id": 100, + "emoji_name": "joy", + "emoji_code": "1f602", + "reaction_type": "unicode_emoji", + } + with patch.object( + self.client, "call_endpoint", return_value={"result": "success"} + ) as mock_call: + res = self.client.remove_reaction(payload) + self.assertEqual(res, {"result": "success"}) + + mock_call.assert_called_once_with( + url="messages/100/reactions", + method="DELETE", + request={ + "emoji_name": "joy", + "emoji_code": "1f602", + "reaction_type": "unicode_emoji", + }, + ) + self.assertIn("message_id", payload) diff --git a/zulip/zulip/__init__.py b/zulip/zulip/__init__.py index 6d725a10b..3145141c6 100644 --- a/zulip/zulip/__init__.py +++ b/zulip/zulip/__init__.py @@ -889,10 +889,11 @@ def update_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]: """ See examples/edit-message for example usage. """ + request = {k: v for k, v in message_data.items() if k != "message_id"} return self.call_endpoint( url="messages/%d" % (message_data["message_id"],), method="PATCH", - request=message_data, + request=request, ) def delete_message(self, message_id: int) -> Dict[str, Any]: @@ -966,10 +967,11 @@ def add_reaction(self, reaction_data: Dict[str, Any]) -> Dict[str, Any]: }) {'result': 'success', 'msg': ''} """ + request = {k: v for k, v in reaction_data.items() if k != "message_id"} return self.call_endpoint( url="messages/{}/reactions".format(reaction_data["message_id"]), method="POST", - request=reaction_data, + request=request, ) def remove_reaction(self, reaction_data: Dict[str, Any]) -> Dict[str, Any]: @@ -984,10 +986,11 @@ def remove_reaction(self, reaction_data: Dict[str, Any]) -> Dict[str, Any]: }) {'msg': '', 'result': 'success'} """ + request = {k: v for k, v in reaction_data.items() if k != "message_id"} return self.call_endpoint( url="messages/{}/reactions".format(reaction_data["message_id"]), method="DELETE", - request=reaction_data, + request=request, ) def get_realm_emoji(self) -> Dict[str, Any]: