fix(platform): govern geospatial analysis and raster handoffs
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
from hashlib import sha256
|
||||
|
||||
from geoalchemy2.shape import from_shape
|
||||
from app.core.errors import AppError
|
||||
from app.models import Area, Dataset, DatasetVersion
|
||||
from app.services.raster_operations_service import RasterOperationsService
|
||||
from app.services.storage_service import StorageService
|
||||
from app.api.routes.datasets import _run_job_sync
|
||||
from shapely.geometry import box
|
||||
import pytest
|
||||
@@ -688,6 +690,104 @@ def test_raster_tile_returns_manifest_payload(monkeypatch, tmp_path) -> None:
|
||||
assert payload["manifest"]["tile_server"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("dimension", "expected"),
|
||||
[
|
||||
(512, [0]),
|
||||
(513, [0, 1]),
|
||||
(960, [0, 448]),
|
||||
(961, [0, 448, 449]),
|
||||
],
|
||||
)
|
||||
def test_raster_tile_offsets_use_full_tiles_and_one_unique_edge_start(dimension, expected) -> None:
|
||||
assert RasterOperationsService._tile_offsets(dimension, tile_size=512, step=448) == expected
|
||||
|
||||
|
||||
def test_raster_tile_rejects_limit_before_creating_output(monkeypatch, tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
source = tmp_path / "large-raster.tif"
|
||||
source.write_bytes(b"source")
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
project_id=project_id,
|
||||
name="large-raster.tif",
|
||||
dataset_type="raster",
|
||||
source="user_upload",
|
||||
storage_path=str(source),
|
||||
original_filename="large-raster.tif",
|
||||
stored_filename="large-raster.tif",
|
||||
content_type="image/tiff",
|
||||
size_bytes=6,
|
||||
)
|
||||
db = FakeSession([dataset])
|
||||
|
||||
class FakeSource:
|
||||
width = 2048
|
||||
height = 2048
|
||||
count = 1
|
||||
crs = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
return None
|
||||
|
||||
fake_rasterio = SimpleNamespace(open=lambda _path: FakeSource())
|
||||
monkeypatch.setattr(
|
||||
"app.services.raster_operations_service._import_rasterio",
|
||||
lambda: (fake_rasterio, SimpleNamespace()),
|
||||
)
|
||||
tile_root = tmp_path / "tiles-that-must-not-exist"
|
||||
monkeypatch.setattr(
|
||||
StorageService,
|
||||
"raster_tiles_root",
|
||||
staticmethod(lambda *_args: tile_root),
|
||||
)
|
||||
|
||||
with pytest.raises(AppError) as error:
|
||||
RasterOperationsService.tile(db, dataset_id, tile_size=512, overlap=64, max_tiles=1)
|
||||
|
||||
assert error.value.code == "RASTER_TILE_LIMIT_EXCEEDED"
|
||||
assert error.value.details == {"expected_tile_count": 25, "max_tiles": 1}
|
||||
assert not tile_root.exists()
|
||||
|
||||
|
||||
def test_raster_tile_rejects_changed_source_bytes_before_creating_output(monkeypatch, tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
source = tmp_path / "changed-raster.tif"
|
||||
source.write_bytes(b"changed")
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
project_id=project_id,
|
||||
name="changed-raster.tif",
|
||||
dataset_type="raster",
|
||||
source="user_upload",
|
||||
storage_path=str(source),
|
||||
original_filename="changed-raster.tif",
|
||||
stored_filename="changed-raster.tif",
|
||||
content_type="image/tiff",
|
||||
size_bytes=7,
|
||||
checksum_sha256=sha256(b"original").hexdigest(),
|
||||
data_contract_key="raster.generic",
|
||||
)
|
||||
db = FakeSession([dataset])
|
||||
tile_root = tmp_path / "tiles-that-must-not-exist"
|
||||
monkeypatch.setattr(
|
||||
StorageService,
|
||||
"raster_tiles_root",
|
||||
staticmethod(lambda *_args: tile_root),
|
||||
)
|
||||
|
||||
with pytest.raises(AppError) as error:
|
||||
RasterOperationsService.tile(db, dataset_id)
|
||||
|
||||
assert error.value.code == "DATASET_STORAGE_CHECKSUM_MISMATCH"
|
||||
assert not tile_root.exists()
|
||||
|
||||
|
||||
def test_raster_clip_persists_derived_dataset(monkeypatch, tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
@@ -1393,4 +1493,3 @@ def test_run_job_sync_serializes_index_job_output_dataset_id(monkeypatch) -> Non
|
||||
assert result["job_type"] == "raster.ndvi"
|
||||
assert result["output_dataset_id"] == str(output_dataset_id)
|
||||
assert result["result_json"]["output_dataset_id"] == str(output_dataset_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user