fix(platform): govern geospatial analysis and raster handoffs

This commit is contained in:
Jens
2026-08-30 06:00:15 +02:00
parent 96f90373dc
commit 80a2d1654d
63 changed files with 2335 additions and 312 deletions
+101 -2
View File
@@ -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)