From 0e62789aa32a2e46a261ab61b397924359c80566 Mon Sep 17 00:00:00 2001 From: Roshaan Date: Sun, 9 Aug 2026 02:55:47 +0500 Subject: [PATCH] fix(storage): surface real error when error body lacks message --- src/storage/src/storage3/_async/file_api.py | 2 +- src/storage/src/storage3/_sync/file_api.py | 2 +- src/storage/tests/_async/test_file_api.py | 29 +++++++++++++++++++++ src/storage/tests/_sync/test_file_api.py | 29 +++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/storage/tests/_async/test_file_api.py create mode 100644 src/storage/tests/_sync/test_file_api.py diff --git a/src/storage/src/storage3/_async/file_api.py b/src/storage/src/storage3/_async/file_api.py index 474486f1..d7502577 100644 --- a/src/storage/src/storage3/_async/file_api.py +++ b/src/storage/src/storage3/_async/file_api.py @@ -84,7 +84,7 @@ async def _request( resp["message"], resp["error"], resp["statusCode"] ) from exc except KeyError as err: - message = f"Unable to parse error message: {resp.text}" + message = f"Unable to parse error message: {exc.response.text}" raise StorageApiError(message, "InternalError", 400) from err # close the resource before returning the response diff --git a/src/storage/src/storage3/_sync/file_api.py b/src/storage/src/storage3/_sync/file_api.py index 1bc22a09..defb2e1f 100644 --- a/src/storage/src/storage3/_sync/file_api.py +++ b/src/storage/src/storage3/_sync/file_api.py @@ -84,7 +84,7 @@ def _request( resp["message"], resp["error"], resp["statusCode"] ) from exc except KeyError as err: - message = f"Unable to parse error message: {resp.text}" + message = f"Unable to parse error message: {exc.response.text}" raise StorageApiError(message, "InternalError", 400) from err # close the resource before returning the response diff --git a/src/storage/tests/_async/test_file_api.py b/src/storage/tests/_async/test_file_api.py new file mode 100644 index 00000000..148c6cee --- /dev/null +++ b/src/storage/tests/_async/test_file_api.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import httpx +import pytest +from httpx import Headers +from storage3._async.file_api import AsyncBucketProxy +from storage3.exceptions import StorageApiError +from yarl import URL + + +async def test_request_raises_real_error_when_body_missing_message_field() -> None: + """A non-2xx body without message/error/statusCode must surface as StorageApiError.""" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(400, json={"code": "TooLarge"}, request=request) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + proxy = AsyncBucketProxy( + id="bucket", + _base_url=URL("http://localhost:54321/storage/v1"), + _headers=Headers(), + _client=client, + ) + with pytest.raises(StorageApiError) as exc_info: + await proxy._request("GET", ["object", "open", "bucket", "file.txt"]) + + assert exc_info.value.code == "InternalError" + assert exc_info.value.status == 400 + assert "TooLarge" in exc_info.value.message diff --git a/src/storage/tests/_sync/test_file_api.py b/src/storage/tests/_sync/test_file_api.py new file mode 100644 index 00000000..77d26ad3 --- /dev/null +++ b/src/storage/tests/_sync/test_file_api.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import httpx +import pytest +from httpx import Headers +from storage3._sync.file_api import SyncBucketProxy +from storage3.exceptions import StorageApiError +from yarl import URL + + +def test_request_raises_real_error_when_body_missing_message_field() -> None: + """A non-2xx body without message/error/statusCode must surface as StorageApiError.""" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(400, json={"code": "TooLarge"}, request=request) + + with httpx.Client(transport=httpx.MockTransport(handler)) as client: + proxy = SyncBucketProxy( + id="bucket", + _base_url=URL("http://localhost:54321/storage/v1"), + _headers=Headers(), + _client=client, + ) + with pytest.raises(StorageApiError) as exc_info: + proxy._request("GET", ["object", "open", "bucket", "file.txt"]) + + assert exc_info.value.code == "InternalError" + assert exc_info.value.status == 400 + assert "TooLarge" in exc_info.value.message