from __future__ import annotations from pathlib import Path 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")