feat(provenance): govern source snapshots and data inputs
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -10,12 +11,13 @@ from fastapi.testclient import TestClient
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.main import app
|
||||
from app.models import Dataset, Export
|
||||
from app.models import Dataset, Export, SourceRegistry, SourceSnapshot
|
||||
from app.schemas.export import ExportCreateResponse, MapResultExportRequest
|
||||
from app.schemas.project import ProjectRead
|
||||
from app.services.export_service import ExportService
|
||||
from app.services.project_service import ProjectService
|
||||
from app.services.storage_service import StorageService
|
||||
from app.services.source_registry_service import SourceRegistryService
|
||||
from app.services.temporal_analysis_service import TemporalAnalysisService
|
||||
from app.services.thematic_raster_analysis_service import ThematicRasterAnalysisService
|
||||
|
||||
@@ -48,6 +50,71 @@ def bbox_payload() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def governed_dataset(
|
||||
*,
|
||||
project_id,
|
||||
dataset_id,
|
||||
name: str,
|
||||
dataset_type: str,
|
||||
source_key: str,
|
||||
dataset_role: str = "source",
|
||||
) -> Dataset:
|
||||
"""Build an in-memory stand-in for a passed governed dataset.
|
||||
|
||||
Map-result export is an operational consumption boundary. These tests
|
||||
must therefore model the same source registry/snapshot, checksum and
|
||||
passed-contract evidence supplied by a real adapter rather than relying
|
||||
on an old transient Dataset fixture.
|
||||
"""
|
||||
|
||||
source = SourceRegistry(
|
||||
id=uuid4(),
|
||||
**SourceRegistryService.definition_for(source_key).as_model_values(),
|
||||
)
|
||||
checksum = sha256(f"{dataset_id}:{source_key}:{dataset_type}".encode("utf-8")).hexdigest()
|
||||
snapshot = SourceSnapshot(
|
||||
id=uuid4(),
|
||||
source_registry_id=source.id,
|
||||
snapshot_key=f"test:{source_key}:{checksum}",
|
||||
checksum_sha256=checksum,
|
||||
fetched_at=datetime.now(UTC),
|
||||
crs="EPSG:31370",
|
||||
units=source.default_units,
|
||||
spatial_resolution_json={"x": 1.0, "y": 1.0, "unit": "m"},
|
||||
temporal_coverage_json={"status": "test-fixture"},
|
||||
geographic_coverage_json={"zone": "Flanders"},
|
||||
observed_schema_json={"dataset_type": dataset_type},
|
||||
freshness_status="current",
|
||||
ingest_status="ingested",
|
||||
known_limitations_json=["In-memory governed fixture used only by this export test."],
|
||||
snapshot_metadata_json={"fixture_mode": True},
|
||||
)
|
||||
return Dataset(
|
||||
id=dataset_id,
|
||||
project_id=project_id,
|
||||
name=name,
|
||||
dataset_type=dataset_type,
|
||||
source="governed test fixture",
|
||||
dataset_role=dataset_role,
|
||||
source_name=source.source_key,
|
||||
source_registry_id=source.id,
|
||||
source_snapshot_id=snapshot.id,
|
||||
source_registry=source,
|
||||
source_snapshot=snapshot,
|
||||
data_contract_key=("geointel.raster.geotiff" if dataset_type == "raster" else "geointel.vector.geojson"),
|
||||
data_contract_version="1.0.0",
|
||||
validation_status="passed",
|
||||
provenance_status="complete",
|
||||
lineage_status="not_applicable",
|
||||
quarantine_status="not_quarantined",
|
||||
checksum_sha256=checksum,
|
||||
metadata_json={"fixture_mode": True},
|
||||
source_metadata={"fixture_mode": True, "source_registry_key": source.source_key},
|
||||
provenance_metadata={"fixture_mode": True, "source_snapshot_id": str(snapshot.id)},
|
||||
status="ready",
|
||||
)
|
||||
|
||||
|
||||
def test_map_result_export_request_requires_a_complete_target() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
MapResultExportRequest(project_id=uuid4(), mode="current", bbox=bbox_payload())
|
||||
@@ -67,13 +134,12 @@ def test_current_vector_map_result_uses_authoritative_selection_export(monkeypat
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
area_id = uuid4()
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
dataset = governed_dataset(
|
||||
project_id=project_id,
|
||||
dataset_id=dataset_id,
|
||||
name="buildings.geojson",
|
||||
dataset_type="vector",
|
||||
source="fixture",
|
||||
status="ready",
|
||||
source_key="grb",
|
||||
)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset})
|
||||
expected = ExportCreateResponse(
|
||||
@@ -109,14 +175,12 @@ def test_current_vector_map_result_uses_authoritative_selection_export(monkeypat
|
||||
def test_partitioned_vector_map_result_uses_governed_partition_export(monkeypatch) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
dataset = governed_dataset(
|
||||
project_id=project_id,
|
||||
dataset_id=dataset_id,
|
||||
name="vha-municipality.geojson",
|
||||
dataset_type="vector",
|
||||
source="VHA",
|
||||
source_name="vmm_vha_bathymetry_profiles",
|
||||
status="ready",
|
||||
source_key="vmm_vha_bathymetry_profiles",
|
||||
)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset})
|
||||
expected = ExportCreateResponse(
|
||||
@@ -159,14 +223,12 @@ def test_partitioned_vector_map_result_uses_governed_partition_export(monkeypatc
|
||||
def test_raster_map_result_is_recomputed_and_persisted(tmp_path, monkeypatch) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
dataset = governed_dataset(
|
||||
project_id=project_id,
|
||||
dataset_id=dataset_id,
|
||||
name="space-occupation.tif",
|
||||
dataset_type="raster",
|
||||
source="official",
|
||||
source_name="department_omgeving_thematic_raster",
|
||||
status="ready",
|
||||
source_key="department_omgeving_thematic_raster",
|
||||
)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset})
|
||||
export_path = tmp_path / "space-occupation-analysis.json"
|
||||
@@ -209,7 +271,26 @@ def test_evolution_map_result_is_recomputed_and_persisted(tmp_path, monkeypatch)
|
||||
project_id = uuid4()
|
||||
earlier_id = uuid4()
|
||||
later_id = uuid4()
|
||||
db = FakeSession()
|
||||
earlier_dataset = governed_dataset(
|
||||
project_id=project_id,
|
||||
dataset_id=earlier_id,
|
||||
name="forest-earlier.geojson",
|
||||
dataset_type="vector",
|
||||
source_key="inbo_bwk_natura2000",
|
||||
)
|
||||
later_dataset = governed_dataset(
|
||||
project_id=project_id,
|
||||
dataset_id=later_id,
|
||||
name="forest-later.geojson",
|
||||
dataset_type="vector",
|
||||
source_key="inbo_bwk_natura2000",
|
||||
)
|
||||
db = FakeSession(
|
||||
{
|
||||
(Dataset, earlier_id): earlier_dataset,
|
||||
(Dataset, later_id): later_dataset,
|
||||
}
|
||||
)
|
||||
export_path = tmp_path / "forest-evolution.json"
|
||||
captured: dict = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user