195 lines
6.5 KiB
Python
195 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from uuid import UUID, uuid4
|
|
|
|
from app.core.errors import AppError
|
|
from app.models import Dataset
|
|
from app.services.storage_service import StorageService
|
|
from app.services.vector_operations_service import VectorOperationsService
|
|
|
|
|
|
class FakeSession:
|
|
def __init__(self, datasets):
|
|
self.datasets = {dataset.id: dataset for dataset in datasets}
|
|
self.added = []
|
|
|
|
def get(self, model, item_id):
|
|
return self.datasets.get(item_id)
|
|
|
|
def add(self, value):
|
|
self.added.append(value)
|
|
|
|
def commit(self):
|
|
return None
|
|
|
|
def refresh(self, _value):
|
|
return None
|
|
|
|
|
|
def _make_vector_dataset(dataset_id: UUID, project_id: UUID, raw: str) -> Dataset:
|
|
path = Path(f"./tests/.tmp_{dataset_id}.geojson")
|
|
path.write_text(raw, encoding="utf-8")
|
|
return Dataset(
|
|
id=dataset_id,
|
|
project_id=project_id,
|
|
name=f"{dataset_id}.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(path),
|
|
original_filename=f"{dataset_id}.geojson",
|
|
stored_filename=f"{dataset_id}.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
|
|
|
|
def test_vector_inspect_extracts_feature_count_and_bbox(tmp_path) -> None:
|
|
dataset_id = uuid4()
|
|
project_id = uuid4()
|
|
source_path = tmp_path / f"{dataset_id}.geojson"
|
|
source_path.write_text(
|
|
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[4.1,51.2]}},{"type":"Feature","geometry":{"type":"Point","coordinates":[4.2,51.3]}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
dataset = Dataset(
|
|
id=dataset_id,
|
|
project_id=project_id,
|
|
name="vector.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(source_path),
|
|
original_filename="vector.geojson",
|
|
stored_filename="vector.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
db = FakeSession([dataset])
|
|
|
|
payload = VectorOperationsService.inspect(db, dataset_id)
|
|
|
|
assert payload.feature_count == 2
|
|
assert payload.geometry_type_summary["Point"] == 2
|
|
assert payload.bounds_json == {"min_x": 4.1, "min_y": 51.2, "max_x": 4.2, "max_y": 51.3}
|
|
|
|
|
|
def test_vector_bbox_and_stats_share_summary(tmp_path) -> None:
|
|
dataset_id = uuid4()
|
|
project_id = uuid4()
|
|
source_path = tmp_path / f"{dataset_id}.geojson"
|
|
source_path.write_text(
|
|
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[4.0,51.0]}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
dataset = Dataset(
|
|
id=dataset_id,
|
|
project_id=project_id,
|
|
name="vector.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(source_path),
|
|
original_filename="vector.geojson",
|
|
stored_filename="vector.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
db = FakeSession([dataset])
|
|
|
|
bbox = VectorOperationsService.bbox(db, dataset_id)
|
|
stats = VectorOperationsService.stats(db, dataset_id)
|
|
|
|
assert bbox["feature_count"] == 1
|
|
assert stats["feature_count"] == 1
|
|
assert stats["geometry_type_summary"]["Point"] == 1
|
|
|
|
|
|
def test_vector_intersect_creates_derived_dataset(monkeypatch, tmp_path) -> None:
|
|
source_id = uuid4()
|
|
target_id = uuid4()
|
|
project_id = uuid4()
|
|
source_path = tmp_path / f"{source_id}.geojson"
|
|
target_path = tmp_path / f"{target_id}.geojson"
|
|
source_path.write_text(
|
|
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[4.1,51.2]}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
target_path.write_text(
|
|
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[4.0,51.0],[4.0,52.0],[5.0,52.0],[5.0,51.0],[4.0,51.0]]]}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
source = Dataset(
|
|
id=source_id,
|
|
project_id=project_id,
|
|
name="source.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(source_path),
|
|
original_filename="source.geojson",
|
|
stored_filename="source.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
target = Dataset(
|
|
id=target_id,
|
|
project_id=project_id,
|
|
name="target.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(target_path),
|
|
original_filename="target.geojson",
|
|
stored_filename="target.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
db = FakeSession([source, target])
|
|
|
|
persisted = {}
|
|
|
|
def _persist_dataset_file(project_id: str, dataset_id: str, dataset_type: str, original_filename: str, content: bytes, content_type: str | None):
|
|
persisted["project_id"] = project_id
|
|
persisted["dataset_id"] = dataset_id
|
|
output = tmp_path / f"{dataset_id}_{dataset_type}.geojson"
|
|
output.write_bytes(content)
|
|
return {
|
|
"original_filename": original_filename,
|
|
"stored_filename": output.name,
|
|
"content_type": content_type or "application/geo+json",
|
|
"size_bytes": len(content),
|
|
"checksum_sha256": "test",
|
|
"storage_path": str(output),
|
|
}
|
|
|
|
monkeypatch.setattr(StorageService, "persist_dataset_file", _persist_dataset_file)
|
|
|
|
derived_id = VectorOperationsService.intersect(db, source_id, target_id, "intersect_output")
|
|
assert derived_id is not None
|
|
assert isinstance(derived_id, UUID)
|
|
assert persisted["project_id"] == str(project_id)
|
|
|
|
|
|
def test_vector_operations_reject_invalid_geometry(tmp_path) -> None:
|
|
dataset_id = uuid4()
|
|
project_id = uuid4()
|
|
source_path = tmp_path / f"{dataset_id}.geojson"
|
|
source_path.write_text(
|
|
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":"invalid"}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
dataset = Dataset(
|
|
id=dataset_id,
|
|
project_id=project_id,
|
|
name="invalid.geojson",
|
|
dataset_type="vector",
|
|
source="test",
|
|
storage_path=str(source_path),
|
|
original_filename="invalid.geojson",
|
|
stored_filename="invalid.geojson",
|
|
content_type="application/geo+json",
|
|
)
|
|
db = FakeSession([dataset])
|
|
|
|
try:
|
|
VectorOperationsService.inspect(db, dataset_id)
|
|
except AppError as exc:
|
|
assert exc.code == "INVALID_GEOMETRY"
|
|
else:
|
|
raise AssertionError("Invalid geometry should raise INVALID_GEOMETRY")
|