"""Tests for forests, nature reserves, and landscape parks download paths.""" from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch import geopandas as gpd from shapely.geometry import Polygon from python_pkg.geo_data._poland_nature import ( get_polish_forests, get_polish_landscape_parks, get_polish_nature_reserves, ) def _make_relation_element(name: str, *, include_outer: bool = True) -> dict[str, Any]: """Create a mock OSM relation element.""" members = [] if include_outer: members.append( { "role": "outer", "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], } ) return {"type": "relation", "tags": {"name": name}, "members": members} _POLY = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) class TestGetPolishForests: """Tests for get_polish_forests.""" @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_with_area( 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 = gpd.GeoDataFrame( {"name": ["Puszcza Białowieska"], "area_km2": [600.0]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_forests() assert result.iloc[0]["area_km2"] == 600.0 @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_without_area( 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 = gpd.GeoDataFrame( {"name": ["Puszcza Białowieska"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_forests() assert len(result) == 1 def test_downloads_forests(self) -> None: with ( patch("python_pkg.geo_data._poland_nature.sys.stdout"), patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_nature._add_area_column" ) as mock_add_area, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = { "elements": [ # Valid forest with keyword { "type": "way", "tags": {"name": "Puszcza Białowieska"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # Bory keyword { "type": "way", "tags": {"name": "Bory Tucholskie"}, "geometry": [ {"lon": 2, "lat": 2}, {"lon": 3, "lat": 2}, {"lon": 3, "lat": 3}, {"lon": 2, "lat": 3}, ], }, # No forest keyword -> skip { "type": "way", "tags": {"name": "Random Wood"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # Duplicate { "type": "way", "tags": {"name": "Puszcza Białowieska"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # No name {"type": "way", "tags": {}, "geometry": []}, # Geometry extraction fails (too few coords) { "type": "way", "tags": {"name": "Las Mały"}, "geometry": [{"lon": 0, "lat": 0}], }, ] } mock_gdf = gpd.GeoDataFrame( {"name": ["Puszcza Białowieska", "Bory Tucholskie"]}, geometry=[_POLY, _POLY], crs="EPSG:4326", ) mock_from_features.return_value = mock_gdf gdf_with_area = mock_gdf.copy() gdf_with_area["area_km2"] = [600.0, 300.0] mock_add_area.return_value = gdf_with_area result = get_polish_forests() assert len(result) == 2 def test_downloads_forests_empty(self) -> None: with ( patch("python_pkg.geo_data._poland_nature.sys.stdout"), patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_nature._add_area_column" ) as mock_add_area, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = {"elements": []} empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) mock_from_features.return_value = empty_gdf mock_add_area.return_value = empty_gdf result = get_polish_forests() assert len(result) == 0 class TestGetPolishNatureReserves: """Tests for get_polish_nature_reserves.""" @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_with_area( 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 = gpd.GeoDataFrame( {"name": ["Rezerwat X"], "area_km2": [50.0]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_nature_reserves() assert result.iloc[0]["area_km2"] == 50.0 @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_without_area( 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 = gpd.GeoDataFrame( {"name": ["Rezerwat X"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_nature_reserves() assert len(result) == 1 def test_downloads_reserves(self) -> None: with ( patch("python_pkg.geo_data._poland_nature.sys.stdout"), patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_nature._add_area_column" ) as mock_add_area, ): 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", "tags": {"name": "Rezerwat A"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # Duplicate { "type": "way", "tags": {"name": "Rezerwat A"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # No name {"type": "way", "tags": {}, "geometry": []}, # Geometry fails { "type": "way", "tags": {"name": "Tiny"}, "geometry": [{"lon": 0, "lat": 0}], }, ] } mock_gdf = gpd.GeoDataFrame( {"name": ["Rezerwat A"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_from_features.return_value = mock_gdf gdf_with_area = mock_gdf.copy() gdf_with_area["area_km2"] = [50.0] mock_add_area.return_value = gdf_with_area result = get_polish_nature_reserves() assert len(result) == 1 def test_downloads_reserves_empty(self) -> None: with ( patch("python_pkg.geo_data._poland_nature.sys.stdout"), patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_nature._add_area_column" ) as mock_add_area, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = {"elements": []} empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) mock_from_features.return_value = empty_gdf mock_add_area.return_value = empty_gdf result = get_polish_nature_reserves() assert len(result) == 0 class TestGetPolishLandscapeParks: """Tests for get_polish_landscape_parks.""" @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_with_area( 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 = gpd.GeoDataFrame( {"name": ["Park Krajobrazowy X"], "area_km2": [100.0]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_landscape_parks() assert result.iloc[0]["area_km2"] == 100.0 @patch("python_pkg.geo_data._poland_nature.gpd.read_file") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") def test_cached_without_area( 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 = gpd.GeoDataFrame( {"name": ["Park Krajobrazowy X"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_landscape_parks() assert len(result) == 1 @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") @patch("python_pkg.geo_data._poland_nature._overpass_query") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") @patch("python_pkg.geo_data._poland_nature.sys.stdout") def test_downloads_landscape_parks( 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": [ _make_relation_element("Park Krajobrazowy A"), # Not a relation -> skip { "type": "way", "tags": {"name": "Park Krajobrazowy B"}, "geometry": [], }, # No name {"type": "relation", "tags": {}, "members": []}, # Duplicate _make_relation_element("Park Krajobrazowy A"), # No outer rings _make_relation_element("Park Empty", include_outer=False), ] } mock_gdf = gpd.GeoDataFrame( {"name": ["Park Krajobrazowy A"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_from_features.return_value = mock_gdf result = get_polish_landscape_parks() assert len(result) == 1 @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") @patch("python_pkg.geo_data._poland_nature._overpass_query") @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") @patch("python_pkg.geo_data._poland_nature.sys.stdout") def test_downloads_landscape_parks_empty( 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": []} empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) mock_from_features.return_value = empty_gdf result = get_polish_landscape_parks() assert len(result) == 0