Files
geointel/backend/tests/test_sprint236_bathymetry_expansion.py
T
JensandClaude Opus 5 6572e4ad5f scope frontend contracts to the feature, not to one file
93 test files read a single frontend source and asserted identifiers in it. The
MapWorkspace split showed what that costs: 24 tests went red for a move that
changed no behaviour at all. A contract belongs to the feature — a container,
its hooks, its domain layer — not to whichever file currently holds it.

232 read sites now resolve through read_feature(). The distinction that makes
this safe is direction: a *positive* contract ("this is wired") may widen,
because the identifier must still exist somewhere in the feature; a *negative*
one ("this component performs no transport") is a statement about one file, and
widening it would quietly weaken the check. The 73 single-file reads that
remain are exactly those, and a guard now enforces the rule for new tests.

Verified rather than assumed: of the 732 migrated positive assertions, 644 still
match exactly one module — as specific as before — and the other 86 already
spanned a container and its hook by nature. Two apparent misses are an artefact
of the checking regex reading an escaped newline literally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:05:43 +02:00

480 lines
17 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime
import json
from pathlib import Path
import ssl
import sys
from types import SimpleNamespace
from urllib.error import URLError
from uuid import uuid4
from fastapi.testclient import TestClient
from geoalchemy2.shape import from_shape
import pytest
from shapely.geometry import MultiPolygon, Polygon
from app.core.config import Settings
from app.core.errors import AppError
from app.db.session import get_db
from app.main import app
from app.models import Area, Dataset, DatasetVersion, Project
from app.schemas.bathymetry import BathymetryPartitionFinalizeRequest
from app.services.bathymetry_profile_acquisition_service import BathymetryProfileAcquisitionService
from app.services.mdk_bathymetry_probe_service import MdkBathymetryProbeService
from app.services.vector_feature_service import VectorFeatureService
ROOT = Path(__file__).resolve().parents[2]
SCRIPTS = ROOT / "scripts"
if str(SCRIPTS) not in sys.path:
sys.path.insert(0, str(SCRIPTS))
import provision_flanders_geographic_scope as flanders_scope # noqa: E402
from tests.frontend_contract import read_map_workspace, read_feature
class BinaryResponse:
def __init__(self, content: bytes, *, content_type: str = "application/xml"):
self.content = content
self.headers = {"Content-Type": content_type}
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def read(self, size=-1):
return self.content if size < 0 else self.content[:size]
class JsonResponse:
def __init__(self, payload, *, url="https://geo.api.vlaanderen.be/VRBG/items"):
self.payload = payload
self.url = url
def raise_for_status(self):
return None
def json(self):
return self.payload
class SourceSession:
def __init__(self, payload):
self.payload = payload
def get(self, *_args, **_kwargs):
return JsonResponse(self.payload)
class VersionQuery:
def __init__(self, versions):
self.versions = versions
def filter(self, *_args):
return self
def all(self):
return self.versions
class FinalizeSession:
def __init__(self, rows, versions=None):
self.rows = rows
self.versions = versions or []
self.commit_count = 0
def get(self, model, row_id):
return self.rows.get((model, row_id))
def query(self, model):
assert model is DatasetVersion
return VersionQuery(self.versions)
def commit(self):
self.commit_count += 1
def capabilities_xml() -> bytes:
return b"""<?xml version="1.0"?>
<WCS_Capabilities version="1.0.0" xmlns="http://www.opengis.net/wcs">
<ContentMetadata>
<CoverageOfferingBrief>
<name>EL.GridCoverage</name>
<label>Belgian Continental Shelf bathymetry</label>
<lonLatEnvelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84" />
</CoverageOfferingBrief>
</ContentMetadata>
<Capability><Request><GetCoverage><resultFormat><formats>GeoTIFF</formats></resultFormat></GetCoverage></Request></Capability>
</WCS_Capabilities>"""
def test_mdk_probe_parses_capabilities_without_enabling_acquisition() -> None:
seen = {}
def opener(request, timeout):
seen["url"] = request.full_url
seen["timeout"] = timeout
return BinaryResponse(capabilities_xml())
result = MdkBathymetryProbeService.probe(
settings=Settings(_env_file=None),
opener=opener,
checked_at=datetime(2026, 7, 17, tzinfo=UTC),
)
assert result["status"] == "reachable"
assert result["tls_verified"] is True
assert result["capabilities_reachable"] is True
assert result["acquisition_supported"] is False
assert result["coverage_identifiers"] == ["EL.GridCoverage"]
assert result["advertised_formats"] == ["GeoTIFF"]
assert result["response_sha256"]
assert "request=GetCapabilities" in seen["url"]
assert seen["timeout"] == 20
def test_mdk_probe_reports_tls_failure_and_never_uses_insecure_fallback() -> None:
calls = 0
def opener(_request, timeout):
nonlocal calls
assert timeout == 20
calls += 1
raise URLError(ssl.SSLCertVerificationError("hostname mismatch"))
result = MdkBathymetryProbeService.probe(
settings=Settings(_env_file=None),
opener=opener,
)
assert calls == 1
assert result["status"] == "tls_error"
assert result["tls_verified"] is False
assert result["capabilities_reachable"] is False
assert "insecure fallback is prohibited" in result["message"]
def test_mdk_readiness_api_uses_canonical_envelope(monkeypatch) -> None:
project_id = uuid4()
db = SimpleNamespace(get=lambda model, row_id: Project(id=project_id, name="Mol") if model is Project else None)
monkeypatch.setattr(
MdkBathymetryProbeService,
"probe",
lambda: {
"source_key": "mdk_bcp_bathymetry",
"status": "tls_error",
"configured_url": "https://example.invalid/wcs",
"tls_verified": False,
"capabilities_reachable": False,
"acquisition_supported": False,
"checked_at": "2026-07-18T00:00:00Z",
"message": "TLS validation failed.",
"limitation_message": "No insecure fallback is permitted.",
},
)
app.dependency_overrides[get_db] = lambda: db
try:
response = TestClient(app).get(
f"/api/v1/projects/{project_id}/datasets/bathymetry/sources/mdk_bcp_bathymetry/readiness"
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
assert set(response.json()) == {"data"}
assert response.json()["data"]["status"] == "tls_error"
assert response.json()["data"]["acquisition_supported"] is False
def test_partition_finalization_requires_complete_area_accounting_and_updates_versions() -> None:
project_id = uuid4()
area_ids = [uuid4(), uuid4()]
dataset_id = uuid4()
project = Project(id=project_id, name="Flanders")
areas = [
Area(id=area_ids[0], project_id=project_id, name="Gemeente Mol - officiële grens"),
Area(id=area_ids[1], project_id=project_id, name="Gemeente Geel - officiële grens"),
]
dataset = Dataset(
id=dataset_id,
project_id=project_id,
area_id=area_ids[0],
name="vha.geojson",
dataset_type="vector",
source="VHA",
source_name=BathymetryProfileAcquisitionService.PROVIDER,
source_metadata={
"profile_count": 3,
"document_count": 2,
"structured_depth_count": 1,
"measurement_date_min": "1990-01-01",
"measurement_date_max": "2020-01-01",
},
provenance_metadata={},
status="ready",
)
version = DatasetVersion(id=uuid4(), dataset_id=dataset_id, version=1)
rows = {
(Project, project_id): project,
(Area, area_ids[0]): areas[0],
(Area, area_ids[1]): areas[1],
(Dataset, dataset_id): dataset,
}
db = FinalizeSession(rows, [version])
payload = BathymetryPartitionFinalizeRequest(
partition_scope_key="flanders",
expected_area_ids=area_ids,
dataset_ids=[dataset_id],
no_profile_area_ids=[area_ids[1]],
manifest_sha256="a" * 64,
observed_at=datetime(2026, 7, 17, tzinfo=UTC),
)
result = BathymetryProfileAcquisitionService.finalize_partitions(db, project_id, payload)
assert result["regional_partitions_complete"] is True
assert result["partition_count"] == 2
assert result["data_partition_count"] == 1
assert result["no_profile_partition_count"] == 1
assert result["profile_count"] == 3
assert dataset.source_metadata["coverage_scope"] == "flanders"
assert dataset.source_metadata["municipality"] == "Mol"
assert dataset.source_metadata["partitioned_source_audit"] is True
assert version.source_metadata == dataset.source_metadata
assert version.provenance_metadata == dataset.provenance_metadata
assert db.commit_count == 1
incomplete = payload.model_copy(update={"no_profile_area_ids": []})
with pytest.raises(AppError) as exc_info:
BathymetryProfileAcquisitionService.finalize_partitions(
FinalizeSession(rows, [version]),
project_id,
incomplete,
)
assert exc_info.value.code == "BATHYMETRY_PARTITION_MANIFEST_INCOMPLETE"
def test_partition_selection_uses_latest_complete_manifest_without_duplicates() -> None:
project_id = uuid4()
source_name = BathymetryProfileAcquisitionService.PROVIDER
def partition(area_id, manifest, observed_at, data_partition_count):
return Dataset(
id=uuid4(),
project_id=project_id,
area_id=area_id,
name="vha.geojson",
dataset_type="vector",
source="VHA",
source_name=source_name,
source_metadata={
"regional_partitions_complete": True,
"partition_scope_key": "flanders",
"partition_manifest_sha256": manifest,
"partition_manifest_observed_at": observed_at,
"data_partition_count": data_partition_count,
},
status="ready",
)
old = [partition(uuid4(), "a" * 64, "2026-07-16T00:00:00+00:00", 1)]
new_area_ids = [uuid4(), uuid4()]
new = [
partition(area_id, "b" * 64, "2026-07-17T00:00:00+00:00", 2)
for area_id in new_area_ids
]
incomplete = [partition(uuid4(), "c" * 64, "2026-07-18T00:00:00+00:00", 2)]
selected = VectorFeatureService._latest_complete_partition_manifest(
[*old, *new, *incomplete],
source_name=source_name,
partition_scope_key="flanders",
)
assert {dataset.area_id for dataset in selected} == set(new_area_ids)
assert all(dataset.source_metadata["partition_manifest_sha256"] == "b" * 64 for dataset in selected)
def test_partition_selection_route_uses_exact_municipality_and_canonical_envelope(monkeypatch) -> None:
project_id, area_id, dataset_id = uuid4(), uuid4(), uuid4()
area_geometry = MultiPolygon(
[
Polygon(
[
(5.0, 51.0),
(5.2, 51.0),
(5.2, 51.2),
(5.0, 51.2),
(5.0, 51.0),
]
)
]
)
area = Area(
id=area_id,
project_id=project_id,
name="Gemeente Mol - officiele grens",
geometry=from_shape(area_geometry, srid=4326),
)
db = SimpleNamespace(get=lambda model, row_id: area if model is Area and row_id == area_id else None)
captured = {}
def select_partitions(_db, **kwargs):
captured.update(kwargs)
return {
"selection_bbox": kwargs["bbox"],
"selection_area_id": area_id,
"feature_count": 1,
"total_feature_count": 1,
"limit": kwargs["limit"],
"truncated": False,
"geojson": {"type": "FeatureCollection", "features": []},
"partition_count": 1,
"available_partition_count": 269,
"partition_scope_key": "flanders",
"source_name": BathymetryProfileAcquisitionService.PROVIDER,
"dataset_ids": [dataset_id],
}
monkeypatch.setattr(VectorFeatureService, "select_partitioned_features_by_bbox", select_partitions)
app.dependency_overrides[get_db] = lambda: db
try:
response = TestClient(app).post(
f"/api/v1/projects/{project_id}/datasets/bathymetry/profiles/partitions/select",
json={
"bbox": {
"min_x": 5.0,
"min_y": 51.0,
"max_x": 5.2,
"max_y": 51.2,
"crs": "EPSG:4326",
},
"area_id": str(area_id),
"limit": 1000,
},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
assert set(response.json()) == {"data"}
assert response.json()["data"]["partition_count"] == 1
assert response.json()["data"]["available_partition_count"] == 269
assert captured["partition_area_id"] == area_id
assert captured["source_name"] == BathymetryProfileAcquisitionService.PROVIDER
assert captured["partition_scope_key"] == "flanders"
def test_flanders_scope_discovery_uses_complete_unique_vrbg_inventory() -> None:
features = [
{
"type": "Feature",
"id": f"Refgem.{index:05d}",
"properties": {"NISCODE": f"{index:05d}", "NAAM": f"Gemeente {index:03d}"},
"geometry": {
"type": "Polygon",
"coordinates": [[[4.0, 50.5], [4.1, 50.5], [4.1, 50.6], [4.0, 50.5]]],
},
}
for index in range(1, 286)
]
scope, selected, source_url = flanders_scope.discover_flanders_scope(
SourceSession({"features": list(reversed(features))}),
timeout=30,
min_municipalities=270,
max_municipalities=300,
)
assert scope.key == "flanders"
assert scope.project_name == "Flanders Regional Workbench"
assert len(scope.members) == 285
assert len(set(scope.nis_codes)) == 285
assert [item["properties"]["NISCODE"] for item in selected] == sorted(scope.nis_codes)
assert source_url.startswith("https://geo.api.vlaanderen.be/")
def test_expansion_scripts_are_packaged_and_readiness_checked() -> None:
dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8")
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
for script in (
"provision_flanders_geographic_scope.py",
"provision_flanders_bathymetry_profiles.py",
"probe_mdk_bathymetry.py",
):
assert f"COPY scripts/{script}" in dockerfile
assert f"py_compile scripts/{script}" in readiness
sources = {
item["key"]: item
for item in BathymetryProfileAcquisitionService.list_sources()
}
assert sources["mdk_bcp_bathymetry"]["integration_status"] == "probe_only"
# Bounded acquisition is implemented but remains disabled by default.
assert sources["mdk_bcp_bathymetry"]["acquisition_supported"] is True
assert sources["mdk_bcp_bathymetry"]["configured"] is False
assert "EL_wcs" in sources["mdk_bcp_bathymetry"]["service_url"]
def test_frontend_exhaustively_pages_regional_area_inventory() -> None:
area_api = (ROOT / "frontend" / "src" / "services" / "api" / "areas.ts").read_text(
encoding="utf-8"
)
assert "const AREA_PAGE_SIZE = 200" in area_api
assert "while (offset < (total ?? 0))" in area_api
assert "items.length !== total" in area_api
assert "list: listProjectAreas" in area_api
def test_frontend_bounds_large_area_and_dataset_catalogs() -> None:
area_panel = (
ROOT / "frontend" / "src" / "components" / "project" / "AreaPanel.tsx"
).read_text(encoding="utf-8")
dataset_panel = read_feature("datasets")
assert "const AREA_CATALOG_PAGE_SIZE = 12" in area_panel
assert "{catalogOpen ? (" in area_panel
assert "visibleAreas.map" in area_panel
assert "Zoek gemeente of regio" in area_panel
assert "const DATASET_CATALOG_PAGE_SIZE = 10" in dataset_panel
assert "visiblePrimaryDatasets.map" in dataset_panel
assert "Zoek in beschikbare bronnen" in dataset_panel
assert "{historyOpen ? <ul" in dataset_panel
def test_frontend_labels_flanders_scope_without_kempen_mislabeling() -> None:
dataset_display = (
ROOT / "frontend" / "src" / "lib" / "datasetDisplay.ts"
).read_text(encoding="utf-8")
assert "coverageScope === 'flanders' && layer === 'regional_boundary'" in dataset_display
assert "'Grens Vlaanderen'" in dataset_display
assert "coverageScope === 'flanders' && layer === 'municipality_boundaries'" in dataset_display
assert "'Gemeentegrenzen Vlaanderen'" in dataset_display
def test_frontend_uses_partitioned_bathymetry_selection_for_regional_scope() -> None:
app = read_feature("shell")
index = (ROOT / "frontend" / "index.html").read_text(encoding="utf-8")
focus = (
ROOT / "frontend" / "src" / "config" / "primaryFocus.ts"
).read_text(encoding="utf-8")
map_workspace = read_map_workspace()
theme_hook = read_feature("map_workspace")
dataset_api = read_feature("datasets")
assert "isPartitionedBathymetry" in map_workspace
assert "regionalPartitionedThemeActive" in map_workspace
assert "availabilityLabel" in map_workspace
assert "activeSelectionResult?.geojson ?? null" in map_workspace
assert "selectBathymetryProfilePartitions" in theme_hook
assert "datasets/bathymetry/profiles/partitions/select" in dataset_api
assert "regionalBathymetryContextActive" in app
assert "regionalBathymetryProfileCount" in app
assert "profielen · ${regionalBathymetryPartitions.length} gemeenten" in app
assert "FLANDERS_WORKSPACE_LABEL = 'Vlaanderen (285 gemeenten)'" in focus
assert "<title>GeoIntel</title>" in index