From 70b45d4ba04119d3b2a9c2f25a99a02b274d1359 Mon Sep 17 00:00:00 2001 From: Lior eliav <33252035+LioriE@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:22:55 +0300 Subject: [PATCH 1/4] fix(http): return last response for non-JSON bodies get_last_response() raised JSONDecodeError instead of returning the response when the body was not JSON (e.g. an nginx 502 HTML page): `mgmt_resp or auth_resp` evaluates truthiness, which called DescopeResponse.__bool__ -> json(). __str__ and __repr__ had the same problem, so logging the response crashed too. The inspection dunders now fall back on a parse failure instead of raising, leaving JSON-body semantics unchanged. Explicit JSON access (json(), __getitem__, get, keys, items, __len__, __iter__) still raises. Adds an is_json property, and records the last response on put(), which was missing in both the sync and async clients. --- README.md | 6 +++-- descope/_http_client_base.py | 35 +++++++++++++++++++++++----- descope/descope_client.py | 4 ++-- descope/descope_client_async.py | 2 +- descope/http_client.py | 4 +++- descope/http_client_async.py | 2 ++ samples/verbose_mode_example.py | 4 ++-- tests/test_descope_client.py | 30 ++++++++++++++++++++++++ tests/test_http_client.py | 41 +++++++++++++++++++++++++++++++++ 9 files changed, 114 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e01e34fd0..e776b7e21 100644 --- a/README.md +++ b/README.md @@ -565,7 +565,7 @@ try: except AuthException as e: # Access the last response metadata for debugging response = client.get_last_response() - if response: + if response is not None: logger.error(f"Request failed with status {response.status_code}") logger.error(f"cf-ray: {response.headers.get('cf-ray')}") logger.error(f"x-request-id: {response.headers.get('x-request-id')}") @@ -587,7 +587,9 @@ except AuthException as e: - `response.text` - Raw response body as text (str) - `response.url` - Request URL (str) - `response.ok` - Whether status code is < 400 (bool) -- `response.json()` - Parsed JSON response (dict/list) +- `response.json()` - Parsed JSON response (dict/list), raises if the body is not JSON +- `response.is_json` - Whether the body can be parsed as JSON (bool) +- `response.raw` - The underlying `httpx.Response` - `response["key"]` - Dict-like access to JSON data (for backward compatibility) For a complete example, see [samples/verbose_mode_example.py](https://github.com/descope/python-sdk/blob/main/samples/verbose_mode_example.py). diff --git a/descope/_http_client_base.py b/descope/_http_client_base.py index 5e2a1a9df..673fe3c91 100644 --- a/descope/_http_client_base.py +++ b/descope/_http_client_base.py @@ -62,6 +62,15 @@ def json(self): self._json_data = self.raw.json() return self._json_data + @property + def is_json(self) -> bool: + """True if the response body can be parsed as JSON.""" + try: + self.json() + except ValueError: + return False + return True + # Dict-like interface for backward compatibility def __getitem__(self, key): return self.json()[key] @@ -81,22 +90,36 @@ def items(self): def get(self, key, default=None): return self.json().get(key, default) + # Inspection dunders never parse-fail: a non-JSON body (an nginx 502 HTML + # page, for example) must still be loggable and truthy as a response object. def __str__(self): - return str(self.json()) + try: + return str(self.json()) + except ValueError: + return self.raw.text def __repr__(self): - return f"DescopeResponse({repr(self.json())})" + try: + return f"DescopeResponse({repr(self.json())})" + except ValueError: + return f"DescopeResponse(status_code={self.raw.status_code}, text={self.raw.text[:200]!r})" def __bool__(self): - return bool(self.json()) + try: + return bool(self.json()) + except ValueError: + return True def __len__(self): return len(self.json()) def __eq__(self, other): - if isinstance(other, DescopeResponse): - return self.json() == other.json() - return self.json() == other + try: + if isinstance(other, DescopeResponse): + return self.json() == other.json() + return self.json() == other + except ValueError: + return self is other def __ne__(self, other): return not self.__eq__(other) diff --git a/descope/descope_client.py b/descope/descope_client.py index 8f39159f0..2a7563550 100644 --- a/descope/descope_client.py +++ b/descope/descope_client.py @@ -387,7 +387,7 @@ def get_last_response(self): client.mgmt.user.create(login_id="test@example.com") except AuthException: resp = client.get_last_response() - if resp: + if resp is not None: # Access metadata for debugging cf_ray = resp.headers.get("cf-ray") status = resp.status_code @@ -398,4 +398,4 @@ def get_last_response(self): # Return whichever is not None, preferring mgmt if both exist # (in practice, only one should be non-None at a time) - return mgmt_resp or auth_resp + return mgmt_resp if mgmt_resp is not None else auth_resp diff --git a/descope/descope_client_async.py b/descope/descope_client_async.py index 1da2a94ad..d60588653 100644 --- a/descope/descope_client_async.py +++ b/descope/descope_client_async.py @@ -322,4 +322,4 @@ def get_last_response(self): """Get the last HTTP response when verbose mode is enabled.""" mgmt_resp = self._mgmt_http.get_last_response() auth_resp = self._auth_http.get_last_response() - return mgmt_resp or auth_resp + return mgmt_resp if mgmt_resp is not None else auth_resp diff --git a/descope/http_client.py b/descope/http_client.py index badcf0c2a..2067d4b80 100644 --- a/descope/http_client.py +++ b/descope/http_client.py @@ -104,6 +104,8 @@ def put( timeout=self.timeout_seconds, ) ) + if self.verbose: + self._thread_local.last_response = DescopeResponse(response) self._raise_from_response(response) return response @@ -172,7 +174,7 @@ def get_last_response(self) -> DescopeResponse | None: client.mgmt.user.create(login_id="u1") except AuthException: resp = client.get_last_response() - if resp: + if resp is not None: logger.error(f"cf-ray: {resp.headers.get('cf-ray')}") """ return getattr(self._thread_local, "last_response", None) diff --git a/descope/http_client_async.py b/descope/http_client_async.py index 205b5d225..6f4e11189 100644 --- a/descope/http_client_async.py +++ b/descope/http_client_async.py @@ -109,6 +109,8 @@ async def put( params=params, ) ) + if self.verbose: + self._last_response_var.set(DescopeResponse(response)) self._raise_from_response(response) return response diff --git a/samples/verbose_mode_example.py b/samples/verbose_mode_example.py index 872597827..03b365e7d 100644 --- a/samples/verbose_mode_example.py +++ b/samples/verbose_mode_example.py @@ -35,7 +35,7 @@ def example_with_verbose_mode(): # Access the last response metadata response = client.get_last_response() - if response: + if response is not None: logger.info("Request succeeded!") logger.info("Status: %s", response.status_code) logger.info("cf-ray: %s", response.headers.get("cf-ray")) @@ -44,7 +44,7 @@ def example_with_verbose_mode(): except AuthException: # When an error occurs, capture the response metadata for debugging response = client.get_last_response() - if response: + if response is not None: logger.error("Request failed with status %s", response.status_code) logger.error("cf-ray: %s", response.headers.get("cf-ray")) logger.error("x-request-id: %s", response.headers.get("x-request-id")) diff --git a/tests/test_descope_client.py b/tests/test_descope_client.py index 9fa791213..8406b3497 100644 --- a/tests/test_descope_client.py +++ b/tests/test_descope_client.py @@ -839,3 +839,33 @@ async def test_verbose_mode_captures_mgmt_response(self, client_factory): assert last_resp["user"]["id"] == "u1" assert last_resp.headers.get("cf-ray") == "mgmt-ray-123" assert last_resp.status_code == 200 + + async def test_verbose_mode_returns_response_on_non_json_body(self, client_factory): + """get_last_response() must not parse the body: a 502 HTML page is still returned.""" + html = "502 Bad Gateway" + mock_response = mock.Mock() + mock_response.is_success = False + mock_response.status_code = 502 + mock_response.text = html + mock_response.headers = {"cf-ray": "mgmt-ray-502"} + mock_response.json.side_effect = json.JSONDecodeError("Expecting value", html, 0) + + client = client_factory.make( + PROJECT_ID, + public_key=PUBLIC_KEY_DICT, + management_key="test-mgmt-key", + verbose=True, + ) + if client_factory.mode == "async": + client._raw._license_attempted = True + + with client.mock_mgmt_post(mock_response): + with pytest.raises(AuthException): + await client.invoke(client.mgmt.user.create(login_id="test@example.com")) + + last_resp = client.get_last_response() + assert last_resp is not None + assert last_resp.status_code == 502 + assert last_resp.text == html + assert last_resp.headers.get("cf-ray") == "mgmt-ray-502" + assert last_resp.is_json is False diff --git a/tests/test_http_client.py b/tests/test_http_client.py index ac497b59f..6693c4a51 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -1,3 +1,4 @@ +import json import os import unittest from unittest.mock import Mock, patch @@ -124,6 +125,46 @@ def test_cookies_and_content(self): assert resp.cookies.get("session") == "abc123" assert resp.content == b'{"data":"test"}' + def test_non_json_body_is_inspectable(self): + """A non-JSON body (e.g. an nginx 502 HTML page) must not break inspection.""" + html = "502 Bad Gateway" + mock_response = Mock() + mock_response.json.side_effect = json.JSONDecodeError("Expecting value", html, 0) + mock_response.status_code = 502 + mock_response.text = html + mock_response.headers = {"cf-ray": "abc123"} + mock_response.is_success = False + + resp = DescopeResponse(mock_response) + + assert bool(resp) is True + assert str(resp) == html + assert "502" in repr(resp) + assert resp.is_json is False + assert resp.status_code == 502 + assert resp.text == html + assert resp.headers.get("cf-ray") == "abc123" + assert resp.ok is False + assert resp == resp + assert resp != DescopeResponse(mock_response) + + # Explicit JSON access still raises + with self.assertRaises(json.JSONDecodeError): + resp.json() + with self.assertRaises(json.JSONDecodeError): + resp["errorCode"] + + def test_is_json_true_for_json_body(self): + mock_response = Mock() + mock_response.json.return_value = {"data": "test"} + assert DescopeResponse(mock_response).is_json is True + + def test_empty_json_body_is_falsy(self): + """Existing truthiness semantics for JSON bodies are preserved.""" + mock_response = Mock() + mock_response.json.return_value = {} + assert bool(DescopeResponse(mock_response)) is False + @patch("httpx.get") def test_verbose_mode_captures_response_before_error(self, mock_get): """Test that verbose mode captures response even when errors are raised. From 8058e9f664eae73991fbee4e88d82bb64caae6e8 Mon Sep 17 00:00:00 2001 From: Lior eliav <33252035+LioriE@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:01:18 +0300 Subject: [PATCH 2/4] fix(http): make response truthiness mean "a response exists" bool(response) answered "is the JSON body non-empty", which meant truthiness parsed the body. It is now unconditionally True: an empty JSON body no longer reads as no-response, and truthiness never parses. Kept explicit rather than deleted, since __len__ is defined and Python would otherwise fall back to it for truthiness, reintroducing both the parse and the empty-body falsiness. --- README.md | 1 + descope/_http_client_base.py | 14 ++++++++++---- tests/test_http_client.py | 10 +++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e776b7e21..2899cd237 100644 --- a/README.md +++ b/README.md @@ -580,6 +580,7 @@ except AuthException as e: - When enabled, only the **most recent** HTTP response is stored - `get_last_response()` returns `None` when verbose mode is disabled - The response object provides dict-like access to JSON data while also exposing HTTP metadata +- Check for a response with `if response is not None:` — a response object is always truthy, including when the body is empty or not JSON **Available metadata on response objects:** - `response.headers` - HTTP response headers (dict-like object) diff --git a/descope/_http_client_base.py b/descope/_http_client_base.py index 673fe3c91..f00036576 100644 --- a/descope/_http_client_base.py +++ b/descope/_http_client_base.py @@ -50,6 +50,12 @@ class DescopeResponse: This allows backward compatibility (acting like a dict) while exposing HTTP metadata like cf-ray headers for debugging. + + Members that need the parsed body (``json()``, ``__getitem__``, ``get``, + ``keys``, ``values``, ``items``, ``__len__``, ``__iter__``, ``__contains__``) + raise on a non-JSON body. Inspecting the response itself never does: + ``bool()`` is always True, and ``str()``/``repr()`` fall back to the raw + text, so a response is always loggable. Use ``is_json`` to check first. """ def __init__(self, response: httpx.Response): @@ -105,10 +111,10 @@ def __repr__(self): return f"DescopeResponse(status_code={self.raw.status_code}, text={self.raw.text[:200]!r})" def __bool__(self): - try: - return bool(self.json()) - except ValueError: - return True + # A response object is always truthy: truthiness answers "did I get a + # response", not "is the body non-empty". Must stay explicit — without + # it Python falls back to __len__, which parses the body. + return True def __len__(self): return len(self.json()) diff --git a/tests/test_http_client.py b/tests/test_http_client.py index 6693c4a51..2834aad62 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -159,11 +159,15 @@ def test_is_json_true_for_json_body(self): mock_response.json.return_value = {"data": "test"} assert DescopeResponse(mock_response).is_json is True - def test_empty_json_body_is_falsy(self): - """Existing truthiness semantics for JSON bodies are preserved.""" + def test_empty_json_body_is_truthy(self): + """Truthiness means "a response exists", not "the body is non-empty".""" mock_response = Mock() mock_response.json.return_value = {} - assert bool(DescopeResponse(mock_response)) is False + resp = DescopeResponse(mock_response) + + assert bool(resp) is True + mock_response.json.assert_not_called() # truthiness must not parse the body + assert len(resp) == 0 @patch("httpx.get") def test_verbose_mode_captures_response_before_error(self, mock_get): From 7026a260314a8f4c2cfad75f57905d55864379eb Mon Sep 17 00:00:00 2001 From: Lior eliav <33252035+LioriE@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:15:05 +0300 Subject: [PATCH 3/4] fix(http): bound non-JSON body in str/repr, test put capture Revert the get_last_response() accessor and the docs to plain truthiness now that __bool__ no longer parses the body: `mgmt_resp or auth_resp` is behaviorally identical to the explicit None check. Address review: - str() echoed the whole non-JSON body while repr() capped at 200 chars. Body size is upstream-controlled, so both now share one bounded preview and point at .text for the full body. - put() verbose capture had no test in either client, which is how it was missed in the first place. Added to both. --- README.md | 4 ++-- descope/_http_client_base.py | 14 ++++++++++-- descope/descope_client.py | 4 ++-- descope/descope_client_async.py | 2 +- descope/http_client.py | 2 +- samples/verbose_mode_example.py | 4 ++-- tests/test_http_client.py | 39 ++++++++++++++++++++++++++++++--- tests/test_http_client_async.py | 12 ++++++++++ 8 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2899cd237..c739e4d45 100644 --- a/README.md +++ b/README.md @@ -565,7 +565,7 @@ try: except AuthException as e: # Access the last response metadata for debugging response = client.get_last_response() - if response is not None: + if response: logger.error(f"Request failed with status {response.status_code}") logger.error(f"cf-ray: {response.headers.get('cf-ray')}") logger.error(f"x-request-id: {response.headers.get('x-request-id')}") @@ -580,7 +580,7 @@ except AuthException as e: - When enabled, only the **most recent** HTTP response is stored - `get_last_response()` returns `None` when verbose mode is disabled - The response object provides dict-like access to JSON data while also exposing HTTP metadata -- Check for a response with `if response is not None:` — a response object is always truthy, including when the body is empty or not JSON +- A response object is always truthy, including when the body is empty or not JSON, so `if response:` is a safe presence check **Available metadata on response objects:** - `response.headers` - HTTP response headers (dict-like object) diff --git a/descope/_http_client_base.py b/descope/_http_client_base.py index f00036576..432181a08 100644 --- a/descope/_http_client_base.py +++ b/descope/_http_client_base.py @@ -30,6 +30,9 @@ def sdk_version(): return version("descope") +# Longest non-JSON body echoed into str()/repr() of a response +_MAX_TEXT_PREVIEW = 200 + # HTTP status codes that should trigger automatic retries _RETRY_STATUS_CODES = {503, 520, 521, 522, 524, 530} # Delays in seconds between retries: first retry after 100ms, subsequent retries after 5s @@ -96,19 +99,26 @@ def items(self): def get(self, key, default=None): return self.json().get(key, default) + def _text_preview(self): + """Bounded view of a non-JSON body: its size is upstream-controlled.""" + text = self.raw.text + if len(text) <= _MAX_TEXT_PREVIEW: + return text + return f"{text[:_MAX_TEXT_PREVIEW]}... ({len(text)} chars, use .text for the full body)" + # Inspection dunders never parse-fail: a non-JSON body (an nginx 502 HTML # page, for example) must still be loggable and truthy as a response object. def __str__(self): try: return str(self.json()) except ValueError: - return self.raw.text + return self._text_preview() def __repr__(self): try: return f"DescopeResponse({repr(self.json())})" except ValueError: - return f"DescopeResponse(status_code={self.raw.status_code}, text={self.raw.text[:200]!r})" + return f"DescopeResponse(status_code={self.raw.status_code}, text={self._text_preview()!r})" def __bool__(self): # A response object is always truthy: truthiness answers "did I get a diff --git a/descope/descope_client.py b/descope/descope_client.py index 2a7563550..8f39159f0 100644 --- a/descope/descope_client.py +++ b/descope/descope_client.py @@ -387,7 +387,7 @@ def get_last_response(self): client.mgmt.user.create(login_id="test@example.com") except AuthException: resp = client.get_last_response() - if resp is not None: + if resp: # Access metadata for debugging cf_ray = resp.headers.get("cf-ray") status = resp.status_code @@ -398,4 +398,4 @@ def get_last_response(self): # Return whichever is not None, preferring mgmt if both exist # (in practice, only one should be non-None at a time) - return mgmt_resp if mgmt_resp is not None else auth_resp + return mgmt_resp or auth_resp diff --git a/descope/descope_client_async.py b/descope/descope_client_async.py index d60588653..1da2a94ad 100644 --- a/descope/descope_client_async.py +++ b/descope/descope_client_async.py @@ -322,4 +322,4 @@ def get_last_response(self): """Get the last HTTP response when verbose mode is enabled.""" mgmt_resp = self._mgmt_http.get_last_response() auth_resp = self._auth_http.get_last_response() - return mgmt_resp if mgmt_resp is not None else auth_resp + return mgmt_resp or auth_resp diff --git a/descope/http_client.py b/descope/http_client.py index 2067d4b80..598bc7adb 100644 --- a/descope/http_client.py +++ b/descope/http_client.py @@ -174,7 +174,7 @@ def get_last_response(self) -> DescopeResponse | None: client.mgmt.user.create(login_id="u1") except AuthException: resp = client.get_last_response() - if resp is not None: + if resp: logger.error(f"cf-ray: {resp.headers.get('cf-ray')}") """ return getattr(self._thread_local, "last_response", None) diff --git a/samples/verbose_mode_example.py b/samples/verbose_mode_example.py index 03b365e7d..872597827 100644 --- a/samples/verbose_mode_example.py +++ b/samples/verbose_mode_example.py @@ -35,7 +35,7 @@ def example_with_verbose_mode(): # Access the last response metadata response = client.get_last_response() - if response is not None: + if response: logger.info("Request succeeded!") logger.info("Status: %s", response.status_code) logger.info("cf-ray: %s", response.headers.get("cf-ray")) @@ -44,7 +44,7 @@ def example_with_verbose_mode(): except AuthException: # When an error occurs, capture the response metadata for debugging response = client.get_last_response() - if response is not None: + if response: logger.error("Request failed with status %s", response.status_code) logger.error("cf-ray: %s", response.headers.get("cf-ray")) logger.error("x-request-id: %s", response.headers.get("x-request-id")) diff --git a/tests/test_http_client.py b/tests/test_http_client.py index 2834aad62..3fc7d7e3d 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -145,14 +145,29 @@ def test_non_json_body_is_inspectable(self): assert resp.text == html assert resp.headers.get("cf-ray") == "abc123" assert resp.ok is False - assert resp == resp - assert resp != DescopeResponse(mock_response) + # Equality falls back to identity rather than raising + assert (resp == DescopeResponse(mock_response)) is False # Explicit JSON access still raises with self.assertRaises(json.JSONDecodeError): resp.json() with self.assertRaises(json.JSONDecodeError): - resp["errorCode"] + resp.__getitem__("errorCode") + + def test_long_non_json_body_is_truncated_in_str_and_repr(self): + """Body size is upstream-controlled, so logging must not echo it unbounded.""" + body = "x" * 5000 + mock_response = Mock() + mock_response.json.side_effect = json.JSONDecodeError("Expecting value", body, 0) + mock_response.status_code = 502 + mock_response.text = body + + resp = DescopeResponse(mock_response) + + assert len(str(resp)) < 300 + assert "5000 chars" in str(resp) + assert len(repr(resp)) < 300 + assert resp.text == body # full body still reachable def test_is_json_true_for_json_body(self): mock_response = Mock() @@ -296,6 +311,24 @@ def test_verbose_mode_captures_patch_response(self, mock_patch): assert last_resp["updated"] == "user1" assert last_resp.status_code == 200 + @patch("httpx.put") + def test_verbose_mode_captures_put_response(self, mock_put): + """Test that PUT responses are captured in verbose mode.""" + mock_response = Mock() + mock_response.is_success = True + mock_response.json.return_value = {"replaced": "user1"} + mock_response.headers = {"cf-ray": "put123"} + mock_response.status_code = 200 + mock_put.return_value = mock_response + + client = HTTPClient(project_id="test123", verbose=True) + client.put("/users/1", body={"name": "replaced"}) + + last_resp = client.get_last_response() + assert last_resp is not None + assert last_resp["replaced"] == "user1" + assert last_resp.status_code == 200 + @patch("httpx.delete") def test_verbose_mode_captures_delete_response(self, mock_delete): """Test that DELETE responses are captured in verbose mode.""" diff --git a/tests/test_http_client_async.py b/tests/test_http_client_async.py index 25f100dcb..82b627f3e 100644 --- a/tests/test_http_client_async.py +++ b/tests/test_http_client_async.py @@ -317,6 +317,18 @@ async def test_patch_captures_response_when_verbose(self): assert last is not None assert last.status_code == 200 + async def test_put_captures_response_when_verbose(self): + client = make_async_client(verbose=True) + client._async_client.put = AsyncMock( + return_value=make_resp(status=200, json_data={"replaced": 1}, headers={"cf-ray": "r5"}) + ) + + await client.put("/x", body={}) + + last = client.get_last_response() + assert last is not None + assert last.status_code == 200 + async def test_delete_captures_response_when_verbose(self): client = make_async_client(verbose=True) client._async_client.delete = AsyncMock( From 91f19586db337275308196697d1d741b87e36c62 Mon Sep 17 00:00:00 2001 From: Lior eliav <33252035+LioriE@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:00:16 +0300 Subject: [PATCH 4/4] revert: drop put() verbose capture from this PR The put() last-response gap belongs to descope/etc#16377, together with the get_last_response() staleness half of that issue. Keeping it here would half-close that issue and put changes outside this bug's scope into a fix for descope/etc#17556. --- descope/http_client.py | 2 -- descope/http_client_async.py | 2 -- tests/test_http_client.py | 18 ------------------ tests/test_http_client_async.py | 12 ------------ 4 files changed, 34 deletions(-) diff --git a/descope/http_client.py b/descope/http_client.py index 598bc7adb..badcf0c2a 100644 --- a/descope/http_client.py +++ b/descope/http_client.py @@ -104,8 +104,6 @@ def put( timeout=self.timeout_seconds, ) ) - if self.verbose: - self._thread_local.last_response = DescopeResponse(response) self._raise_from_response(response) return response diff --git a/descope/http_client_async.py b/descope/http_client_async.py index 6f4e11189..205b5d225 100644 --- a/descope/http_client_async.py +++ b/descope/http_client_async.py @@ -109,8 +109,6 @@ async def put( params=params, ) ) - if self.verbose: - self._last_response_var.set(DescopeResponse(response)) self._raise_from_response(response) return response diff --git a/tests/test_http_client.py b/tests/test_http_client.py index 3fc7d7e3d..ab48302d5 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -311,24 +311,6 @@ def test_verbose_mode_captures_patch_response(self, mock_patch): assert last_resp["updated"] == "user1" assert last_resp.status_code == 200 - @patch("httpx.put") - def test_verbose_mode_captures_put_response(self, mock_put): - """Test that PUT responses are captured in verbose mode.""" - mock_response = Mock() - mock_response.is_success = True - mock_response.json.return_value = {"replaced": "user1"} - mock_response.headers = {"cf-ray": "put123"} - mock_response.status_code = 200 - mock_put.return_value = mock_response - - client = HTTPClient(project_id="test123", verbose=True) - client.put("/users/1", body={"name": "replaced"}) - - last_resp = client.get_last_response() - assert last_resp is not None - assert last_resp["replaced"] == "user1" - assert last_resp.status_code == 200 - @patch("httpx.delete") def test_verbose_mode_captures_delete_response(self, mock_delete): """Test that DELETE responses are captured in verbose mode.""" diff --git a/tests/test_http_client_async.py b/tests/test_http_client_async.py index 82b627f3e..25f100dcb 100644 --- a/tests/test_http_client_async.py +++ b/tests/test_http_client_async.py @@ -317,18 +317,6 @@ async def test_patch_captures_response_when_verbose(self): assert last is not None assert last.status_code == 200 - async def test_put_captures_response_when_verbose(self): - client = make_async_client(verbose=True) - client._async_client.put = AsyncMock( - return_value=make_resp(status=200, json_data={"replaced": 1}, headers={"cf-ray": "r5"}) - ) - - await client.put("/x", body={}) - - last = client.get_last_response() - assert last is not None - assert last.status_code == 200 - async def test_delete_captures_response_when_verbose(self): client = make_async_client(verbose=True) client._async_client.delete = AsyncMock(