From 74fa51a97b223326e6a1beb4062377ca75dc74a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 04:54:01 +0000 Subject: [PATCH] test: cover malformed error responses The error handling in SeamHttpClient distinguishes standard Seam error bodies, which become SeamHttpApiError, from everything else, which falls through is_api_error_response to raise_for_status. Only the first half was covered: the suite never exercised a non-JSON body, malformed JSON, a JSON body without an error object, or an error object without string type and message fields. Cover all four against the recording server, which grows an optional content type override so it can serve malformed JSON. The fake cannot produce these responses. This closes the same gap just restored in the Ruby SDK, where the equivalent branches briefly lost their specs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB --- test/conftest.py | 9 +++++++- test/http_error_test.py | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 1c89a548..bacc479f 100755 --- a/test/conftest.py +++ b/test/conftest.py @@ -58,6 +58,9 @@ def recording_server_fixture(): def recording_server(responses): """Serve the given (status, body) responses, repeating the last one. + A response may also be (status, body, content_type) to override the + content type inferred from the body, e.g. to serve malformed JSON. + Yields the endpoint along with the list of requests received so far. """ @@ -80,7 +83,8 @@ def do_POST(self): # BaseHTTPRequestHandler dispatches on this name. } ) - status, payload = remaining.pop(0) if len(remaining) > 1 else remaining[0] + response = remaining.pop(0) if len(remaining) > 1 else remaining[0] + status, payload, *rest = response if isinstance(payload, str): content_type = "text/plain" @@ -89,6 +93,9 @@ def do_POST(self): # BaseHTTPRequestHandler dispatches on this name. content_type = "application/json" body = json.dumps(payload).encode() + if rest: + content_type = rest[0] + self.send_response(status) self.send_header("content-type", content_type) self.send_header("content-length", str(len(body))) diff --git a/test/http_error_test.py b/test/http_error_test.py index 27454229..d737cfcc 100644 --- a/test/http_error_test.py +++ b/test/http_error_test.py @@ -60,3 +60,50 @@ def test_seam_http_throws_http_error_on_non_standard_response(server): seam.devices.list() assert exc_info.value.response.status_code == 503 + + +# The fake cannot produce malformed error responses, so the recording server +# drives the bodies that must fall through is_api_error_response and raise a +# plain HTTPError rather than being parsed into a SeamHttpApiError. +def test_seam_http_raises_http_error_on_non_json_response(recording_server): + with recording_server([(500, "Internal Server Error")]) as (endpoint, _): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + with pytest.raises(niquests.HTTPError) as exc_info: + seam.devices.list() + + assert exc_info.value.response.status_code == 500 + + +def test_seam_http_raises_http_error_on_malformed_json(recording_server): + responses = [(500, "{invalid json", "application/json")] + + with recording_server(responses) as (endpoint, _): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + with pytest.raises(niquests.HTTPError) as exc_info: + seam.devices.list() + + assert exc_info.value.response.status_code == 500 + + +def test_seam_http_raises_http_error_on_json_without_error_object(recording_server): + with recording_server([(500, {"message": "Some error"})]) as (endpoint, _): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + with pytest.raises(niquests.HTTPError) as exc_info: + seam.devices.list() + + assert exc_info.value.response.status_code == 500 + + +def test_seam_http_raises_http_error_on_error_object_without_type_and_message( + recording_server, +): + with recording_server([(500, {"error": {"code": 500}})]) as (endpoint, _): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + with pytest.raises(niquests.HTTPError) as exc_info: + seam.devices.list() + + assert exc_info.value.response.status_code == 500