Constrain selections to active work areas
This commit is contained in:
@@ -6,7 +6,10 @@ from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from geoalchemy2.shape import from_shape, to_shape
|
||||
from shapely.geometry import box
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.main import app
|
||||
from app.models import Area, Dataset, Export
|
||||
from app.schemas.export import ExportCreateResponse
|
||||
@@ -140,7 +143,8 @@ def test_vector_selection_export_uses_exact_area_scope_when_requested(tmp_path,
|
||||
source="fixture",
|
||||
status="ready",
|
||||
)
|
||||
area_geometry = object()
|
||||
area_shape = box(5.0, 51.1, 5.2, 51.3)
|
||||
area_geometry = from_shape(area_shape, srid=4326)
|
||||
area = SimpleNamespace(id=area_id, project_id=project_id, name="Gemeente Mol", geometry=area_geometry)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset, (Area, area_id): area})
|
||||
selection_bbox = {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"}
|
||||
@@ -170,12 +174,78 @@ def test_vector_selection_export_uses_exact_area_scope_when_requested(tmp_path,
|
||||
area_id=area_id,
|
||||
)
|
||||
|
||||
assert captured["selection_geometry"] is area_geometry
|
||||
assert to_shape(captured["selection_geometry"]).equals(area_shape)
|
||||
assert captured["selection_area_id"] == area_id
|
||||
assert captured["full_dataset_area"] is True
|
||||
assert response.metadata_json["selection_area_id"] == str(area_id)
|
||||
|
||||
|
||||
def test_area_constrained_bbox_uses_intersection_and_disables_full_area_fast_path(tmp_path, monkeypatch) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
area_id = uuid4()
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
project_id=project_id,
|
||||
area_id=area_id,
|
||||
name="mol-buildings.geojson",
|
||||
dataset_type="vector",
|
||||
source="fixture",
|
||||
source_metadata={"geometry_clipped_to_area": True},
|
||||
status="ready",
|
||||
)
|
||||
area_shape = box(5.0, 51.0, 5.2, 51.2)
|
||||
area = SimpleNamespace(
|
||||
id=area_id,
|
||||
project_id=project_id,
|
||||
name="Gemeente Mol - officiële grens",
|
||||
geometry=from_shape(area_shape, srid=4326),
|
||||
)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset, (Area, area_id): area})
|
||||
crossing_bbox = {"min_x": 4.9, "min_y": 51.1, "max_x": 5.1, "max_y": 51.3, "crs": "EPSG:4326"}
|
||||
captured: dict = {}
|
||||
selection_payload = {
|
||||
"selection_bbox": crossing_bbox,
|
||||
"selection_area_id": str(area_id),
|
||||
"feature_count": 0,
|
||||
"limit": 250,
|
||||
"truncated": False,
|
||||
"geojson": {"type": "FeatureCollection", "features": []},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(StorageService, "dataset_export_path", lambda *_args: str(tmp_path / "selection.geojson"))
|
||||
|
||||
def fake_select(*_args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return selection_payload
|
||||
|
||||
monkeypatch.setattr(VectorFeatureService, "select_features_by_bbox", fake_select)
|
||||
|
||||
ExportService.export_vector_selection_geojson(
|
||||
db,
|
||||
dataset_id,
|
||||
crossing_bbox,
|
||||
area_id=area_id,
|
||||
)
|
||||
|
||||
assert to_shape(captured["selection_geometry"]).equals(box(5.0, 51.1, 5.1, 51.2))
|
||||
assert captured["selection_area_id"] == area_id
|
||||
assert captured["full_dataset_area"] is False
|
||||
|
||||
|
||||
def test_area_constrained_bbox_rejects_selection_outside_work_area() -> None:
|
||||
area_geometry = from_shape(box(5.0, 51.0, 5.2, 51.2), srid=4326)
|
||||
outside_bbox = {"min_x": 4.0, "min_y": 50.0, "max_x": 4.1, "max_y": 50.1, "crs": "EPSG:4326"}
|
||||
|
||||
try:
|
||||
VectorFeatureService.constrain_bbox_to_area(outside_bbox, area_geometry)
|
||||
except AppError as error:
|
||||
assert error.code == "VECTOR_SELECTION_OUTSIDE_AREA"
|
||||
assert error.status_code == 422
|
||||
else:
|
||||
raise AssertionError("Expected an outside-area selection to be rejected")
|
||||
|
||||
|
||||
def test_frontend_exposes_map_selection_export_action() -> None:
|
||||
types = (ROOT / "frontend" / "src" / "types.ts").read_text(encoding="utf-8")
|
||||
exports_api = (ROOT / "frontend" / "src" / "services" / "api" / "exports.ts").read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user