"""Tests for python_pkg.geo_data._warsaw module.""" from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch import geopandas as gpd from shapely.geometry import LineString, Polygon from python_pkg.geo_data._warsaw import ( _merge_bridge_segments, get_vistula_river, get_warsaw_boundary, get_warsaw_bridges, get_warsaw_districts, ) class TestGetWarsawBoundary: """Tests for get_warsaw_boundary.""" @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") def test_cached(self, mock_cache_dir: MagicMock, mock_read: MagicMock) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_read.return_value = mock_gdf result = get_warsaw_boundary() assert result is mock_gdf @patch("python_pkg.geo_data._warsaw.gpd.GeoDataFrame.to_file") @patch("python_pkg.geo_data._warsaw._ensure_cache_dir") @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw._PKG_DIR") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") def test_from_districts_file_with_warszawa( self, mock_cache_dir: MagicMock, mock_pkg_dir: MagicMock, mock_read: MagicMock, mock_ensure: MagicMock, mock_to_file: MagicMock, ) -> None: mock_cache_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_cache_path) mock_cache_path.exists.return_value = False mock_districts_path = MagicMock() mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( return_value=MagicMock() ) mock_pkg_dir.__truediv__.return_value.__truediv__.return_value.__truediv__ = ( MagicMock(return_value=mock_districts_path) ) mock_districts_path.exists.return_value = True mock_warsaw_gdf = gpd.GeoDataFrame( {"name": ["Warszawa", "Mokotów"]}, geometry=[ Polygon([(20, 52), (21, 52), (21, 53), (20, 53)]), Polygon([(20.5, 52.5), (20.6, 52.5), (20.6, 52.6), (20.5, 52.6)]), ], crs="EPSG:4326", ) mock_read.return_value = mock_warsaw_gdf result = get_warsaw_boundary() assert len(result) == 1 @patch("python_pkg.geo_data._warsaw.gpd.GeoDataFrame.to_file") @patch("python_pkg.geo_data._warsaw._ensure_cache_dir") @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw._PKG_DIR") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") def test_from_districts_file_no_warszawa_entry( self, mock_cache_dir: MagicMock, mock_pkg_dir: MagicMock, mock_read: MagicMock, mock_ensure: MagicMock, mock_to_file: MagicMock, ) -> None: mock_cache_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_cache_path) mock_cache_path.exists.return_value = False mock_districts_path = MagicMock() mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( return_value=MagicMock() ) mock_pkg_dir.__truediv__.return_value.__truediv__.return_value.__truediv__ = ( MagicMock(return_value=mock_districts_path) ) mock_districts_path.exists.return_value = True # No "Warszawa" entry mock_warsaw_gdf = gpd.GeoDataFrame( {"name": ["Mokotów", "Śródmieście"]}, geometry=[ Polygon([(20, 52), (21, 52), (21, 53), (20, 53)]), Polygon([(20.5, 52.5), (20.6, 52.5), (20.6, 52.6), (20.5, 52.6)]), ], crs="EPSG:4326", ) mock_read.return_value = mock_warsaw_gdf result = get_warsaw_boundary() assert len(result) == 1 def test_fallback_overpass(self) -> None: with ( patch("python_pkg.geo_data._warsaw.sys.stdout"), patch("python_pkg.geo_data._warsaw.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._warsaw._PKG_DIR") as mock_pkg_dir, patch("python_pkg.geo_data._warsaw._overpass_query") as mock_query, patch("python_pkg.geo_data._warsaw._ensure_cache_dir"), patch( "python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features" ) as mock_from_features, ): mock_cache_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_cache_path) mock_cache_path.exists.return_value = False mock_districts_path = MagicMock() mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( return_value=MagicMock() ) nested = mock_pkg_dir.__truediv__.return_value.__truediv__ nested.return_value.__truediv__ = MagicMock( return_value=mock_districts_path ) mock_districts_path.exists.return_value = False mock_query.return_value = { "elements": [ { "type": "relation", "members": [ { "role": "outer", "geometry": [ {"lon": 20, "lat": 52}, {"lon": 21, "lat": 52}, {"lon": 21, "lat": 53}, ], }, # non-outer member { "role": "inner", "geometry": [ {"lon": 20.5, "lat": 52.5}, ], }, ], }, # Not a relation {"type": "way"}, # Relation with no outer geometry (empty coords) { "type": "relation", "members": [ {"role": "inner", "geometry": [{"lon": 20, "lat": 52}]}, ], }, ] } mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_from_features.return_value = mock_gdf result = get_warsaw_boundary() assert result is mock_gdf class TestGetWarsawDistricts: """Tests for get_warsaw_districts.""" @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw._PKG_DIR") def test_districts_file_exists( self, mock_pkg_dir: MagicMock, mock_read: MagicMock ) -> None: mock_districts_path = MagicMock() mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( return_value=MagicMock() ) mock_pkg_dir.__truediv__.return_value.__truediv__.return_value.__truediv__ = ( MagicMock(return_value=mock_districts_path) ) mock_districts_path.exists.return_value = True mock_gdf = gpd.GeoDataFrame( {"name": ["Warszawa", "Mokotów", "Śródmieście"]}, geometry=[ Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]), Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]), Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]), ], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_warsaw_districts() assert "Warszawa" not in result["name"].values @patch("python_pkg.geo_data._warsaw._PKG_DIR") def test_districts_file_not_found(self, mock_pkg_dir: MagicMock) -> None: mock_districts_path = MagicMock() mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( return_value=MagicMock() ) mock_pkg_dir.__truediv__.return_value.__truediv__.return_value.__truediv__ = ( MagicMock(return_value=mock_districts_path) ) mock_districts_path.exists.return_value = False import pytest with pytest.raises(FileNotFoundError, match="Warsaw districts GeoJSON"): get_warsaw_districts() class TestGetVistulaRiver: """Tests for get_vistula_river.""" @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") def test_cached(self, mock_cache_dir: MagicMock, mock_read: MagicMock) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_read.return_value = mock_gdf result = get_vistula_river() assert result is mock_gdf @patch("python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features") @patch("python_pkg.geo_data._warsaw._ensure_cache_dir") @patch("python_pkg.geo_data._warsaw._overpass_query") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") @patch("python_pkg.geo_data._warsaw.sys.stdout") def test_downloads( self, mock_stdout: MagicMock, mock_cache_dir: MagicMock, mock_query: MagicMock, mock_ensure: MagicMock, mock_from_features: MagicMock, ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = { "elements": [ { "type": "way", "geometry": [ {"lon": 20.0, "lat": 52.0}, {"lon": 21.0, "lat": 52.5}, ], }, # Too few coords { "type": "way", "geometry": [{"lon": 20.0, "lat": 52.0}], }, # Not a way {"type": "node"}, # Way without geometry {"type": "way"}, ] } mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_from_features.return_value = mock_gdf result = get_vistula_river() assert result is mock_gdf class TestGetWarsawBridges: """Tests for get_warsaw_bridges.""" @patch("python_pkg.geo_data._warsaw.gpd.read_file") @patch("python_pkg.geo_data._warsaw.CACHE_DIR") def test_cached(self, mock_cache_dir: MagicMock, mock_read: MagicMock) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_read.return_value = mock_gdf result = get_warsaw_bridges() assert result is mock_gdf def test_downloads(self) -> None: with ( patch("python_pkg.geo_data._warsaw.sys.stdout"), patch("python_pkg.geo_data._warsaw.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._warsaw.get_vistula_river") as mock_vistula, patch("python_pkg.geo_data._warsaw._overpass_query") as mock_query, patch("python_pkg.geo_data._warsaw._ensure_cache_dir"), patch( "python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features" ) as mock_from_features, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False # Create a real Vistula geometry for intersection tests vistula_gdf = gpd.GeoDataFrame( {"name": ["Wisła"]}, geometry=[LineString([(20.0, 52.2), (21.0, 52.2)])], crs="EPSG:4326", ) mock_vistula.return_value = vistula_gdf mock_query.return_value = { "elements": [ # Bridge that intersects vistula buffer { "type": "way", "id": 1, "tags": {"name": "Most Łazienkowski"}, "geometry": [ {"lon": 20.5, "lat": 52.19}, {"lon": 20.5, "lat": 52.21}, ], }, # Bridge far from vistula { "type": "way", "id": 2, "tags": {"name": "Most Daleki"}, "geometry": [ {"lon": 20.5, "lat": 55.0}, {"lon": 20.5, "lat": 55.1}, ], }, # Not a way {"type": "node", "tags": {"name": "Most X"}}, # Way without geometry {"type": "way", "tags": {"name": "Most Y"}}, # No name { "type": "way", "id": 3, "tags": {}, "geometry": [ {"lon": 20.5, "lat": 52.19}, {"lon": 20.5, "lat": 52.21}, ], }, # Duplicate { "type": "way", "id": 4, "tags": {"name": "Most Łazienkowski"}, "geometry": [ {"lon": 20.5, "lat": 52.19}, {"lon": 20.5, "lat": 52.21}, ], }, # Too few coords { "type": "way", "id": 5, "tags": {"name": "Most Short"}, "geometry": [{"lon": 20.5, "lat": 52.19}], }, ] } mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_from_features.return_value = mock_gdf result = get_warsaw_bridges() assert result is mock_gdf class TestMergeBridgeSegments: """Tests for _merge_bridge_segments.""" def test_single_segment(self) -> None: features: list[dict[str, Any]] = [ { "properties": {"name": "Most A"}, "geometry": {"coordinates": [(20, 52), (21, 52)]}, } ] result = _merge_bridge_segments(features) assert len(result) == 1 assert result[0]["geometry"]["type"] == "LineString" def test_multiple_segments_same_name(self) -> None: features: list[dict[str, Any]] = [ { "properties": {"name": "Most A"}, "geometry": {"coordinates": [(20, 52), (21, 52)]}, }, { "properties": {"name": "Most A"}, "geometry": {"coordinates": [(21, 52), (22, 52)]}, }, ] result = _merge_bridge_segments(features) assert len(result) == 1 assert result[0]["geometry"]["type"] == "MultiLineString"