From 99de43c0ce31b456fcd2948b94373c1139639e3b Mon Sep 17 00:00:00 2001 From: Hans Vanrompay Date: Tue, 13 Aug 2024 12:18:21 +0200 Subject: [PATCH 1/4] unit test for fetcher --- .../test_unit_collection_fetcher.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tests/test_openeo_gfmap/test_unit_collection_fetcher.py diff --git a/tests/test_openeo_gfmap/test_unit_collection_fetcher.py b/tests/test_openeo_gfmap/test_unit_collection_fetcher.py new file mode 100644 index 0000000..77d964a --- /dev/null +++ b/tests/test_openeo_gfmap/test_unit_collection_fetcher.py @@ -0,0 +1,126 @@ +from unittest.mock import MagicMock + +import pytest +import xarray as xr + +from openeo_gfmap import BoundingBoxExtent, TemporalContext +from openeo_gfmap.backend import BackendContext +from openeo_gfmap.fetching import CollectionFetcher + + +@pytest.fixture +def mock_backend_context(): + """Fixture to create a mock backend context.""" + return MagicMock(spec=BackendContext) + + +@pytest.fixture +def mock_connection(): + """Fixture to create a mock connection.""" + return MagicMock() + + +@pytest.fixture +def mock_spatial_extent(): + """Fixture for spatial extent.""" + return BoundingBoxExtent( + west=5.0515130512706845, + south=51.215806593713, + east=5.060320484557499, + north=51.22149744530769, + epsg=4326, + ) + + +@pytest.fixture +def mock_temporal_context(): + """Fixture for temporal context.""" + return TemporalContext(start_date="2023-04-01", end_date="2023-05-01") + + +@pytest.fixture +def mock_collection_fetch(): + """Fixture for the collection fetch function.""" + return MagicMock(return_value=xr.DataArray([1, 2, 3], dims=["bands"])) + + +@pytest.fixture +def mock_collection_preprocessing(): + """Fixture for the collection preprocessing function.""" + return MagicMock(return_value=xr.DataArray([1, 2, 3], dims=["bands"])) + + +def test_collection_fetcher( + mock_connection, + mock_spatial_extent, + mock_temporal_context, + mock_backend_context, + mock_collection_fetch, + mock_collection_preprocessing, +): + """Test CollectionFetcher with basic data fetching.""" + + # Create the CollectionFetcher with the mock functions + fetcher = CollectionFetcher( + backend_context=mock_backend_context, + bands=["B01", "B02", "B03"], + collection_fetch=mock_collection_fetch, # Use the mock collection fetch function + collection_preprocessing=mock_collection_preprocessing, # Use the mock preprocessing function + ) + + # Call the method you're testing + result = fetcher.get_cube( + mock_connection, mock_spatial_extent, mock_temporal_context + ) + + # Assertions to check if everything works as expected + assert isinstance( + result, xr.DataArray + ) # Check if the result is an xarray DataArray + assert result.dims == ("bands",) # Ensure the dimensions are as expected + assert result.values.tolist() == [ + 1, + 2, + 3, + ] # Ensure the values match the expected output + + +def test_collection_fetcher_get_cube( + mock_connection, + mock_spatial_extent, + mock_temporal_context, + mock_backend_context, + mock_collection_fetch, + mock_collection_preprocessing, +): + """Test that CollectionFetcher.get_cube is called correctly.""" + + bands = ["S2-L2A-B01", "S2-L2A-B02"] + + # Create the CollectionFetcher with the mock functions + fetcher = CollectionFetcher( + backend_context=mock_backend_context, + bands=bands, + collection_fetch=mock_collection_fetch, # Use the mock collection fetch function + collection_preprocessing=mock_collection_preprocessing, # Use the mock preprocessing function + ) + + # Call the method you're testing + result = fetcher.get_cube( + mock_connection, mock_spatial_extent, mock_temporal_context + ) + + # Assert the fetch method was called with the correct arguments + mock_collection_fetch.assert_called_once_with( + mock_connection, + mock_spatial_extent, + mock_temporal_context, + bands, + **fetcher.params, # Check for additional parameters if any + ) + + # Assert the preprocessing method was called once + mock_collection_preprocessing.assert_called_once() + + # Assert that the result is an instance of xarray.DataArray + assert isinstance(result, xr.DataArray) From 452b55d0e535e60c9103a7e8e304e65038321038 Mon Sep 17 00:00:00 2001 From: Hans Vanrompay Date: Tue, 13 Aug 2024 15:00:49 +0200 Subject: [PATCH 2/4] unit test for s2 fetcher --- src/openeo_gfmap/fetching/s2.py | 1 + .../test_unit_collection_fetcher.py | 16 +++ .../test_unit_s2_extractors.py | 123 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 tests/test_openeo_gfmap/test_unit_s2_extractors.py diff --git a/src/openeo_gfmap/fetching/s2.py b/src/openeo_gfmap/fetching/s2.py index 690f872..94934da 100644 --- a/src/openeo_gfmap/fetching/s2.py +++ b/src/openeo_gfmap/fetching/s2.py @@ -113,6 +113,7 @@ def s2_l2a_fetch_default( return s2_l2a_fetch_default +# TODO deprecated? def _get_s2_l2a_element84_fetcher( collection_name: str, fetch_type: FetchType ) -> Callable: diff --git a/tests/test_openeo_gfmap/test_unit_collection_fetcher.py b/tests/test_openeo_gfmap/test_unit_collection_fetcher.py index 77d964a..63f9034 100644 --- a/tests/test_openeo_gfmap/test_unit_collection_fetcher.py +++ b/tests/test_openeo_gfmap/test_unit_collection_fetcher.py @@ -124,3 +124,19 @@ def test_collection_fetcher_get_cube( # Assert that the result is an instance of xarray.DataArray assert isinstance(result, xr.DataArray) + + +def test_collection_fetcher_with_empty_bands( + mock_backend_context, mock_connection, mock_spatial_extent, mock_temporal_context +): + """Test that CollectionFetcher raises an error with empty bands.""" + bands = [] + fetcher = CollectionFetcher( + backend_context=mock_backend_context, + bands=bands, + collection_fetch=MagicMock(), # No need to mock fetch here + collection_preprocessing=MagicMock(), + ) + + # with pytest.raises(ValueError, match="Bands cannot be empty"): + fetcher.get_cube(mock_connection, mock_spatial_extent, mock_temporal_context) diff --git a/tests/test_openeo_gfmap/test_unit_s2_extractors.py b/tests/test_openeo_gfmap/test_unit_s2_extractors.py new file mode 100644 index 0000000..76012f0 --- /dev/null +++ b/tests/test_openeo_gfmap/test_unit_s2_extractors.py @@ -0,0 +1,123 @@ +from unittest.mock import MagicMock, patch + +import openeo +import pytest + +from openeo_gfmap import BoundingBoxExtent, TemporalContext +from openeo_gfmap.backend import Backend, BackendContext +from openeo_gfmap.fetching import ( + CollectionFetcher, + FetchType, + build_sentinel2_l2a_extractor, +) +from openeo_gfmap.fetching.s2 import ( + BASE_SENTINEL2_L2A_MAPPING, + _get_s2_l2a_default_fetcher, +) + +# Mock constants for the tests +BANDS = ["S2-L2A-B01", "S2-L2A-B02"] +COLLECTION_NAME = "SENTINEL2_L2A" +FETCH_TYPE = FetchType.TILE + + +@pytest.fixture +def mock_backend_context(): + """Fixture to create a mock backend context.""" + backend_context = MagicMock(spec=BackendContext) + backend_context.backend = Backend.CDSE + return backend_context + + +@pytest.fixture +def mock_connection(): + """Fixture to create a mock connection.""" + return MagicMock() + + +@pytest.fixture +def mock_spatial_extent(): + """Fixture for spatial extent.""" + return BoundingBoxExtent( + west=5.0515130512706845, + south=51.215806593713, + east=5.060320484557499, + north=51.22149744530769, + epsg=4326, + ) + + +@pytest.fixture +def mock_temporal_extent(): + """Fixture for temporal context.""" + return TemporalContext(start_date="2023-04-01", end_date="2023-05-01") + + +# test the fetcher +def test_get_s2_l2a_default_fetcher_returns_callable(): + """Test that the fetcher function is callable.""" + fetcher = _get_s2_l2a_default_fetcher(COLLECTION_NAME, FETCH_TYPE) + assert callable(fetcher) + + +@patch("openeo_gfmap.fetching.s2.convert_band_names") +@patch("openeo_gfmap.fetching.s2._load_collection") +def test_fetch_function_calls_convert_and_load( + mock_load_collection, + mock_convert_band_names, + mock_connection, + mock_spatial_extent, + mock_temporal_extent, +): + """Test that the fetch function calls convert_band_names and _load_collection.""" + fetcher = _get_s2_l2a_default_fetcher(COLLECTION_NAME, FETCH_TYPE) + + # Set up the mock return value for band conversion + mock_convert_band_names.return_value = BANDS + + # Call the fetch function + result = fetcher(mock_connection, mock_spatial_extent, mock_temporal_extent, BANDS) + + # Assert that convert_band_names was called with correct bands + mock_convert_band_names.assert_called_once_with(BANDS, BASE_SENTINEL2_L2A_MAPPING) + + # Assert that _load_collection was called with correct parameters + mock_load_collection.assert_called_once_with( + mock_connection, + BANDS, + COLLECTION_NAME, + mock_spatial_extent, + mock_temporal_extent, + FETCH_TYPE, + **{}, + ) + + +@patch("openeo_gfmap.fetching.s2._load_collection") +def test_fetch_function_returns_datacube( + mock_load_collection, mock_connection, mock_spatial_extent, mock_temporal_extent +): + """Test that the fetch function returns a DataCube.""" + fetcher = _get_s2_l2a_default_fetcher(COLLECTION_NAME, FETCH_TYPE) + + # Mock return value for _load_collection + mock_load_collection.return_value = MagicMock(spec=openeo.DataCube) + + result = fetcher(mock_connection, mock_spatial_extent, mock_temporal_extent, BANDS) + + # Assert that the result is an instance of DataCube + assert isinstance(result, openeo.DataCube) + + +# test the extractor + + +def test_build_sentinel2_l2a_extractor(mock_backend_context): + """Test that build_sentinel2_l2a_extractor returns a CollectionFetcher.""" + bands = ["S2-L2A-B01", "S2-L2A-B02"] + extractor = build_sentinel2_l2a_extractor( + backend_context=mock_backend_context, bands=bands, fetch_type=FetchType.TILE + ) + + assert isinstance(extractor, CollectionFetcher) + assert extractor.bands == bands From 5aa5bb2c6fdcb4ec76e84df16bd02d791c6209a1 Mon Sep 17 00:00:00 2001 From: Hans Vanrompay Date: Wed, 14 Aug 2024 16:20:32 +0200 Subject: [PATCH 3/4] added tests for the common functions used by the extractors --- src/openeo_gfmap/fetching/commons.py | 1 + tests/test_openeo_gfmap/test_commons.py | 226 ++++++++++++++++++ .../test_unit_s2_extractors.py | 103 +++++++- tests/test_openeo_gfmap/utils.py | 35 +++ 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 tests/test_openeo_gfmap/test_commons.py diff --git a/src/openeo_gfmap/fetching/commons.py b/src/openeo_gfmap/fetching/commons.py index 1ce6bb0..c9f249c 100644 --- a/src/openeo_gfmap/fetching/commons.py +++ b/src/openeo_gfmap/fetching/commons.py @@ -108,6 +108,7 @@ def _load_collection_hybrid( return cube +# TODO; deprecated? def _load_collection( connection: openeo.Connection, bands: list, diff --git a/tests/test_openeo_gfmap/test_commons.py b/tests/test_openeo_gfmap/test_commons.py new file mode 100644 index 0000000..71eab16 --- /dev/null +++ b/tests/test_openeo_gfmap/test_commons.py @@ -0,0 +1,226 @@ +from unittest.mock import MagicMock + +import openeo +import pytest + +from openeo_gfmap.fetching.commons import ( + convert_band_names, + rename_bands, + resample_reproject, +) +from openeo_gfmap.fetching.s2 import ( + BASE_SENTINEL2_L2A_MAPPING, + ELEMENT84_SENTINEL2_L2A_MAPPING, +) + +from .utils import create_test_datacube + +# band names + + +def test_convert_band_names_base_mapping(): + """Test conversion with BASE_SENTINEL2_L2A_MAPPING.""" + desired_bands = ["S2-L2A-B01", "S2-L2A-B03", "S2-L2A-B12"] + result = convert_band_names(desired_bands, BASE_SENTINEL2_L2A_MAPPING) + assert result == ["B01", "B03", "B12"] + + +def test_convert_band_names_element84_mapping(): + """Test conversion with ELEMENT84_SENTINEL2_L2A_MAPPING.""" + desired_bands = ["S2-L2A-B01", "S2-L2A-B08", "S2-L2A-B12"] + result = convert_band_names(desired_bands, ELEMENT84_SENTINEL2_L2A_MAPPING) + assert result == ["coastal", "nir", "swir22"] + + +def test_convert_band_names_mixed_case(): + """Test conversion with a mix of known and unknown bands in BASE_SENTINEL2_L2A_MAPPING.""" + desired_bands = ["S2-L2A-B01", "S2-L2A-B99"] # S2-L2A-B99 does not exist + with pytest.raises(KeyError): + convert_band_names(desired_bands, BASE_SENTINEL2_L2A_MAPPING) + + +def test_convert_band_names_empty_base_mapping(): + """Test conversion with an empty desired_bands list in BASE_SENTINEL2_L2A_MAPPING.""" + desired_bands = [] + result = convert_band_names(desired_bands, BASE_SENTINEL2_L2A_MAPPING) + assert result == [] + + +def test_convert_band_names_empty_element84_mapping(): + """Test conversion with an empty desired_bands list in ELEMENT84_SENTINEL2_L2A_MAPPING.""" + desired_bands = [] + result = convert_band_names(desired_bands, ELEMENT84_SENTINEL2_L2A_MAPPING) + assert result == [] + + +def test_convert_band_names_with_nonexistent_band_element84(): + """Test conversion where a band is not in ELEMENT84_SENTINEL2_L2A_MAPPING.""" + desired_bands = ["S2-L2A-B01", "S2-L2A-B99"] # S2-L2A-B99 does not exist + with pytest.raises(KeyError): + convert_band_names(desired_bands, ELEMENT84_SENTINEL2_L2A_MAPPING) + + +# resampling +def test_resample_reproject_valid_epsg(): + """Test resample_reproject with a valid EPSG code.""" + # Create a mock DataCube object + mock_datacube = MagicMock(spec=openeo.DataCube) + + # Mock the resample_spatial method to return a new mock DataCube + mock_resampled_datacube = MagicMock(spec=openeo.DataCube) + mock_datacube.resample_spatial.return_value = mock_resampled_datacube + + # Call the resample_reproject function + result = resample_reproject( + mock_datacube, resolution=10.0, epsg_code="4326", method="bilinear" + ) + + # Ensure resample_spatial was called correctly + mock_datacube.resample_spatial.assert_called_once_with( + resolution=10.0, projection="4326", method="bilinear" + ) + + +# invalid espg +def test_resample_reproject_invalid_epsg(): + """Test resample_reproject with an invalid EPSG code.""" + # Create a mock DataCube object + mock_datacube = MagicMock(spec=openeo.DataCube) + + # Attempt to call the resample_reproject function with an invalid EPSG code + with pytest.raises(ValueError, match="is not a valid EPSG code"): + resample_reproject( + mock_datacube, resolution=10.0, epsg_code="invalid_epsg", method="bilinear" + ) + + # Ensure resample_spatial was not called + mock_datacube.resample_spatial.assert_not_called() + + +# valid resolution +def test_resample_reproject_only_resolution(): + """Test resample_reproject with only resolution provided.""" + # Create a mock DataCube object + mock_datacube = MagicMock(spec=openeo.DataCube) + + # Mock the resample_spatial method to return a new mock DataCube + mock_resampled_datacube = MagicMock(spec=openeo.DataCube) + mock_datacube.resample_spatial.return_value = mock_resampled_datacube + + # Call the resample_reproject function with only resolution provided + result = resample_reproject(mock_datacube, resolution=20.0) + + # Ensure resample_spatial was called correctly with the resolution and default method + mock_datacube.resample_spatial.assert_called_once_with( + resolution=20.0, method="near" + ) + + +# default espg +def test_resample_reproject_no_epsg(): + """Test resample_reproject with no EPSG code provided.""" + # Create a mock DataCube object + mock_datacube = MagicMock(spec=openeo.DataCube) + + # Mock the resample_spatial method to return a new mock DataCube + mock_resampled_datacube = MagicMock(spec=openeo.DataCube) + mock_datacube.resample_spatial.return_value = mock_resampled_datacube + + # Call the resample_reproject function without specifying an EPSG code + result = resample_reproject( + mock_datacube, resolution=10.0, epsg_code=None, method="bilinear" + ) + + # Ensure resample_spatial was called correctly without the projection argument + mock_datacube.resample_spatial.assert_called_once_with( + resolution=10.0, method="bilinear" + ) + + +# default resampling +def test_resample_reproject_default_method(): + """Test resample_reproject with a valid EPSG code and default resampling method.""" + # Create a mock DataCube object + mock_datacube = MagicMock(spec=openeo.DataCube) + + # Mock the resample_spatial method to return a new mock DataCube + mock_resampled_datacube = MagicMock(spec=openeo.DataCube) + mock_datacube.resample_spatial.return_value = mock_resampled_datacube + + # Call the resample_reproject function with default method ("near") + result = resample_reproject(mock_datacube, resolution=10.0, epsg_code="4326") + + # Ensure resample_spatial was called correctly with the default method + mock_datacube.resample_spatial.assert_called_once_with( + resolution=10.0, projection="4326", method="near" + ) + + +def test_rename_bands_all_present(): + """Test rename_bands when all bands in the mapping are present in the datacube.""" + + datacube = create_test_datacube() + + mapping = {"B01": "coastal", "B02": "blue", "B03": "green"} + + # Call the rename_bands function + result = rename_bands(datacube, mapping) + + # Extract the band names from the result metadata + result_band_names = [band.name for band in result.metadata._dimensions[0].bands] + + # Check that only the available bands have been renamed + expected_renamed_bands = [ + "coastal", + "blue", + "green", + ] + datacube.metadata.band_names[ + 3: + ] # Assuming B04-B12 remain unchanged + + assert result_band_names == expected_renamed_bands + + +def test_rename_bands_some_missing(): + """Test rename_bands when some bands are not present in the datacube.""" + + # Use the fixture with specific bands + datacube = create_test_datacube(bands=["B01", "B02"]) # Only include B01 and B02 + + mapping = { + "B01": "coastal", + "B02": "blue", + "B03": "green", # B03 is not in the datacube + } + + # Call the rename_bands function + result = rename_bands(datacube, mapping) + + # Extract the band names from the result metadata + result_band_names = [band.name for band in result.metadata._dimensions[0].bands] + + # Check that only the available bands have been renamed + expected_renamed_bands = ["coastal", "blue"] + assert result_band_names == expected_renamed_bands + + +def test_rename_bands_no_bands_present(): + """Test rename_bands when no bands in the mapping are present in the datacube.""" + + # Use the fixture with specific bands + datacube = create_test_datacube(bands=["B04", "B05"]) # Only include B04 and B05 + + mapping = { + "B01": "coastal", + "B02": "blue", + "B03": "green", # None of these bands are present in the datacube + } + + # Call the rename_bands function + result = rename_bands(datacube, mapping) + + # Extract the band names from the result metadata + result_band_names = [band.name for band in result.metadata._dimensions[0].bands] + + # Check that only the available bands have been renamed + assert result_band_names == [] diff --git a/tests/test_openeo_gfmap/test_unit_s2_extractors.py b/tests/test_openeo_gfmap/test_unit_s2_extractors.py index 76012f0..35579f1 100644 --- a/tests/test_openeo_gfmap/test_unit_s2_extractors.py +++ b/tests/test_openeo_gfmap/test_unit_s2_extractors.py @@ -13,8 +13,11 @@ from openeo_gfmap.fetching.s2 import ( BASE_SENTINEL2_L2A_MAPPING, _get_s2_l2a_default_fetcher, + _get_s2_l2a_default_processor, ) +from .utils import create_test_datacube + # Mock constants for the tests BANDS = ["S2-L2A-B01", "S2-L2A-B02"] COLLECTION_NAME = "SENTINEL2_L2A" @@ -53,6 +56,12 @@ def mock_temporal_extent(): return TemporalContext(start_date="2023-04-01", end_date="2023-05-01") +@pytest.fixture +def mock_datacube(): + """Fixture to create a mock DataCube using the create_test_datacube function.""" + return create_test_datacube() + + # test the fetcher def test_get_s2_l2a_default_fetcher_returns_callable(): """Test that the fetcher function is callable.""" @@ -109,9 +118,101 @@ def test_fetch_function_returns_datacube( assert isinstance(result, openeo.DataCube) -# test the extractor +# test the processor output +def test_get_s2_l2a_default_processor_returns_callable(): + """Test that the processor function is callable.""" + processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) + assert callable(processor) + + +# test the processor behavior if resample and rename +@patch("openeo_gfmap.fetching.s2.resample_reproject") +@patch("openeo_gfmap.fetching.s2.rename_bands") +def test_processor_calls_resample_reproject_and_rename_bands( + mock_rename_bands, mock_resample_reproject, mock_datacube +): + """Test that the processor function calls resample_reproject and rename_bands.""" + processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) + + # Mock the return value of resample_reproject + mock_resample_reproject.return_value = mock_datacube + # Mock the return value of rename_bands + mock_rename_bands.return_value = mock_datacube + + # Call the processor function + result = processor(mock_datacube, target_resolution=10.0, target_crs="EPSG:4326") + + # Assert that resample_reproject was called with correct parameters + mock_resample_reproject.assert_called_once_with( + mock_datacube, 10.0, "EPSG:4326", method="near" + ) + + # Assert that rename_bands was called with the correct cube and mapping + mock_rename_bands.assert_called_once_with(mock_datacube, BASE_SENTINEL2_L2A_MAPPING) + + # Assert that the result is a DataCube + assert isinstance(result, openeo.DataCube) + + +# test error in case only target crs is given and not target resolution +@patch("openeo_gfmap.fetching.s2.resample_reproject") +def test_processor_raises_valueerror_for_missing_resolution( + mock_resample_reproject, mock_datacube +): + """Test that the processor raises a ValueError if target_crs is specified without target_resolution.""" + processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) + # Prepare the parameters with target_crs set and target_resolution missing (i.e., set to None) + params = {"target_crs": "4326", "target_resolution": None} + + # Expect ValueError when target_crs is specified without target_resolution + with pytest.raises( + ValueError, + match="In fetching parameters: `target_crs` specified but not `target_resolution`, which is required to perform reprojection.", + ): + processor(mock_datacube, **params) + + +# test target resolution explicit None +@patch("openeo_gfmap.fetching.s2.rename_bands") +def test_processor_skips_reprojection_if_disabled(mock_rename_bands, mock_datacube): + """Test that the processor skips reprojection if target_resolution is None.""" + processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) + + # Mock the return value of rename_bands + mock_rename_bands.return_value = mock_datacube + + # Call the processor function with target_resolution=None + result = processor(mock_datacube, target_resolution=None, target_crs=None) + + # Assert that resample_reproject was not called + mock_rename_bands.assert_called_once() + + # Assert that the result is a DataCube + assert isinstance(result, openeo.DataCube) + + +# test wheter rescaling is used +@patch("openeo_gfmap.fetching.s2.rename_bands") +def test_processor_changes_datatype_to_uint16(mock_rename_bands, mock_datacube): + """Test that the processor changes the datatype to uint16.""" + processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) + + # Mock the return value of rename_bands + mock_rename_bands.return_value = mock_datacube + + # Mock the linear_scale_range method to simulate the datatype conversion + mock_datacube.linear_scale_range = MagicMock(return_value=mock_datacube) + + # Call the processor function + result_cube = processor(mock_datacube) + + # Check that linear_scale_range was called with the correct parameters + mock_datacube.linear_scale_range.assert_called_once_with(0, 65534, 0, 65534) + + +# test the extractor def test_build_sentinel2_l2a_extractor(mock_backend_context): """Test that build_sentinel2_l2a_extractor returns a CollectionFetcher.""" bands = ["S2-L2A-B01", "S2-L2A-B02"] diff --git a/tests/test_openeo_gfmap/utils.py b/tests/test_openeo_gfmap/utils.py index 0d21b69..d3c52f1 100644 --- a/tests/test_openeo_gfmap/utils.py +++ b/tests/test_openeo_gfmap/utils.py @@ -1,9 +1,44 @@ """Utilitiaries used in tests, such as download test resources.""" from tempfile import NamedTemporaryFile +from unittest.mock import MagicMock +import numpy as np +import openeo import requests import xarray as xr +from openeo.metadata import Band, BandDimension, CollectionMetadata + +from openeo_gfmap.fetching.s2 import BASE_SENTINEL2_L2A_MAPPING + + +def create_test_datacube(bands=None): + """Create a test DataCube with predefined bands or specified bands.""" + + if bands is None: + bands = list(BASE_SENTINEL2_L2A_MAPPING.keys()) + + # Create a simple xarray DataArray with the given bands, all set to 1 + data = np.ones((100, 100, len(bands))) # 100x100 grid with 'len(bands)' bands + coords = {"x": np.arange(100), "y": np.arange(100), "bands": bands} + dataarray = xr.DataArray(data, coords=coords, dims=["x", "y", "bands"]) + + # Create a mock connection + connection = MagicMock(spec=openeo.Connection) + + # Create new metadata to reflect the current bands + band_objects = [Band(name=band_name) for band_name in bands] + band_dimension = BandDimension(name="bands", bands=band_objects) + metadata = CollectionMetadata( + metadata={"id": "sentinel2_l2a", "title": "Sentinel-2 L2A"}, + dimensions=[band_dimension], + ) + + # Wrap this DataArray into an OpenEO DataCube + cube = openeo.DataCube(graph=None, connection=connection, metadata=metadata) + cube.dataarray = dataarray # Add the dataarray to the cube + + return cube def load_dataset_url(url: str) -> NamedTemporaryFile: From 849bc55ec558d7e3e199c3d8685737c50e7c2910 Mon Sep 17 00:00:00 2001 From: Hans Vanrompay Date: Wed, 14 Aug 2024 16:28:19 +0200 Subject: [PATCH 4/4] linter fix --- tests/test_openeo_gfmap/test_commons.py | 8 ++++---- tests/test_openeo_gfmap/test_unit_s2_extractors.py | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_openeo_gfmap/test_commons.py b/tests/test_openeo_gfmap/test_commons.py index 71eab16..5e58023 100644 --- a/tests/test_openeo_gfmap/test_commons.py +++ b/tests/test_openeo_gfmap/test_commons.py @@ -71,7 +71,7 @@ def test_resample_reproject_valid_epsg(): mock_datacube.resample_spatial.return_value = mock_resampled_datacube # Call the resample_reproject function - result = resample_reproject( + resample_reproject( mock_datacube, resolution=10.0, epsg_code="4326", method="bilinear" ) @@ -108,7 +108,7 @@ def test_resample_reproject_only_resolution(): mock_datacube.resample_spatial.return_value = mock_resampled_datacube # Call the resample_reproject function with only resolution provided - result = resample_reproject(mock_datacube, resolution=20.0) + resample_reproject(mock_datacube, resolution=20.0) # Ensure resample_spatial was called correctly with the resolution and default method mock_datacube.resample_spatial.assert_called_once_with( @@ -127,7 +127,7 @@ def test_resample_reproject_no_epsg(): mock_datacube.resample_spatial.return_value = mock_resampled_datacube # Call the resample_reproject function without specifying an EPSG code - result = resample_reproject( + resample_reproject( mock_datacube, resolution=10.0, epsg_code=None, method="bilinear" ) @@ -148,7 +148,7 @@ def test_resample_reproject_default_method(): mock_datacube.resample_spatial.return_value = mock_resampled_datacube # Call the resample_reproject function with default method ("near") - result = resample_reproject(mock_datacube, resolution=10.0, epsg_code="4326") + resample_reproject(mock_datacube, resolution=10.0, epsg_code="4326") # Ensure resample_spatial was called correctly with the default method mock_datacube.resample_spatial.assert_called_once_with( diff --git a/tests/test_openeo_gfmap/test_unit_s2_extractors.py b/tests/test_openeo_gfmap/test_unit_s2_extractors.py index 35579f1..ba3e920 100644 --- a/tests/test_openeo_gfmap/test_unit_s2_extractors.py +++ b/tests/test_openeo_gfmap/test_unit_s2_extractors.py @@ -85,7 +85,7 @@ def test_fetch_function_calls_convert_and_load( mock_convert_band_names.return_value = BANDS # Call the fetch function - result = fetcher(mock_connection, mock_spatial_extent, mock_temporal_extent, BANDS) + fetcher(mock_connection, mock_spatial_extent, mock_temporal_extent, BANDS) # Assert that convert_band_names was called with correct bands mock_convert_band_names.assert_called_once_with(BANDS, BASE_SENTINEL2_L2A_MAPPING) @@ -156,10 +156,7 @@ def test_processor_calls_resample_reproject_and_rename_bands( # test error in case only target crs is given and not target resolution -@patch("openeo_gfmap.fetching.s2.resample_reproject") -def test_processor_raises_valueerror_for_missing_resolution( - mock_resample_reproject, mock_datacube -): +def test_processor_raises_valueerror_for_missing_resolution(mock_datacube): """Test that the processor raises a ValueError if target_crs is specified without target_resolution.""" processor = _get_s2_l2a_default_processor(COLLECTION_NAME, FETCH_TYPE) @@ -210,6 +207,7 @@ def test_processor_changes_datatype_to_uint16(mock_rename_bands, mock_datacube): # Check that linear_scale_range was called with the correct parameters mock_datacube.linear_scale_range.assert_called_once_with(0, 65534, 0, 65534) + assert isinstance(result_cube, openeo.DataCube) # test the extractor