From d3f792ba344b343dcd5708e8a591bf54db50523c Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Fri, 14 Feb 2025 11:12:58 -0700 Subject: [PATCH 1/3] test: additional params in webhook create/update calls --- tests/cassettes/test_webhook_create.yaml | 37 ++++++++-------- tests/cassettes/test_webhook_update.yaml | 54 ++++++++++++------------ tests/test_webhook.py | 8 ++-- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/tests/cassettes/test_webhook_create.yaml b/tests/cassettes/test_webhook_create.yaml index 3ce6296d..a68b4a9a 100644 --- a/tests/cassettes/test_webhook_create.yaml +++ b/tests/cassettes/test_webhook_create.yaml @@ -1,6 +1,7 @@ interactions: - request: - body: '{"webhook": {"url": "http://example.com"}}' + body: '{"webhook": {"url": "http://example.com", "params": {"webhook_secret": + "s\u00e9cret"}}}' headers: Accept: - '*/*' @@ -9,7 +10,7 @@ interactions: Connection: - keep-alive Content-Length: - - '42' + - '87' Content-Type: - application/json authorization: @@ -20,8 +21,8 @@ interactions: uri: https://api.easypost.com/v2/webhooks response: body: - string: '{"id": "hook_2d6400825b4011efbc7c119ee916485a", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2024-08-15T19:54:27Z", + string: '{"id": "hook_4bb2d37aeaff11efab531f670556be6e", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:48Z", "disabled_at": null}' headers: cache-control: @@ -42,27 +43,25 @@ interactions: - chunked x-backend: - easypost - x-canary: - - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf2e78865a0001778b0 + - a17ffd3467af879fe78740000059c5dc x-frame-options: - SAMEORIGIN x-node: - - bigweb43nuq + - bigweb58nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb4nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb3nuq 51d74985a2 + - extlb1nuq 99aac35317 x-runtime: - - '0.126613' + - '0.148259' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502132348-e5a3dc472b-master x-xss-protection: - 1; mode=block status: @@ -84,7 +83,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/webhooks/hook_2d6400825b4011efbc7c119ee916485a + uri: https://api.easypost.com/v2/webhooks/hook_4bb2d37aeaff11efab531f670556be6e response: body: string: '{}' @@ -112,20 +111,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf3e78865a0001778f4 + - a17ffd3267af87a0e78740180059c632 x-frame-options: - SAMEORIGIN x-node: - - bigweb39nuq + - bigweb33nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb4nuq 51d74985a2 + - extlb1nuq 99aac35317 x-runtime: - - '0.397231' + - '0.284460' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502132348-e5a3dc472b-master x-xss-protection: - 1; mode=block status: diff --git a/tests/cassettes/test_webhook_update.yaml b/tests/cassettes/test_webhook_update.yaml index f6b522e1..13dbb874 100644 --- a/tests/cassettes/test_webhook_update.yaml +++ b/tests/cassettes/test_webhook_update.yaml @@ -20,8 +20,8 @@ interactions: uri: https://api.easypost.com/v2/webhooks response: body: - string: '{"id": "hook_2e96c0205b4011efaa5e4f8e947e652b", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2024-08-15T19:54:29Z", + string: '{"id": "hook_4c3b5178eaff11ef8e003d5bcc679766", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:49Z", "disabled_at": null}' headers: cache-control: @@ -42,34 +42,32 @@ interactions: - chunked x-backend: - easypost - x-canary: - - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf4e78865a300177ade + - a17ffd3967af87a0e78740190059c6d4 x-frame-options: - SAMEORIGIN x-node: - - bigweb43nuq + - bigweb41nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb4nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb3nuq 51d74985a2 + - extlb1nuq 99aac35317 x-runtime: - - '0.147174' + - '0.115492' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502132348-e5a3dc472b-master x-xss-protection: - 1; mode=block status: code: 201 message: Created - request: - body: '{"webhook": {}}' + body: '{"webhook": {"params": {"webhook_secret": "s\u00e9cret"}}}' headers: Accept: - '*/*' @@ -78,7 +76,7 @@ interactions: Connection: - keep-alive Content-Length: - - '15' + - '58' Content-Type: - application/json authorization: @@ -86,11 +84,11 @@ interactions: user-agent: - method: PATCH - uri: https://api.easypost.com/v2/webhooks/hook_2e96c0205b4011efaa5e4f8e947e652b + uri: https://api.easypost.com/v2/webhooks/hook_4c3b5178eaff11ef8e003d5bcc679766 response: body: - string: '{"id": "hook_2e96c0205b4011efaa5e4f8e947e652b", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2024-08-15T19:54:29Z", + string: '{"id": "hook_4c3b5178eaff11ef8e003d5bcc679766", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:49Z", "disabled_at": null}' headers: cache-control: @@ -116,20 +114,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf5e78865a300177b13 + - a17ffd3667af87a1e787401a0059c736 x-frame-options: - SAMEORIGIN x-node: - - bigweb42nuq + - bigweb41nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb4nuq 51d74985a2 + - extlb1nuq 99aac35317 x-runtime: - - '0.553461' + - '0.425372' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502132348-e5a3dc472b-master x-xss-protection: - 1; mode=block status: @@ -151,7 +149,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/webhooks/hook_2e96c0205b4011efaa5e4f8e947e652b + uri: https://api.easypost.com/v2/webhooks/hook_4c3b5178eaff11ef8e003d5bcc679766 response: body: string: '{}' @@ -179,20 +177,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf5e78865a300177bc0 + - a17ffd3867af87a1e787401b0059c7dc x-frame-options: - SAMEORIGIN x-node: - - bigweb34nuq + - bigweb53nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb4nuq 51d74985a2 + - extlb1nuq 99aac35317 x-runtime: - - '0.447202' + - '0.287960' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502132348-e5a3dc472b-master x-xss-protection: - 1; mode=block status: diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 386504bd..2cee51f3 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -5,8 +5,8 @@ @pytest.mark.vcr() -def test_webhook_create(webhook_url, test_client): - webhook = test_client.webhook.create(url=webhook_url) +def test_webhook_create(webhook_url, webhook_secret, test_client): + webhook = test_client.webhook.create(url=webhook_url, params={"webhook_secret": webhook_secret}) assert isinstance(webhook, Webhook) assert str.startswith(webhook.id, "hook_") @@ -42,9 +42,9 @@ def test_webhook_all(page_size, test_client): @pytest.mark.vcr() -def test_webhook_update(webhook_url, test_client): +def test_webhook_update(webhook_url, webhook_secret, test_client): webhook = test_client.webhook.create(url=webhook_url) - test_client.webhook.update(webhook.id) + test_client.webhook.update(webhook.id, params={"webhook_secret": webhook_secret}) assert isinstance(webhook, Webhook) From e552533de839a4d03c2840034b7fd6451406ef74 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:52:16 -0700 Subject: [PATCH 2/3] chore: bump expired cassettes from 180 days to 365 --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index a95732dc..c34c8a5f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,7 +45,7 @@ def pytest_sessionfinish(session, exitstatus): @pytest.fixture(autouse=True) -def check_expired_cassettes(expiration_days: int = 180, throw_error: bool = False): +def check_expired_cassettes(expiration_days: int = 365, throw_error: bool = False): """Checks for expired cassettes and throws errors if they are too old and must be re-recorded.""" test_name = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0] # type: ignore cassette_filepath = os.path.join("tests", "cassettes", f"{test_name}.yaml") From 279962a7336e3b4a32c26c2732efa00f8c3f439e Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:30:29 -0700 Subject: [PATCH 3/3] fix: payload wrapping for updating a webhook --- CHANGELOG.md | 4 ++ easypost/services/webhook_service.py | 11 ++++- examples | 2 +- tests/cassettes/test_webhook_create.yaml | 38 +++++++-------- tests/cassettes/test_webhook_update.yaml | 59 +++++++++++++----------- tests/conftest.py | 11 +++-- tests/test_webhook.py | 22 +++++++-- 7 files changed, 91 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4140753..26a37fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Next Release + +- Fixes the payload wrapping for updating a webhook + ## v9.5.0 (2024-10-24) - Adds `tracking_codes` as a parameter of the `all` method on the TrackerService diff --git a/easypost/services/webhook_service.py b/easypost/services/webhook_service.py index 2817e666..4e2ef885 100644 --- a/easypost/services/webhook_service.py +++ b/easypost/services/webhook_service.py @@ -3,7 +3,12 @@ Dict, ) +from easypost.easypost_object import convert_to_easypost_object from easypost.models import Webhook +from easypost.requestor import ( + RequestMethod, + Requestor, +) from easypost.services.base_service import BaseService @@ -26,7 +31,11 @@ def retrieve(self, id: str) -> Webhook: def update(self, id: str, **params) -> Webhook: """Update a Webhook.""" - return self._update_resource(self._model_class, id, **params) + url = self._instance_url(self._model_class, id) + + response = Requestor(self._client).request(method=RequestMethod.PATCH, url=url, params=params) + + return convert_to_easypost_object(response=response) def delete(self, id: str) -> None: """Delete a Webhook.""" diff --git a/examples b/examples index 0492e408..7669825f 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 0492e408e1b37b2ec18bcb4a4d228ab0f458f59d +Subproject commit 7669825fb53be074d7f585c78c4f38ad4fefe0d0 diff --git a/tests/cassettes/test_webhook_create.yaml b/tests/cassettes/test_webhook_create.yaml index a68b4a9a..bce56884 100644 --- a/tests/cassettes/test_webhook_create.yaml +++ b/tests/cassettes/test_webhook_create.yaml @@ -1,7 +1,7 @@ interactions: - request: - body: '{"webhook": {"url": "http://example.com", "params": {"webhook_secret": - "s\u00e9cret"}}}' + body: '{"webhook": {"url": "http://example.com", "webhook_secret": "s\u00e9cret", + "custom_headers": [{"name": "test", "value": "header"}]}}' headers: Accept: - '*/*' @@ -10,7 +10,7 @@ interactions: Connection: - keep-alive Content-Length: - - '87' + - '132' Content-Type: - application/json authorization: @@ -21,14 +21,14 @@ interactions: uri: https://api.easypost.com/v2/webhooks response: body: - string: '{"id": "hook_4bb2d37aeaff11efab531f670556be6e", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:48Z", - "disabled_at": null}' + string: '{"id": "hook_5086302eeeef11ef883e4736f95f329e", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-19T18:28:29Z", + "disabled_at": null, "custom_headers": [{"name": "test", "value": "header"}]}' headers: cache-control: - private, no-cache, no-store content-length: - - '161' + - '213' content-type: - application/json; charset=utf-8 expires: @@ -48,20 +48,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - a17ffd3467af879fe78740000059c5dc + - 3c3ed33567b622cce2b8e5f1000336bc x-frame-options: - SAMEORIGIN x-node: - - bigweb58nuq + - bigweb40nuq x-permitted-cross-domain-policies: - none x-proxied: - intlb3nuq 51d74985a2 - - extlb1nuq 99aac35317 + - extlb2nuq 99aac35317 x-runtime: - - '0.148259' + - '0.367575' x-version-label: - - easypost-202502132348-e5a3dc472b-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: @@ -83,7 +83,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/webhooks/hook_4bb2d37aeaff11efab531f670556be6e + uri: https://api.easypost.com/v2/webhooks/hook_5086302eeeef11ef883e4736f95f329e response: body: string: '{}' @@ -111,20 +111,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - a17ffd3267af87a0e78740180059c632 + - 3c3ed33367b622cde2b8e5f200033741 x-frame-options: - SAMEORIGIN x-node: - - bigweb33nuq + - bigweb59nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb4nuq 51d74985a2 - - extlb1nuq 99aac35317 + - intlb3nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.284460' + - '0.286270' x-version-label: - - easypost-202502132348-e5a3dc472b-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: diff --git a/tests/cassettes/test_webhook_update.yaml b/tests/cassettes/test_webhook_update.yaml index 13dbb874..66035525 100644 --- a/tests/cassettes/test_webhook_update.yaml +++ b/tests/cassettes/test_webhook_update.yaml @@ -20,14 +20,14 @@ interactions: uri: https://api.easypost.com/v2/webhooks response: body: - string: '{"id": "hook_4c3b5178eaff11ef8e003d5bcc679766", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:49Z", - "disabled_at": null}' + string: '{"id": "hook_72e4caf4eeef11efb30a1373530d9838", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-19T18:29:26Z", + "disabled_at": null, "custom_headers": []}' headers: cache-control: - private, no-cache, no-store content-length: - - '161' + - '181' content-type: - application/json; charset=utf-8 expires: @@ -42,32 +42,35 @@ interactions: - chunked x-backend: - easypost + x-canary: + - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - a17ffd3967af87a0e78740190059c6d4 + - 3c3ed32f67b62306e2b8e6d800036fff x-frame-options: - SAMEORIGIN x-node: - - bigweb41nuq + - bigweb32nuq x-permitted-cross-domain-policies: - none x-proxied: - intlb3nuq 51d74985a2 - - extlb1nuq 99aac35317 + - extlb2nuq 99aac35317 x-runtime: - - '0.115492' + - '0.121274' x-version-label: - - easypost-202502132348-e5a3dc472b-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: code: 201 message: Created - request: - body: '{"webhook": {"params": {"webhook_secret": "s\u00e9cret"}}}' + body: '{"webhook_secret": "s\u00e9cret", "custom_headers": [{"name": "test", "value": + "header"}]}' headers: Accept: - '*/*' @@ -76,7 +79,7 @@ interactions: Connection: - keep-alive Content-Length: - - '58' + - '90' Content-Type: - application/json authorization: @@ -84,17 +87,17 @@ interactions: user-agent: - method: PATCH - uri: https://api.easypost.com/v2/webhooks/hook_4c3b5178eaff11ef8e003d5bcc679766 + uri: https://api.easypost.com/v2/webhooks/hook_72e4caf4eeef11efb30a1373530d9838 response: body: - string: '{"id": "hook_4c3b5178eaff11ef8e003d5bcc679766", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2025-02-14T18:12:49Z", - "disabled_at": null}' + string: '{"id": "hook_72e4caf4eeef11efb30a1373530d9838", "object": "Webhook", + "mode": "test", "url": "http://example.com", "created_at": "2025-02-19T18:29:26Z", + "disabled_at": null, "custom_headers": [{"name": "test", "value": "header"}]}' headers: cache-control: - private, no-cache, no-store content-length: - - '161' + - '213' content-type: - application/json; charset=utf-8 expires: @@ -114,20 +117,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - a17ffd3667af87a1e787401a0059c736 + - 3c3ed32f67b62306e2b8e6d900037029 x-frame-options: - SAMEORIGIN x-node: - - bigweb41nuq + - bigweb56nuq x-permitted-cross-domain-policies: - none x-proxied: - intlb4nuq 51d74985a2 - - extlb1nuq 99aac35317 + - extlb2nuq 99aac35317 x-runtime: - - '0.425372' + - '0.637407' x-version-label: - - easypost-202502132348-e5a3dc472b-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: @@ -149,7 +152,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/webhooks/hook_4c3b5178eaff11ef8e003d5bcc679766 + uri: https://api.easypost.com/v2/webhooks/hook_72e4caf4eeef11efb30a1373530d9838 response: body: string: '{}' @@ -172,25 +175,27 @@ interactions: - chunked x-backend: - easypost + x-canary: + - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - a17ffd3867af87a1e787401b0059c7dc + - 3c3ed32e67b62307e2b8e9e9000370f3 x-frame-options: - SAMEORIGIN x-node: - - bigweb53nuq + - bigweb32nuq x-permitted-cross-domain-policies: - none x-proxied: - intlb4nuq 51d74985a2 - - extlb1nuq 99aac35317 + - extlb2nuq 99aac35317 x-runtime: - - '0.287960' + - '0.326322' x-version-label: - - easypost-202502132348-e5a3dc472b-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: diff --git a/tests/conftest.py b/tests/conftest.py index c34c8a5f..be08784e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -338,17 +338,22 @@ def event_bytes(): @pytest.fixture def webhook_hmac_signature(): - return read_fixture_data()["webhook_hmac_signature"] + return read_fixture_data()["webhooks"]["hmac_signature"] @pytest.fixture def webhook_secret(): - return read_fixture_data()["webhook_secret"] + return read_fixture_data()["webhooks"]["secret"] @pytest.fixture def webhook_url(): - return read_fixture_data()["webhook_url"] + return read_fixture_data()["webhooks"]["url"] + + +@pytest.fixture +def webhook_custom_headers(): + return read_fixture_data()["webhooks"]["custom_headers"] @pytest.fixture diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 2cee51f3..021ae77a 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -5,12 +5,18 @@ @pytest.mark.vcr() -def test_webhook_create(webhook_url, webhook_secret, test_client): - webhook = test_client.webhook.create(url=webhook_url, params={"webhook_secret": webhook_secret}) +def test_webhook_create(webhook_url, webhook_secret, webhook_custom_headers, test_client): + webhook = test_client.webhook.create( + url=webhook_url, + webhook_secret=webhook_secret, + custom_headers=webhook_custom_headers, + ) assert isinstance(webhook, Webhook) assert str.startswith(webhook.id, "hook_") assert webhook.url == webhook_url + assert webhook.custom_headers[0]["name"] == "test" + assert webhook.custom_headers[0]["value"] == "header" test_client.webhook.delete( webhook.id @@ -42,11 +48,17 @@ def test_webhook_all(page_size, test_client): @pytest.mark.vcr() -def test_webhook_update(webhook_url, webhook_secret, test_client): +def test_webhook_update(webhook_url, webhook_secret, webhook_custom_headers, test_client): webhook = test_client.webhook.create(url=webhook_url) - test_client.webhook.update(webhook.id, params={"webhook_secret": webhook_secret}) + updated_webhook = test_client.webhook.update( + webhook.id, + webhook_secret=webhook_secret, + custom_headers=webhook_custom_headers, + ) - assert isinstance(webhook, Webhook) + assert isinstance(updated_webhook, Webhook) + assert updated_webhook.custom_headers[0]["name"] == "test" + assert updated_webhook.custom_headers[0]["value"] == "header" test_client.webhook.delete( webhook.id