GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
"""A precise failure must not be reported as a generic one.
|
|
|
|
``get_dataset_geojson`` wrapped the JSON parse, the metadata read, the CRS
|
|
resolution and the canonicalisation in one ``try``, and reported everything as
|
|
"Stored dataset is not valid JSON" with a 500. An operator whose dataset has an
|
|
unusable CRS was sent to inspect a file that parses perfectly well, and the
|
|
specific AppError the canonicaliser raised — with its own code and status —
|
|
never reached them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.core.errors import AppError
|
|
from app.models import Dataset
|
|
from app.services.dataset_service import DatasetService
|
|
from app.services.vector_feature_service import VectorFeatureService
|
|
|
|
|
|
class FakeSession:
|
|
def __init__(self, dataset: Dataset) -> None:
|
|
self.dataset = dataset
|
|
|
|
def get(self, _model, item_id):
|
|
return self.dataset if item_id == self.dataset.id else None
|
|
|
|
|
|
def _dataset(tmp_path: Path, payload: str) -> Dataset:
|
|
path = tmp_path / "features.geojson"
|
|
path.write_text(payload, encoding="utf-8")
|
|
return Dataset(
|
|
id=uuid4(),
|
|
project_id=uuid4(),
|
|
name="features.geojson",
|
|
dataset_type="geojson",
|
|
source="test",
|
|
status="ready",
|
|
storage_path=str(path),
|
|
crs="EPSG:4326",
|
|
)
|
|
|
|
|
|
def test_a_valid_dataset_is_returned(tmp_path: Path) -> None:
|
|
dataset = _dataset(
|
|
tmp_path,
|
|
json.dumps(
|
|
{
|
|
"type": "FeatureCollection",
|
|
"features": [
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {"type": "Point", "coordinates": [5.0, 51.0]},
|
|
"properties": {},
|
|
}
|
|
],
|
|
}
|
|
),
|
|
)
|
|
|
|
result = DatasetService.get_dataset_geojson(FakeSession(dataset), dataset.id)
|
|
|
|
assert result["type"] == "FeatureCollection"
|
|
|
|
|
|
def test_unparseable_bytes_are_reported_as_invalid_json(tmp_path: Path) -> None:
|
|
dataset = _dataset(tmp_path, "{ not json")
|
|
|
|
with pytest.raises(AppError) as exc_info:
|
|
DatasetService.get_dataset_geojson(FakeSession(dataset), dataset.id)
|
|
|
|
assert exc_info.value.code == "INVALID_GEOJSON"
|
|
assert "JSON" in exc_info.value.message
|
|
|
|
|
|
def test_a_parseable_file_that_is_not_a_feature_collection_says_so(tmp_path: Path) -> None:
|
|
"""Valid JSON, wrong shape. Telling the operator it is not JSON sends them
|
|
to inspect a file that parses perfectly well."""
|
|
|
|
dataset = _dataset(tmp_path, json.dumps({"type": "Feature", "geometry": None}))
|
|
|
|
with pytest.raises(AppError) as exc_info:
|
|
DatasetService.get_dataset_geojson(FakeSession(dataset), dataset.id)
|
|
|
|
assert exc_info.value.code == "INVALID_GEOJSON"
|
|
assert "FeatureCollection" in exc_info.value.message
|
|
# The canonicaliser's own status survives; it is a bad request, not a
|
|
# server fault.
|
|
assert exc_info.value.status_code == 400
|
|
|
|
|
|
def test_a_canonicalisation_failure_keeps_its_own_code(tmp_path: Path, monkeypatch) -> None:
|
|
dataset = _dataset(tmp_path, json.dumps({"type": "FeatureCollection", "features": []}))
|
|
|
|
def failing(*_args, **_kwargs):
|
|
raise AppError(code="INVALID_CRS", message="Unusable source CRS", status_code=409)
|
|
|
|
monkeypatch.setattr(VectorFeatureService, "canonicalize_geojson_payload", staticmethod(failing))
|
|
|
|
with pytest.raises(AppError) as exc_info:
|
|
DatasetService.get_dataset_geojson(FakeSession(dataset), dataset.id)
|
|
|
|
assert exc_info.value.code == "INVALID_CRS"
|
|
assert exc_info.value.status_code == 409
|
|
|
|
|
|
def test_an_unexpected_failure_still_becomes_a_server_error(tmp_path: Path, monkeypatch) -> None:
|
|
"""A genuine bug must not masquerade as a client mistake."""
|
|
|
|
dataset = _dataset(tmp_path, json.dumps({"type": "FeatureCollection", "features": []}))
|
|
|
|
def exploding(*_args, **_kwargs):
|
|
raise RuntimeError("pyproj blew up")
|
|
|
|
monkeypatch.setattr(VectorFeatureService, "canonicalize_geojson_payload", staticmethod(exploding))
|
|
|
|
with pytest.raises(AppError) as exc_info:
|
|
DatasetService.get_dataset_geojson(FakeSession(dataset), dataset.id)
|
|
|
|
assert exc_info.value.status_code == 500
|
|
assert exc_info.value.code == "DATASET_GEOJSON_UNREADABLE"
|