From e65ed8a95b5e9d6502f3de1330ed2bf1473e3371 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:15:48 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A7=AA=20Add=20test=20for=20check=5Fe?= =?UTF-8?q?rror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive unit tests for the `enapter.http.api.errors.check_error` function. Covered all logical branches, including successful responses, generic server errors (non-JSON payloads triggering raise_for_status), and API-specific errors (single and multiple errors from DTO). Also added coverage for empty payloads parsing. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- tests/unit/test_http/test_api/test_errors.py | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/unit/test_http/test_api/test_errors.py diff --git a/tests/unit/test_http/test_api/test_errors.py b/tests/unit/test_http/test_api/test_errors.py new file mode 100644 index 0000000..0dcd6b6 --- /dev/null +++ b/tests/unit/test_http/test_api/test_errors.py @@ -0,0 +1,72 @@ +import httpx +import pytest + +from enapter.http.api.errors import Error, MultiError, check_error + + +@pytest.mark.asyncio +async def test_check_error_success(): + """Test that check_error does nothing when response is successful.""" + response = httpx.Response(status_code=200, request=httpx.Request("GET", "https://example.com")) + await check_error(response) + + +@pytest.mark.asyncio +async def test_check_error_non_json(): + """Test that check_error calls raise_for_status when response is not JSON.""" + response = httpx.Response(status_code=500, content=b"Internal Server Error", request=httpx.Request("GET", "https://example.com")) + with pytest.raises(httpx.HTTPStatusError): + await check_error(response) + + +@pytest.mark.asyncio +async def test_check_error_api_error_single(): + """Test that check_error raises a single Error when one is present in DTO.""" + dto = { + "errors": [ + { + "message": "Device not found", + "code": "DEVICE_NOT_FOUND", + "details": {"device_id": "test_device"}, + } + ] + } + response = httpx.Response(status_code=404, json=dto, request=httpx.Request("GET", "https://example.com")) + with pytest.raises(Error) as exc_info: + await check_error(response) + + assert exc_info.value.message == "Device not found" + assert exc_info.value.code == "DEVICE_NOT_FOUND" + assert exc_info.value.details == {"device_id": "test_device"} + + +@pytest.mark.asyncio +async def test_check_error_api_error_multiple(): + """Test that check_error raises MultiError when multiple are present in DTO.""" + dto = { + "errors": [ + {"message": "Invalid parameter A", "code": "INVALID_PARAM"}, + {"message": "Invalid parameter B", "code": "INVALID_PARAM"}, + ] + } + response = httpx.Response(status_code=400, json=dto, request=httpx.Request("GET", "https://example.com")) + with pytest.raises(MultiError) as exc_info: + await check_error(response) + + assert len(exc_info.value.errors) == 2 + assert exc_info.value.errors[0].message == "Invalid parameter A" + assert exc_info.value.errors[1].message == "Invalid parameter B" + + +def test_error_from_dto_empty(): + """Test Error.from_dto with minimal fields.""" + err = Error.from_dto({}) + assert err.message == "" + assert err.code is None + assert err.details is None + + +def test_multi_error_from_dto_empty_list(): + """Test MultiError.from_dto raises ValueError on empty error list.""" + with pytest.raises(ValueError, match="empty error list"): + MultiError.from_dto({"errors": []}) From 729bdf2cbb08a3c1ab2380c362645fe8bcc39168 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:20:45 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20black=20formatting=20i?= =?UTF-8?q?n=20test=5Ferrors.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran `black tests/unit/test_http/test_api/test_errors.py` to fix formatting violations that were causing the CI `make check` step to fail. Now lines involving `httpx.Response` object creation are properly wrapped to respect the line length limit. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- tests/unit/test_http/test_api/test_errors.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_http/test_api/test_errors.py b/tests/unit/test_http/test_api/test_errors.py index 0dcd6b6..526b2b8 100644 --- a/tests/unit/test_http/test_api/test_errors.py +++ b/tests/unit/test_http/test_api/test_errors.py @@ -7,14 +7,20 @@ @pytest.mark.asyncio async def test_check_error_success(): """Test that check_error does nothing when response is successful.""" - response = httpx.Response(status_code=200, request=httpx.Request("GET", "https://example.com")) + response = httpx.Response( + status_code=200, request=httpx.Request("GET", "https://example.com") + ) await check_error(response) @pytest.mark.asyncio async def test_check_error_non_json(): """Test that check_error calls raise_for_status when response is not JSON.""" - response = httpx.Response(status_code=500, content=b"Internal Server Error", request=httpx.Request("GET", "https://example.com")) + response = httpx.Response( + status_code=500, + content=b"Internal Server Error", + request=httpx.Request("GET", "https://example.com"), + ) with pytest.raises(httpx.HTTPStatusError): await check_error(response) @@ -31,7 +37,9 @@ async def test_check_error_api_error_single(): } ] } - response = httpx.Response(status_code=404, json=dto, request=httpx.Request("GET", "https://example.com")) + response = httpx.Response( + status_code=404, json=dto, request=httpx.Request("GET", "https://example.com") + ) with pytest.raises(Error) as exc_info: await check_error(response) @@ -49,7 +57,9 @@ async def test_check_error_api_error_multiple(): {"message": "Invalid parameter B", "code": "INVALID_PARAM"}, ] } - response = httpx.Response(status_code=400, json=dto, request=httpx.Request("GET", "https://example.com")) + response = httpx.Response( + status_code=400, json=dto, request=httpx.Request("GET", "https://example.com") + ) with pytest.raises(MultiError) as exc_info: await check_error(response) From 4d59e5e095da067f4ed60ca88d7d1e62880ddec6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:40:24 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A7=AA=20Add=20test=20verifying=20`ch?= =?UTF-8?q?eck=5Ferror`=20reads=20response=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR feedback by adding `test_check_error_reads_body` which verifies that `response.aread()` is called when `response.is_success` is False, ensuring the body is completely read for streamed responses. Also fixed PEP8 import order. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- tests/unit/test_http/test_api/test_errors.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit/test_http/test_api/test_errors.py b/tests/unit/test_http/test_api/test_errors.py index 526b2b8..72114bb 100644 --- a/tests/unit/test_http/test_api/test_errors.py +++ b/tests/unit/test_http/test_api/test_errors.py @@ -1,3 +1,5 @@ +from unittest.mock import AsyncMock + import httpx import pytest @@ -80,3 +82,18 @@ def test_multi_error_from_dto_empty_list(): """Test MultiError.from_dto raises ValueError on empty error list.""" with pytest.raises(ValueError, match="empty error list"): MultiError.from_dto({"errors": []}) + + +@pytest.mark.asyncio +async def test_check_error_reads_body(): + """Test that check_error reads the response body.""" + response = httpx.Response( + status_code=500, + content=b"Internal Server Error", + request=httpx.Request("GET", "https://example.com"), + ) + response.aread = AsyncMock() + with pytest.raises(httpx.HTTPStatusError): + await check_error(response) + + response.aread.assert_awaited_once()