diff --git a/imednet/core/paginator.py b/imednet/core/paginator.py index 00956e67..cefbd9bd 100644 --- a/imednet/core/paginator.py +++ b/imednet/core/paginator.py @@ -105,10 +105,9 @@ def _iter_sync(self) -> Iterator[Any]: # Raw list endpoints do not support pagination params response: httpx.Response = client.get(self.path, params=self.params) payload = response.json() - if isinstance(payload, list): - yield from payload - else: - yield from [] + if not isinstance(payload, list): + raise TypeError(f"API response must be a list, got {type(payload).__name__}") + yield from payload class AsyncJsonListPaginator(AsyncPaginator): @@ -119,9 +118,7 @@ async def _iter_async(self) -> AsyncIterator[Any]: # Raw list endpoints do not support pagination params response: httpx.Response = await client.get(self.path, params=self.params) payload = response.json() - if isinstance(payload, list): - for item in payload: - yield item - else: - # Fallback for empty or malformed response - pass + if not isinstance(payload, list): + raise TypeError(f"API response must be a list, got {type(payload).__name__}") + for item in payload: + yield item diff --git a/poetry.lock b/poetry.lock index fc1d01fe..c6447985 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2759,14 +2759,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "3.1.5" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, - {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] @@ -2800,4 +2800,4 @@ sqlalchemy = ["SQLAlchemy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "f566c8f13cf3eb0c80d496ed8ecf21f9cdd87e0ad0277fe67db4440f8ab6bb09" +content-hash = "9eb342134be043a99cd62fb62e7c27ef7631d148f4038092163da233d629525e" diff --git a/pyproject.toml b/pyproject.toml index 4d308f2e..8981519a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ faker = "^24.9" pre-commit = "^4.2.0" virtualenv = "^20.36.1" isort = "^6.0.1" -werkzeug = "^3.1.5" +werkzeug = "^3.1.6" sphinx = "^6.2.0" sphinx-autodoc-typehints = "*" sphinx-rtd-theme = "^3.0.2" diff --git a/tests/unit/test_json_list_paginator_robustness.py b/tests/unit/test_json_list_paginator_robustness.py new file mode 100644 index 00000000..44a7653c --- /dev/null +++ b/tests/unit/test_json_list_paginator_robustness.py @@ -0,0 +1,71 @@ +from unittest.mock import Mock + +import pytest + +from imednet.core.paginator import AsyncJsonListPaginator, JsonListPaginator + + +class MockClient: + def __init__(self, response_data): + self.response_data = response_data + + def get(self, path, params=None): + response = Mock() + response.json.return_value = self.response_data + return response + + +class MockAsyncClient: + def __init__(self, response_data): + self.response_data = response_data + + async def get(self, path, params=None): + response = Mock() + response.json.return_value = self.response_data + return response + + +def test_json_list_paginator_raises_on_dict(): + """ + Test that JsonListPaginator raises TypeError when the API returns a dictionary + instead of the expected list. + """ + # Simulate an error response or unexpected object structure + client = MockClient({"error": "Something went wrong", "details": "Unexpected format"}) + paginator = JsonListPaginator(client, "/path") + + # Currently this fails (returns empty list), we want it to raise TypeError + with pytest.raises(TypeError, match="API response must be a list"): + list(paginator) + + +def test_json_list_paginator_raises_on_none(): + """Test that JsonListPaginator raises TypeError when the API returns null.""" + client = MockClient(None) + paginator = JsonListPaginator(client, "/path") + + with pytest.raises(TypeError, match="API response must be a list"): + list(paginator) + + +@pytest.mark.asyncio +async def test_async_json_list_paginator_raises_on_dict(): + """ + Test that AsyncJsonListPaginator raises TypeError when the API returns a dictionary + instead of the expected list. + """ + client = MockAsyncClient({"error": "Async error"}) + paginator = AsyncJsonListPaginator(client, "/path") # type: ignore + + with pytest.raises(TypeError, match="API response must be a list"): + [item async for item in paginator] + + +@pytest.mark.asyncio +async def test_async_json_list_paginator_raises_on_none(): + """Test that AsyncJsonListPaginator raises TypeError when the API returns null.""" + client = MockAsyncClient(None) + paginator = AsyncJsonListPaginator(client, "/path") # type: ignore + + with pytest.raises(TypeError, match="API response must be a list"): + [item async for item in paginator]