diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b1cce5b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11"] + steps: + - uses: actions/checkout@v2 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[dev]" + - name: Test with pytest + run: python -m pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore index bfeabdf..ec45caf 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,9 @@ venv.bak/ .spyderproject .spyproject +# Visual Studio Code project settings +.vscode/ + # Rope project settings .ropeproject @@ -166,4 +169,5 @@ cython_debug/ # Others *inspo* -wip-file* \ No newline at end of file +wip-file* +__main__.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b000f61..ba6ab20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,3 @@ dependencies = [ [project.optional-dependencies] dev = ["ruff", "pytest"] -[tool.pyright] -venv = "venv" -venvPath = "." \ No newline at end of file diff --git a/sigpac_tools/anotate.py b/sigpac_tools/anotate.py index b42a821..c610867 100644 --- a/sigpac_tools/anotate.py +++ b/sigpac_tools/anotate.py @@ -68,9 +68,6 @@ def __query( print(response.url) return response.json() case _: - logger.error( - "Layer not supported. Supported layers: ['parcela', 'recinto']" - ) raise KeyError( "Layer not supported. Supported layers: ['parcela', 'recinto']" ) @@ -102,10 +99,8 @@ def get_metadata(layer: str, data: dict): If the layer is not supported """ if not layer: - logger.error("Layer not specified") raise ValueError("Layer not specified") elif layer not in ["parcela", "recinto"]: - logger.error("Layer not supported. Supported layers: ['parcela', 'recinto']") raise KeyError("Layer not supported. Supported layers: ['parcela', 'recinto']") prov = data.get("province", None) @@ -117,19 +112,14 @@ def get_metadata(layer: str, data: dict): zone = data.get("zone", 0) if not prov: - logger.error("Province not specified") raise ValueError("Province not specified") if not muni: - logger.error("Municipality not specified") raise ValueError("Municipality not specified") if not polg: - logger.error("Polygon not specified") raise ValueError("Polygon not specified") if not parc: - logger.error("Parcel not specified") raise ValueError("Parcel not specified") if not encl and layer == "recinto": - logger.error("Enclosure not specified") raise ValueError("Enclosure not specified") logger.info(f"Searching for the metadata of the location (province {prov}, municipality {muni}, polygon {polg}, parcel {parc}) in the SIGPAC database...") diff --git a/sigpac_tools/locate.py b/sigpac_tools/locate.py index 5cbb0f3..e161e7b 100644 --- a/sigpac_tools/locate.py +++ b/sigpac_tools/locate.py @@ -59,7 +59,6 @@ def geometry_from_coords(layer: str, lat: float, lon: float, reference: int) -> If the layer is not supported """ if not layer or not lat or not lon or not reference: - logger.error("Layer, latitude, longitude or reference not specified") raise ValueError("Layer, latitude, longitude or reference not specified") tile_x, tile_y = lng_lat_to_tile(lon, lat, 15) @@ -82,9 +81,6 @@ def geometry_from_coords(layer: str, lat: float, lon: float, reference: int) -> return result else: - logger.error( - f'Layer "{layer}" not supported. Supported layers: "parcela", "recinto"' - ) raise KeyError( f'Layer "{layer}" not supported. Supported layers: "parcela", "recinto"' ) diff --git a/sigpac_tools/search.py b/sigpac_tools/search.py index 318223e..edb1b5c 100644 --- a/sigpac_tools/search.py +++ b/sigpac_tools/search.py @@ -84,9 +84,6 @@ def search(data: dict) -> dict: return geojson else: - logger.error( - '"Community" has not been specified and it could have not been found from the "province" parameter' - ) raise ValueError( '"Community" has not been specified and it could have not been found from the "province" parameter' ) diff --git a/tests/test_anotate.py b/tests/test_anotate.py new file mode 100644 index 0000000..1d24ae5 --- /dev/null +++ b/tests/test_anotate.py @@ -0,0 +1,113 @@ +import pytest +from unittest.mock import patch +from sigpac_tools.anotate import get_metadata + +# Mock data for the test +layer_parcela_response = {"key": "value"} +layer_recinto_response = {"key": "value2"} + + +class TestGetMetadata: + @patch("sigpac_tools.anotate.__query") + def test_parcela_layer(self, mock_query): + mock_query.return_value = layer_parcela_response + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + "parcel": 1, + } + + result = get_metadata("parcela", data) + assert result == layer_parcela_response + + @patch("sigpac_tools.anotate.__query") + def test_recinto_layer(self, mock_query): + mock_query.return_value = layer_recinto_response + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + "parcel": 1, + "enclosure": 1, + } + + result = get_metadata("recinto", data) + assert result == layer_recinto_response + + def test_missing_layer(self): + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + "parcel": 1, + } + + with pytest.raises(ValueError, match="Layer not specified"): + get_metadata("", data) + + def test_invalid_layer(self): + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + "parcel": 1, + } + + with pytest.raises(KeyError): + get_metadata("invalid_layer", data) + + def test_missing_province(self): + data = { + "municipality": 1, + "polygon": 1, + "parcel": 1, + } + + with pytest.raises(ValueError): + get_metadata("parcela", data) + + def test_missing_municipality(self): + data = { + "province": 1, + "polygon": 1, + "parcel": 1, + } + + with pytest.raises(ValueError): + get_metadata("parcela", data) + + def test_missing_polygon(self): + data = { + "province": 1, + "municipality": 1, + "parcel": 1, + } + + with pytest.raises(ValueError): + get_metadata("parcela", data) + + def test_missing_parcel(self): + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + } + + with pytest.raises(ValueError): + get_metadata("parcela", data) + + def test_missing_enclosure_recinto_layer(self): + data = { + "province": 1, + "municipality": 1, + "polygon": 1, + "parcel": 1, + } + + with pytest.raises(ValueError): + get_metadata("recinto", data) + + +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_locate.py b/tests/test_locate.py new file mode 100644 index 0000000..ea2c493 --- /dev/null +++ b/tests/test_locate.py @@ -0,0 +1,101 @@ +import pytest +from unittest.mock import patch, Mock +from sigpac_tools.locate import geometry_from_coords +from sigpac_tools._globals import BASE_URL + +# Mock data for the geojson response +mock_geojson_response = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "parcela": 123, + "recinto": 456, + }, + "geometry": { + "type": "Polygon", + "coordinates": [[[0, 0], [1, 1], [1, 0], [0, 0]]], + }, + } + ], +} + +class TestGeometryFromCoords: + @patch("sigpac_tools.locate.requests.get") + @patch("sigpac_tools.locate.lng_lat_to_tile") + @patch("sigpac_tools.locate.transform_coords") + def test_geometry_from_coords_parcela(self, mock_transform_coords, mock_lng_lat_to_tile, mock_get): + mock_lng_lat_to_tile.return_value = (1234, 5678) + mock_response = Mock() + mock_response.json.return_value = mock_geojson_response + mock_get.return_value = mock_response + + layer = "parcela" + lat = 40.0 + lon = -3.0 + reference = 123 + + result = geometry_from_coords(layer, lat, lon, reference) + + mock_lng_lat_to_tile.assert_called_once_with(lon, lat, 15) + mock_get.assert_called_once_with(f"{BASE_URL}/vectorsdg/vector/parcela@3857/15.1234.5678.geojson") + mock_transform_coords.assert_called_once() + assert result == mock_geojson_response["features"][0]["geometry"] + + @patch("sigpac_tools.locate.requests.get") + @patch("sigpac_tools.locate.lng_lat_to_tile") + @patch("sigpac_tools.locate.transform_coords") + def test_geometry_from_coords_recinto(self, mock_transform_coords, mock_lng_lat_to_tile, mock_get): + mock_lng_lat_to_tile.return_value = (1234, 5678) + mock_response = Mock() + mock_response.json.return_value = mock_geojson_response + mock_get.return_value = mock_response + + layer = "recinto" + lat = 40.0 + lon = -3.0 + reference = 456 + + result = geometry_from_coords(layer, lat, lon, reference) + + mock_lng_lat_to_tile.assert_called_once_with(lon, lat, 15) + mock_get.assert_called_once_with(f"{BASE_URL}/vectorsdg/vector/recinto@3857/15.1234.5678.geojson") + mock_transform_coords.assert_called_once() + assert result == mock_geojson_response["features"][0]["geometry"] + + @patch("sigpac_tools.locate.requests.get") + @patch("sigpac_tools.locate.lng_lat_to_tile") + def test_geometry_not_found(self, mock_lng_lat_to_tile, mock_get): + mock_lng_lat_to_tile.return_value = (1234, 5678) + mock_response = Mock() + mock_response.json.return_value = {"type": "FeatureCollection", "features": []} + mock_get.return_value = mock_response + + layer = "parcela" + lat = 40.0 + lon = -3.0 + reference = 999 + + result = geometry_from_coords(layer, lat, lon, reference) + + mock_lng_lat_to_tile.assert_called_once_with(lon, lat, 15) + mock_get.assert_called_once_with(f"{BASE_URL}/vectorsdg/vector/parcela@3857/15.1234.5678.geojson") + assert result is None + + def test_invalid_layer(self): + with pytest.raises(KeyError, match='Layer "invalid" not supported. Supported layers: "parcela", "recinto"'): + geometry_from_coords("invalid", 40.0, -3.0, 123) + + def test_missing_parameters(self): + with pytest.raises(ValueError, match="Layer, latitude, longitude or reference not specified"): + geometry_from_coords("", 40.0, -3.0, 123) + with pytest.raises(ValueError, match="Layer, latitude, longitude or reference not specified"): + geometry_from_coords("parcela", None, -3.0, 123) + with pytest.raises(ValueError, match="Layer, latitude, longitude or reference not specified"): + geometry_from_coords("parcela", 40.0, None, 123) + with pytest.raises(ValueError, match="Layer, latitude, longitude or reference not specified"): + geometry_from_coords("parcela", 40.0, -3.0, None) + +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_search.py b/tests/test_search.py new file mode 100644 index 0000000..8f9aa97 --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,109 @@ +import pytest +from unittest.mock import patch, Mock +from sigpac_tools.search import search +from sigpac_tools._globals import BASE_URL + +# Mock data for responses +provinces_response = {"type": "FeatureCollection", "features": []} +municipalities_response = {"type": "FeatureCollection", "features": []} +polygons_response = {"type": "FeatureCollection", "features": []} +parcels_response = {"type": "FeatureCollection", "features": []} +parcel_response = {"type": "FeatureCollection", "features": [{"id": "parcel"}]} + + +class TestSearch: + @patch("sigpac_tools.search.requests.get") + @patch("sigpac_tools.search.findCommunity") + def test_search_provinces(self, mock_findCommunity, mock_get): + mock_findCommunity.return_value = 1 + mock_response = Mock() + mock_response.json.return_value = provinces_response + mock_get.return_value = mock_response + + data = {"community": 1} + result = search(data) + + mock_get.assert_called_once_with( + f"{BASE_URL}/fega/ServiciosVisorSigpac/query/provincias/1.geojson" + ) + assert result == provinces_response + + @patch("sigpac_tools.search.requests.get") + @patch("sigpac_tools.search.findCommunity") + def test_search_municipalities(self, mock_findCommunity, mock_get): + mock_response = Mock() + mock_response.json.return_value = municipalities_response + mock_get.return_value = mock_response + + data = {"province": 1} + result = search(data) + + mock_get.assert_called_once_with( + f"{BASE_URL}/fega/ServiciosVisorSigpac/query/municipios/1.geojson" + ) + assert result == municipalities_response + + @patch("sigpac_tools.search.requests.get") + def test_search_polygons(self, mock_get): + mock_response = Mock() + mock_response.json.return_value = polygons_response + mock_get.return_value = mock_response + + data = {"province": 1, "municipality": 1} + result = search(data) + + mock_get.assert_called_once_with( + f"{BASE_URL}/fega/ServiciosVisorSigpac/query/poligonos/1/1/0/0.geojson" + ) + assert result == polygons_response + + @patch("sigpac_tools.search.requests.get") + def test_search_parcels(self, mock_get): + mock_response = Mock() + mock_response.json.return_value = parcels_response + mock_get.return_value = mock_response + + data = {"province": 1, "municipality": 1, "polygon": 1} + result = search(data) + + mock_get.assert_called_once_with( + f"{BASE_URL}/fega/ServiciosVisorSigpac/query/parcelas/1/1/0/0/1.geojson" + ) + assert result == parcels_response + + @patch("sigpac_tools.search.requests.get") + def test_search_specific_parcel(self, mock_get): + mock_response = Mock() + mock_response.json.return_value = parcel_response + mock_get.return_value = mock_response + + data = {"province": 1, "municipality": 1, "polygon": 1, "parcel": 1} + result = search(data) + + mock_get.assert_called_once_with( + f"{BASE_URL}/fega/ServiciosVisorSigpac/query/recintos/1/1/0/0/1/1.geojson" + ) + assert result == parcel_response + + @patch("sigpac_tools.search.findCommunity") + def test_missing_community_and_province(self, mock_findCommunity): + data = {"municipality": 1} + with pytest.raises( + ValueError, + match='"Community" has not been specified, neither has been "province" and it is compulsory to find the community associated', + ): + search(data) + + @patch("sigpac_tools.search.findCommunity") + def test_missing_community_not_found(self, mock_findCommunity): + mock_findCommunity.return_value = None + data = {"province": 1} + with pytest.raises( + ValueError, + match='"Community" has not been specified and it could have not been found from the "province" parameter', + ): + search(data) + + +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..b1b8a37 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,58 @@ +import pytest + +from sigpac_tools.utils import lng_lat_to_tile, transform_coords, findCommunity + + +class TestLngLatToTile: + def test_central_coordinates(self): + lng, lat, zoom = 0, 0, 1 + expected = (0, 0) + assert lng_lat_to_tile(lng, lat, zoom) == expected + + def test_different_zoom_level(self): + lng, lat, zoom = 0, 0, 2 + expected = (1, 1) + assert lng_lat_to_tile(lng, lat, zoom) == expected + + def test_specific_coordinates(self): + lng, lat, zoom = -3.7038, 40.4168, 10 + expected = (501, 637) + assert lng_lat_to_tile(lng, lat, zoom) == expected + + +class TestTransformCoords: + def test_transform_coordinates(self): + feature = { + "geometry": { + "coordinates": [[[1492237.0, 6894685.0], [1492237.0, 6894685.0]]] + } + } + + transform_coords(feature) + coords = feature["geometry"]["coordinates"][0][0] + + # Expected coordinates in EPSG:4326 + expected_coords = [13.3781405, 52.5150436] + + assert abs(round(coords[0], 6) - round(expected_coords[0], 6)) < 0.05 + assert abs(round(coords[1], 6) - round(expected_coords[1], 6)) < 0.05 + + +class TestFindCommunity: + def test_existing_province_id(self): + province_id = 29 + expected = 1 + assert findCommunity(province_id) == expected + + def test_non_existing_province_id(self): + province_id = 60 + assert findCommunity(province_id) is None + + def test_another_existing_province_id(self): + province_id = 2 + expected = 8 + assert findCommunity(province_id) == expected + + +if __name__ == "__main__": + pytest.main()