Skip to content

Commit

Permalink
GTC-2171 - Test Raster Tile Cache Assets Function (#361)
Browse files Browse the repository at this point in the history
* ✅ test(RasterTileCacheAsset): Add collaborator tests
This commit reflects the effort to assert how `raster_tile_cache_asset`'s collaborators are called. The goal is to use these tests to provide feedback during a refactor of this function. While there was code coverage around the function, there weren't any `assert`s for behavior around its dependencies. Now, we have the feedback for more in-depth structural change.

* ✅ test: Use `call_args_list` with mocks
I definitely think this is a better form than what I was doing previously with `ANY`. The diffs are MUCH nicer.
https://stackoverflow.com/questions/21611559/assert-that-a-method-was-called-with-one-argument-out-of-several/56765027#56765027

* ✅ test: Add RasterTileSetSourceCreationOptions tests
Found a nice way to leverage pytest diffing when asserting a subset of a dict.
pytest-dev/pytest#2376 (comment)

* 🎨 refactor: Move `patch` decorator to class declaration
This removes lots of boilerplate code!
From the python docs:
Patch can be used as a TestCase class decorator. It works by decorating each test method in the class. This reduces the boilerplate code when your test methods share a common patchings set. patch() finds tests by looking for method names that start with patch.TEST_PREFIX. By default this is 'test', which matches the way unittest finds tests. You can specify an alternative prefix by setting patch.TEST_PREFIX.
  • Loading branch information
gtempus committed Dec 22, 2022
1 parent 3425ec2 commit 3ee7591
Show file tree
Hide file tree
Showing 9 changed files with 1,262 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MODULE_PATH_UNDER_TEST = "app.tasks.raster_tile_cache_assets.raster_tile_cache_assets"
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from datetime import datetime
from unittest.mock import AsyncMock
from uuid import UUID

import pytest

from app.models.orm.assets import Asset as ORMAsset
from app.models.pydantic.change_log import ChangeLog
from app.models.pydantic.creation_options import RasterTileSetSourceCreationOptions
from app.models.pydantic.jobs import Job
from app.models.pydantic.symbology import Symbology
from app.tasks.raster_tile_cache_assets.symbology import (
SymbologyFuncType,
SymbologyInfo,
)


@pytest.fixture(scope="module")
def tile_cache_asset_uuid():
return UUID("e0a2dc44-aee1-4f71-963b-70a85869685d")


@pytest.fixture(scope="module")
def source_asset_uuid():
return UUID("a8230040-23ad-4def-aca3-a292b6161557")


@pytest.fixture()
def creation_options_dict(source_asset_uuid):
return {
"creation_options": {
"min_zoom": 0,
"max_zoom": 0, # make sure to test this at least with 1
"max_static_zoom": 0,
"implementation": "default",
"symbology": {
"type": "date_conf_intensity", # try no_symbology
},
"resampling": "nearest",
"source_asset_id": source_asset_uuid,
}
}


@pytest.fixture()
def max_zoom_and_min_zoom_different_creation_options_dict(source_asset_uuid):
return {
"creation_options": {
"min_zoom": 0,
"max_zoom": 1,
"max_static_zoom": 0,
"implementation": "default",
"symbology": {
"type": "date_conf_intensity", # try no_symbology
},
"resampling": "nearest",
"source_asset_id": source_asset_uuid,
}
}


@pytest.fixture()
def source_asset():
return ORMAsset(
creation_options={
"pixel_meaning": "test_pixels",
"data_type": "boolean",
"grid": "1/4000",
}
)


@pytest.fixture()
def raster_tile_set_source_creation_options():
return RasterTileSetSourceCreationOptions(
pixel_meaning="test_pixels",
data_type="boolean",
grid="1/4000",
source_type="raster",
source_driver="GeoTIFF",
symbology=Symbology(type="date_conf_intensity"),
)


@pytest.fixture()
def tile_cache_job():
return Job(
dataset="test_dataset",
job_name="tile_cache_job",
job_queue="tile_cache_job_queue",
job_definition="tile cache job",
command=["doit"],
vcpus=1,
memory=64,
attempts=1,
attempt_duration_seconds=1,
)


@pytest.fixture()
def symbology_job():
return Job(
dataset="test_dataset",
job_name="symbology_job",
job_queue="symbology_job_queue",
job_definition="symbology job",
command=["doit"],
vcpus=1,
memory=64,
attempts=1,
attempt_duration_seconds=1,
)


@pytest.fixture()
def job():
return Job(
dataset="test_dataset",
job_name="reprojection_job",
job_queue="reprojection_job_queue",
job_definition="source reprojection job",
command=["doit"],
vcpus=1,
memory=64,
attempts=1,
attempt_duration_seconds=1,
)


@pytest.fixture()
def reprojection(job):
return (
job,
"https://some/asset/uri",
)


@pytest.fixture()
def symbology_info(symbology_job):
return SymbologyInfo(
8,
1,
AsyncMock(
spec_set=SymbologyFuncType,
return_value=([symbology_job], "/dummy/symbology/uri"),
),
)


@pytest.fixture()
def change_log():
return ChangeLog(
date_time=datetime(2022, 12, 6), status="success", message="All done!"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
from unittest.mock import patch

import pytest

from app.models.enum.creation_options import RasterDrivers
from app.models.enum.sources import RasterSourceType
from app.models.pydantic.symbology import Symbology
from app.tasks.raster_tile_cache_assets import raster_tile_cache_asset

from . import MODULE_PATH_UNDER_TEST


def assert_contains_subset(kwargs, expected_subset):
"""Simple assertion helper function for asserting fewer keys than what's in
the entire dict."""
__tracebackhide__ = True
assert expected_subset == {k: v for k, v in kwargs.items() if k in expected_subset}


@patch(f"{MODULE_PATH_UNDER_TEST}.execute", autospec=True)
@patch(f"{MODULE_PATH_UNDER_TEST}.symbology_constructor", autospec=True)
@patch(f"{MODULE_PATH_UNDER_TEST}.reproject_to_web_mercator", autospec=True)
@patch(f"{MODULE_PATH_UNDER_TEST}.RasterTileSetSourceCreationOptions", autospec=True)
@patch(f"{MODULE_PATH_UNDER_TEST}.get_asset", autospec=True)
class TestBuildingRasterTileSetSourceCreationOptionsFromSourceAsset:
@pytest.mark.asyncio
async def test_overrides_source_information(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {
"source_type": RasterSourceType.raster,
"source_driver": RasterDrivers.geotiff,
"source_uri": [
"s3://gfw-data-lake-test/test_dataset/2022/raster/epsg-4326/1/4000/test_pixels/geotiff/tiles.geojson"
],
}
assert_contains_subset(kwargs, expected)

@pytest.mark.asyncio
async def test_sets_calc_to_none(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {"calc": None}
assert_contains_subset(kwargs, expected)

@pytest.mark.asyncio
async def test_sets_resampling_method_from_input_params(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {
"resampling": creation_options_dict["creation_options"]["resampling"]
}
assert_contains_subset(kwargs, expected)

@pytest.mark.asyncio
async def test_sets_compute_information_to_false(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {
"compute_stats": False,
"compute_histogram": False,
}
assert_contains_subset(kwargs, expected)

@pytest.mark.asyncio
async def test_sets_symbology_from_input_data(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {
"symbology": Symbology(
**creation_options_dict["creation_options"]["symbology"]
),
}
assert_contains_subset(kwargs, expected)

@pytest.mark.asyncio
async def test_sets_subset_to_none(
self,
get_asset_dummy,
raster_tile_set_source_creation_options_mock,
web_mercator_dummy,
symbology_constructor_dummy,
execute_dummy,
tile_cache_asset_uuid,
creation_options_dict,
source_asset,
raster_tile_set_source_creation_options,
reprojection,
symbology_info,
change_log,
):
get_asset_dummy.return_value = source_asset
raster_tile_set_source_creation_options_mock.return_value = (
raster_tile_set_source_creation_options
)
symbology_constructor_dummy.__getitem__.return_value = symbology_info
web_mercator_dummy.return_value = reprojection
execute_dummy.return_value = change_log

await raster_tile_cache_asset(
"test_dataset", "2022", tile_cache_asset_uuid, creation_options_dict
)

_, kwargs = raster_tile_set_source_creation_options_mock.call_args_list[-1]
expected = {
"subset": None,
}
assert_contains_subset(kwargs, expected)
Loading

0 comments on commit 3ee7591

Please sign in to comment.