From feb1fb958517337a8d1e06ac2d669dd1f1ecc447 Mon Sep 17 00:00:00 2001 From: nlebovits Date: Sat, 4 Oct 2025 20:17:28 -0300 Subject: [PATCH 1/2] Add test coverage for stac_api_io.py error handling - Test decode error in request() method - Test write to URL error in write_text_to_href() - Test unknown STAC object type error in stac_object_from_dict() --- CHANGELOG.md | 4 +++ tests/test_stac_api_io.py | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d763cb..8fec2c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + +- Add test coverage for stac_api_io.py error handling ([#835](https://github.com/stac-utils/pystac-client/pull/835)) + ### Changed - Make `get_collection` raise if `collection_id` is empty ([#809](https://github.com/stac-utils/pystac-client/pull/809)) diff --git a/tests/test_stac_api_io.py b/tests/test_stac_api_io.py index 46369a26..a3fbcafa 100644 --- a/tests/test_stac_api_io.py +++ b/tests/test_stac_api_io.py @@ -1,5 +1,6 @@ import typing from pathlib import Path +from typing import Any from urllib.parse import parse_qs, urlsplit import pystac @@ -289,3 +290,58 @@ def test_stac_io_in_pystac() -> None: stac_io = root._stac_io assert isinstance(stac_io, StacApiIO) assert stac_io.timeout == 42 + + +def test_request_decode_error(requests_mock: Mocker) -> None: + """Test that decode errors in request() are properly handled.""" + url = "https://example.com/bad-encoding" + # Mock a response with invalid UTF-8 content + requests_mock.get(url, status_code=200, content=b"\xff\xfe\x00\x00") + + stac_api_io = StacApiIO() + + with pytest.raises(APIError) as excinfo: + stac_api_io.request(url) + + assert ( + "decode" in str(excinfo.value).lower() or "utf-8" in str(excinfo.value).lower() + ) + + +def test_write_text_to_href_url_error() -> None: + """Test that write_text_to_href raises APIError for URLs.""" + stac_api_io = StacApiIO() + + with pytest.raises(APIError, match="Transactions not supported"): + stac_api_io.write_text_to_href("https://example.com/write", "content") + + +def test_stac_object_from_dict_unknown_type(monkeypatch: MonkeyPatch) -> None: + """Test that unknown STAC object types raise ValueError.""" + stac_api_io = StacApiIO() + + import json + + with open("tests/data/planetary-computer-collection.json") as f: + real_stac_data = json.load(f) + + # Mock identify_stac_object to return an unknown type + class MockInfo: + object_type = "UNKNOWN_TYPE" + + def mock_identify_stac_object(d: dict[str, Any]) -> MockInfo: + return MockInfo() + + # Mock migrate_to_latest to just return the data unchanged + def mock_migrate_to_latest(d: dict[str, Any], info: MockInfo) -> dict[str, Any]: + return d + + monkeypatch.setattr( + "pystac_client.stac_api_io.identify_stac_object", mock_identify_stac_object + ) + monkeypatch.setattr( + "pystac_client.stac_api_io.migrate_to_latest", mock_migrate_to_latest + ) + + with pytest.raises(ValueError, match="Unknown STAC object type"): + stac_api_io.stac_object_from_dict(real_stac_data) From 2c52719d9d99204196a9efb3a81d75ad85265734 Mon Sep 17 00:00:00 2001 From: nlebovits Date: Wed, 22 Oct 2025 15:30:47 -0300 Subject: [PATCH 2/2] remove unreachable test; add no cover info as comment --- pystac_client/stac_api_io.py | 6 +++++- tests/test_stac_api_io.py | 32 -------------------------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/pystac_client/stac_api_io.py b/pystac_client/stac_api_io.py index 0daf73e5..479c9c99 100644 --- a/pystac_client/stac_api_io.py +++ b/pystac_client/stac_api_io.py @@ -287,7 +287,11 @@ def stac_object_from_dict( d, href=str(href), root=root, migrate=False, preserve_dict=preserve_dict ) - raise ValueError(f"Unknown STAC object type {info.object_type}") + raise ValueError( + f"Unknown STAC object type {info.object_type}" + ) # pragma: no cover + # This is unreachable because pystac.identify_stac_object raises STACTypeError + # for unknown object types before this code is executed def get_pages( self, diff --git a/tests/test_stac_api_io.py b/tests/test_stac_api_io.py index a3fbcafa..948601e1 100644 --- a/tests/test_stac_api_io.py +++ b/tests/test_stac_api_io.py @@ -1,6 +1,5 @@ import typing from pathlib import Path -from typing import Any from urllib.parse import parse_qs, urlsplit import pystac @@ -314,34 +313,3 @@ def test_write_text_to_href_url_error() -> None: with pytest.raises(APIError, match="Transactions not supported"): stac_api_io.write_text_to_href("https://example.com/write", "content") - - -def test_stac_object_from_dict_unknown_type(monkeypatch: MonkeyPatch) -> None: - """Test that unknown STAC object types raise ValueError.""" - stac_api_io = StacApiIO() - - import json - - with open("tests/data/planetary-computer-collection.json") as f: - real_stac_data = json.load(f) - - # Mock identify_stac_object to return an unknown type - class MockInfo: - object_type = "UNKNOWN_TYPE" - - def mock_identify_stac_object(d: dict[str, Any]) -> MockInfo: - return MockInfo() - - # Mock migrate_to_latest to just return the data unchanged - def mock_migrate_to_latest(d: dict[str, Any], info: MockInfo) -> dict[str, Any]: - return d - - monkeypatch.setattr( - "pystac_client.stac_api_io.identify_stac_object", mock_identify_stac_object - ) - monkeypatch.setattr( - "pystac_client.stac_api_io.migrate_to_latest", mock_migrate_to_latest - ) - - with pytest.raises(ValueError, match="Unknown STAC object type"): - stac_api_io.stac_object_from_dict(real_stac_data)