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 3ce6296d..bce56884 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", "webhook_secret": "s\u00e9cret", + "custom_headers": [{"name": "test", "value": "header"}]}}' headers: Accept: - '*/*' @@ -9,7 +10,7 @@ interactions: Connection: - keep-alive Content-Length: - - '42' + - '132' Content-Type: - application/json authorization: @@ -20,14 +21,14 @@ 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", - "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: @@ -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 + - 3c3ed33567b622cce2b8e5f1000336bc x-frame-options: - SAMEORIGIN x-node: - - bigweb43nuq + - bigweb40nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb4nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb3nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.126613' + - '0.367575' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502191732-9cdc8ea15b-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_5086302eeeef11ef883e4736f95f329e response: body: string: '{}' @@ -112,20 +111,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf3e78865a0001778f4 + - 3c3ed33367b622cde2b8e5f200033741 x-frame-options: - SAMEORIGIN x-node: - - bigweb39nuq + - bigweb59nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb3nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.397231' + - '0.286270' x-version-label: - - easypost-202408151917-1527448f18-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 f6b522e1..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_2e96c0205b4011efaa5e4f8e947e652b", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2024-08-15T19:54:29Z", - "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: @@ -49,27 +49,28 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf4e78865a300177ade + - 3c3ed32f67b62306e2b8e6d800036fff x-frame-options: - SAMEORIGIN x-node: - - bigweb43nuq + - bigweb32nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb4nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb3nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.147174' + - '0.121274' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: code: 201 message: Created - request: - body: '{"webhook": {}}' + body: '{"webhook_secret": "s\u00e9cret", "custom_headers": [{"name": "test", "value": + "header"}]}' headers: Accept: - '*/*' @@ -78,7 +79,7 @@ interactions: Connection: - keep-alive Content-Length: - - '15' + - '90' Content-Type: - application/json authorization: @@ -86,17 +87,17 @@ interactions: user-agent: - method: PATCH - uri: https://api.easypost.com/v2/webhooks/hook_2e96c0205b4011efaa5e4f8e947e652b + uri: https://api.easypost.com/v2/webhooks/hook_72e4caf4eeef11efb30a1373530d9838 response: body: - string: '{"id": "hook_2e96c0205b4011efaa5e4f8e947e652b", "object": "Webhook", - "mode": "test", "url": "http://example.com", "created_at": "2024-08-15T19:54:29Z", - "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: @@ -116,20 +117,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf5e78865a300177b13 + - 3c3ed32f67b62306e2b8e6d900037029 x-frame-options: - SAMEORIGIN x-node: - - bigweb42nuq + - bigweb56nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb4nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.553461' + - '0.637407' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: @@ -151,7 +152,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/webhooks/hook_2e96c0205b4011efaa5e4f8e947e652b + uri: https://api.easypost.com/v2/webhooks/hook_72e4caf4eeef11efb30a1373530d9838 response: body: string: '{}' @@ -174,25 +175,27 @@ interactions: - chunked x-backend: - easypost + x-canary: + - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - 62c1f5d966be5cf5e78865a300177bc0 + - 3c3ed32e67b62307e2b8e9e9000370f3 x-frame-options: - SAMEORIGIN x-node: - - bigweb34nuq + - bigweb32nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb3nuq c0f5e722d1 - - extlb2nuq b6e1b5034c + - intlb4nuq 51d74985a2 + - extlb2nuq 99aac35317 x-runtime: - - '0.447202' + - '0.326322' x-version-label: - - easypost-202408151917-1527448f18-master + - easypost-202502191732-9cdc8ea15b-master x-xss-protection: - 1; mode=block status: diff --git a/tests/conftest.py b/tests/conftest.py index a95732dc..be08784e 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") @@ -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 386504bd..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, test_client): - webhook = test_client.webhook.create(url=webhook_url) +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, 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) + 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