Initial public release
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
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
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user