From 9c8cead668b912634d24a4217528e19e24d5e775 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 10 Sep 2024 13:30:13 -0400 Subject: [PATCH] Fix IMAP identifiers not encoding correctly (#383) IMAP identifiers sometimes have characters that do not work well in the URL, including slashes, that would make the request fail. For these endpoints we ensure that we are encoding these IDs properly. --- CHANGELOG.md | 4 ++ nylas/resources/drafts.py | 9 ++-- nylas/resources/messages.py | 8 ++-- nylas/resources/threads.py | 8 ++-- tests/resources/test_drafts.py | 72 ++++++++++++++++++++++++++++++++ tests/resources/test_messages.py | 58 +++++++++++++++++++++++++ tests/resources/test_threads.py | 57 +++++++++++++++++++++++++ 7 files changed, 206 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a963084..6c51a4ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ nylas-python Changelog ====================== +Unreleased +---------------- +* Fix IMAP identifiers not encoding correctly + v6.3.1 ---------------- * Fix typo on Clean Messages diff --git a/nylas/resources/drafts.py b/nylas/resources/drafts.py index 1a18546e..23146d88 100644 --- a/nylas/resources/drafts.py +++ b/nylas/resources/drafts.py @@ -1,4 +1,5 @@ import io +import urllib.parse from typing import Optional from nylas.config import RequestOverrides @@ -79,7 +80,7 @@ def find( The requested Draft. """ return super().find( - path=f"/v3/grants/{identifier}/drafts/{draft_id}", + path=f"/v3/grants/{identifier}/drafts/{urllib.parse.quote(draft_id, safe='')}", response_type=Draft, overrides=overrides, ) @@ -149,7 +150,7 @@ def update( Returns: The updated Draft. """ - path = f"/v3/grants/{identifier}/drafts/{draft_id}" + path = f"/v3/grants/{identifier}/drafts/{urllib.parse.quote(draft_id, safe='')}" # Use form data only if the attachment size is greater than 3mb attachment_size = sum( @@ -196,7 +197,7 @@ def destroy( The deletion response. """ return super().destroy( - path=f"/v3/grants/{identifier}/drafts/{draft_id}", + path=f"/v3/grants/{identifier}/drafts/{urllib.parse.quote(draft_id, safe='')}", overrides=overrides, ) @@ -216,7 +217,7 @@ def send( """ json_response = self._http_client._execute( method="POST", - path=f"/v3/grants/{identifier}/drafts/{draft_id}", + path=f"/v3/grants/{identifier}/drafts/{urllib.parse.quote(draft_id, safe='')}", overrides=overrides, ) diff --git a/nylas/resources/messages.py b/nylas/resources/messages.py index 4fd22ed0..7687335f 100644 --- a/nylas/resources/messages.py +++ b/nylas/resources/messages.py @@ -1,4 +1,5 @@ import io +import urllib.parse from typing import Optional, List from nylas.config import RequestOverrides @@ -96,7 +97,7 @@ def find( The requested Message. """ return super().find( - path=f"/v3/grants/{identifier}/messages/{message_id}", + path=f"/v3/grants/{identifier}/messages/{urllib.parse.quote(message_id, safe='')}", response_type=Message, query_params=query_params, overrides=overrides, @@ -122,7 +123,7 @@ def update( The updated Message. """ return super().update( - path=f"/v3/grants/{identifier}/messages/{message_id}", + path=f"/v3/grants/{identifier}/messages/{urllib.parse.quote(message_id, safe='')}", response_type=Message, request_body=request_body, overrides=overrides, @@ -143,7 +144,8 @@ def destroy( The deletion response. """ return super().destroy( - path=f"/v3/grants/{identifier}/messages/{message_id}", overrides=overrides + path=f"/v3/grants/{identifier}/messages/{urllib.parse.quote(message_id, safe='')}", + overrides=overrides, ) def send( diff --git a/nylas/resources/threads.py b/nylas/resources/threads.py index 712199a1..521ef918 100644 --- a/nylas/resources/threads.py +++ b/nylas/resources/threads.py @@ -1,3 +1,4 @@ +import urllib.parse from nylas.config import RequestOverrides from nylas.handler.api_resources import ( ListableApiResource, @@ -60,7 +61,7 @@ def find( The requested Thread. """ return super().find( - path=f"/v3/grants/{identifier}/threads/{thread_id}", + path=f"/v3/grants/{identifier}/threads/{urllib.parse.quote(thread_id, safe='')}", response_type=Thread, overrides=overrides, ) @@ -85,7 +86,7 @@ def update( The updated Thread. """ return super().update( - path=f"/v3/grants/{identifier}/threads/{thread_id}", + path=f"/v3/grants/{identifier}/threads/{urllib.parse.quote(thread_id, safe='')}", response_type=Thread, request_body=request_body, overrides=overrides, @@ -106,5 +107,6 @@ def destroy( The deletion response. """ return super().destroy( - path=f"/v3/grants/{identifier}/threads/{thread_id}", overrides=overrides + path=f"/v3/grants/{identifier}/threads/{urllib.parse.quote(thread_id, safe='')}", + overrides=overrides, ) diff --git a/tests/resources/test_drafts.py b/tests/resources/test_drafts.py index 26e2a591..ecd51ebd 100644 --- a/tests/resources/test_drafts.py +++ b/tests/resources/test_drafts.py @@ -106,6 +106,23 @@ def test_find_draft(self, http_client_response): overrides=None, ) + def test_find_draft_encoded_id(self, http_client_response): + drafts = Drafts(http_client_response) + + drafts.find( + identifier="abc-123", + draft_id="", + ) + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/drafts/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + def test_create_draft(self, http_client_response): drafts = Drafts(http_client_response) request_body = { @@ -206,6 +223,30 @@ def test_update_draft(self, http_client_response): overrides=None, ) + def test_update_draft_encoded_id(self, http_client_response): + drafts = Drafts(http_client_response) + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "cc": [{"name": "Arya Stark", "email": "astark@gmail.com"}], + "body": "This is the body of my draft message.", + } + + drafts.update( + identifier="abc-123", + draft_id="", + request_body=request_body, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/drafts/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + request_body, + overrides=None, + ) + def test_update_draft_small_attachment(self, http_client_response): drafts = Drafts(http_client_response) request_body = { @@ -282,6 +323,23 @@ def test_destroy_draft(self, http_client_delete_response): overrides=None, ) + def test_destroy_draft_encoded_id(self, http_client_delete_response): + drafts = Drafts(http_client_delete_response) + + drafts.destroy( + identifier="abc-123", + draft_id="", + ) + + http_client_delete_response._execute.assert_called_once_with( + "DELETE", + "/v3/grants/abc-123/drafts/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + def test_send_draft(self, http_client_response): drafts = Drafts(http_client_response) @@ -290,3 +348,17 @@ def test_send_draft(self, http_client_response): http_client_response._execute.assert_called_once_with( method="POST", path="/v3/grants/abc-123/drafts/draft-123", overrides=None ) + + def test_send_draft_encoded_id(self, http_client_response): + drafts = Drafts(http_client_response) + + drafts.send( + identifier="abc-123", + draft_id="", + ) + + http_client_response._execute.assert_called_once_with( + method="POST", + path="/v3/grants/abc-123/drafts/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + overrides=None, + ) diff --git a/tests/resources/test_messages.py b/tests/resources/test_messages.py index 1a977909..1af9a957 100644 --- a/tests/resources/test_messages.py +++ b/tests/resources/test_messages.py @@ -109,6 +109,23 @@ def test_find_message(self, http_client_response): overrides=None, ) + def test_find_message_encoded_id(self, http_client_response): + messages = Messages(http_client_response) + + messages.find( + identifier="abc-123", + message_id="", + ) + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + def test_find_message_with_query_params(self, http_client_response): messages = Messages(http_client_response) @@ -151,6 +168,30 @@ def test_update_message(self, http_client_response): overrides=None, ) + def test_update_message_encoded_id(self, http_client_response): + messages = Messages(http_client_response) + request_body = { + "starred": True, + "unread": False, + "folders": ["folder-123"], + "metadata": {"foo": "bar"}, + } + + messages.update( + identifier="abc-123", + message_id="", + request_body=request_body, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + request_body, + overrides=None, + ) + def test_destroy_message(self, http_client_delete_response): messages = Messages(http_client_delete_response) @@ -165,6 +206,23 @@ def test_destroy_message(self, http_client_delete_response): overrides=None, ) + def test_destroy_message_encoded_id(self, http_client_delete_response): + messages = Messages(http_client_delete_response) + + messages.destroy( + identifier="abc-123", + message_id="", + ) + + http_client_delete_response._execute.assert_called_once_with( + "DELETE", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + def test_send_message(self, http_client_response): messages = Messages(http_client_response) request_body = { diff --git a/tests/resources/test_threads.py b/tests/resources/test_threads.py index 73df4ec8..47c9cbec 100644 --- a/tests/resources/test_threads.py +++ b/tests/resources/test_threads.py @@ -161,6 +161,23 @@ def test_find_thread(self, http_client_response): overrides=None, ) + def test_find_thread_encoded_id(self, http_client_response): + threads = Threads(http_client_response) + + threads.find( + identifier="abc-123", + thread_id="", + ) + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/threads/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + def test_update_thread(self, http_client_response): threads = Threads(http_client_response) request_body = { @@ -182,6 +199,29 @@ def test_update_thread(self, http_client_response): overrides=None, ) + def test_update_thread_encoded_id(self, http_client_response): + threads = Threads(http_client_response) + request_body = { + "starred": True, + "unread": False, + "folders": ["folder-123"], + } + + threads.update( + identifier="abc-123", + thread_id="", + request_body=request_body, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/threads/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + request_body, + overrides=None, + ) + def test_destroy_thread(self, http_client_delete_response): threads = Threads(http_client_delete_response) @@ -195,3 +235,20 @@ def test_destroy_thread(self, http_client_delete_response): None, overrides=None, ) + + def test_destroy_thread_encode_id(self, http_client_delete_response): + threads = Threads(http_client_delete_response) + + threads.destroy( + identifier="abc-123", + thread_id="", + ) + + http_client_delete_response._execute.assert_called_once_with( + "DELETE", + "/v3/grants/abc-123/threads/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + )