Add Belgium and North Sea coverage foundation
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from shapely.geometry import box
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.main import app
|
||||
from app.models import Area, Dataset, Project
|
||||
from app.schemas.coverage import CoverageBBox
|
||||
from app.services.coverage_registry_service import CoverageRegistryService, THEMES, ZONES
|
||||
|
||||
|
||||
class FakeQuery:
|
||||
def __init__(self, rows):
|
||||
self.rows = rows
|
||||
|
||||
def filter(self, *_args, **_kwargs):
|
||||
return self
|
||||
|
||||
def all(self):
|
||||
return list(self.rows)
|
||||
|
||||
|
||||
class FakeSession:
|
||||
def __init__(self, *, project, areas, datasets):
|
||||
self.project = project
|
||||
self.areas = areas
|
||||
self.datasets = datasets
|
||||
|
||||
def get(self, model, object_id):
|
||||
if model is Project and str(self.project.id) == str(object_id):
|
||||
return self.project
|
||||
return None
|
||||
|
||||
def query(self, model):
|
||||
if model is Area:
|
||||
return FakeQuery(self.areas)
|
||||
if model is Dataset:
|
||||
return FakeQuery(self.datasets)
|
||||
raise AssertionError(f"Unexpected query model: {model}")
|
||||
|
||||
|
||||
def scope_area(name: str, geometry):
|
||||
return SimpleNamespace(name=name, geometry=geometry)
|
||||
|
||||
|
||||
def test_coverage_catalog_uses_normalized_contracts_and_does_not_change_provider_registry() -> None:
|
||||
catalog = CoverageRegistryService.catalog()
|
||||
|
||||
assert set(catalog.themes) == set(THEMES)
|
||||
assert set(catalog.zones) == set(ZONES)
|
||||
assert catalog.statuses == ["unsupported", "not_configured", "partial", "operational"]
|
||||
assert {source.source_name for source in catalog.sources} >= {
|
||||
"ngi_adminvector",
|
||||
"statbel",
|
||||
"digitaal_vlaanderen",
|
||||
"spw_geoportail",
|
||||
"urbis",
|
||||
"rbins_marine_reporting_units",
|
||||
"rbins_msp_2026",
|
||||
"mdk_bathymetry",
|
||||
}
|
||||
assert next(source for source in catalog.sources if source.source_name == "ngi_adminvector").license_note == "CC BY 4.0"
|
||||
assert next(source for source in catalog.sources if source.source_name == "mdk_bathymetry").integration_status == "not_configured"
|
||||
|
||||
response = TestClient(app).get("/api/v1/external/coverage/catalog")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["data"]["themes"] == list(THEMES)
|
||||
|
||||
|
||||
def test_coverage_resolver_only_reports_operational_for_materialized_ready_dataset() -> None:
|
||||
project_id = uuid4()
|
||||
project = SimpleNamespace(id=project_id)
|
||||
areas = [
|
||||
scope_area("Belgium land", box(2.5, 49.5, 6.4, 51.5)),
|
||||
scope_area("Flanders", box(2.5, 50.7, 5.9, 51.5)),
|
||||
]
|
||||
bbox = CoverageBBox(minx=4.9, miny=50.9, maxx=5.0, maxy=51.0)
|
||||
|
||||
without_materialized = CoverageRegistryService.resolve(
|
||||
FakeSession(project=project, areas=areas, datasets=[]),
|
||||
project_id,
|
||||
bbox,
|
||||
["admin"],
|
||||
)
|
||||
assert without_materialized.intersected_zones == ["flanders"]
|
||||
assert without_materialized.items[0].status == "partial"
|
||||
assert without_materialized.items[0].materialized_dataset_ids == []
|
||||
|
||||
dataset_id = uuid4()
|
||||
materialized = SimpleNamespace(
|
||||
id=dataset_id,
|
||||
status="ready",
|
||||
source_name="ngi_adminvector",
|
||||
reference_layer_name="belgium_regions",
|
||||
source_metadata={"coverage_zones": ["belgium", "flanders", "wallonia", "brussels"]},
|
||||
)
|
||||
with_materialized = CoverageRegistryService.resolve(
|
||||
FakeSession(project=project, areas=areas, datasets=[materialized]),
|
||||
project_id,
|
||||
bbox,
|
||||
["admin"],
|
||||
)
|
||||
admin_item = next(item for item in with_materialized.items if item.zone == "flanders")
|
||||
assert admin_item.status == "operational"
|
||||
assert admin_item.materialized_dataset_ids == [dataset_id]
|
||||
|
||||
|
||||
def test_mixed_land_and_north_sea_selection_remains_split() -> None:
|
||||
project_id = uuid4()
|
||||
project = SimpleNamespace(id=project_id)
|
||||
areas = [
|
||||
scope_area("Belgium land", box(2.5, 49.5, 6.4, 51.5)),
|
||||
scope_area("Flanders", box(2.5, 50.7, 5.9, 51.5)),
|
||||
scope_area("Belgian part of the North Sea", box(2.2, 51.1, 3.4, 51.9)),
|
||||
scope_area("Belgian territorial sea (0-12 nautical miles)", box(2.7, 51.1, 3.4, 51.5)),
|
||||
scope_area("Belgian exclusive economic zone beyond territorial sea", box(2.2, 51.5, 3.1, 51.9)),
|
||||
scope_area("Belgian continental shelf beyond territorial sea", box(2.2, 51.5, 3.1, 51.9)),
|
||||
]
|
||||
|
||||
result = CoverageRegistryService.resolve(
|
||||
FakeSession(project=project, areas=areas, datasets=[]),
|
||||
project_id,
|
||||
CoverageBBox(minx=2.65, miny=51.05, maxx=2.85, maxy=51.2),
|
||||
["admin", "bathymetry"],
|
||||
)
|
||||
|
||||
assert result.intersected_zones == ["flanders", "territorial_sea"]
|
||||
assert len(result.items) == 4
|
||||
assert any("crosses coverage zones" in warning for warning in result.warnings)
|
||||
bathymetry = next(item for item in result.items if item.zone == "territorial_sea" and item.theme == "bathymetry")
|
||||
assert bathymetry.status == "not_configured"
|
||||
assert bathymetry.materialized_dataset_ids == []
|
||||
|
||||
|
||||
def test_flemish_materialization_is_theme_specific() -> None:
|
||||
project_id = uuid4()
|
||||
project = SimpleNamespace(id=project_id)
|
||||
orthophoto_id = uuid4()
|
||||
orthophoto = SimpleNamespace(
|
||||
id=orthophoto_id,
|
||||
status="ready",
|
||||
source_name="digitaal_vlaanderen_orthophoto",
|
||||
reference_layer_name="orthophoto",
|
||||
source_metadata={"coverage_zones": ["flanders"]},
|
||||
)
|
||||
result = CoverageRegistryService.resolve(
|
||||
FakeSession(
|
||||
project=project,
|
||||
areas=[scope_area("Flanders", box(2.5, 50.7, 5.9, 51.5))],
|
||||
datasets=[orthophoto],
|
||||
),
|
||||
project_id,
|
||||
CoverageBBox(minx=4.9, miny=50.9, maxx=5.0, maxy=51.0),
|
||||
["orthophoto", "roads"],
|
||||
)
|
||||
|
||||
assert next(item for item in result.items if item.theme == "orthophoto").status == "operational"
|
||||
assert next(item for item in result.items if item.theme == "roads").status == "partial"
|
||||
|
||||
|
||||
def test_outside_scope_and_unknown_theme_are_explicit() -> None:
|
||||
project_id = uuid4()
|
||||
project = SimpleNamespace(id=project_id)
|
||||
db = FakeSession(
|
||||
project=project,
|
||||
areas=[scope_area("Belgium land", box(2.5, 49.5, 6.4, 51.5))],
|
||||
datasets=[],
|
||||
)
|
||||
result = CoverageRegistryService.resolve(
|
||||
db,
|
||||
project_id,
|
||||
CoverageBBox(minx=7.0, miny=52.0, maxx=7.1, maxy=52.1),
|
||||
["admin"],
|
||||
)
|
||||
assert result.intersected_zones == []
|
||||
assert result.outside_supported_scope is True
|
||||
assert result.items == []
|
||||
|
||||
try:
|
||||
CoverageRegistryService.resolve(
|
||||
db,
|
||||
project_id,
|
||||
CoverageBBox(minx=4.0, miny=50.0, maxx=4.1, maxy=50.1),
|
||||
["invented_metric"],
|
||||
)
|
||||
except AppError as exc:
|
||||
assert exc.code == "COVERAGE_THEME_UNSUPPORTED"
|
||||
assert exc.status_code == 422
|
||||
assert exc.details["unsupported_themes"] == ["invented_metric"]
|
||||
else:
|
||||
raise AssertionError("Unknown coverage theme was accepted")
|
||||
|
||||
|
||||
def test_frontend_prefers_materialized_national_workspace_and_resolves_drawn_bbox() -> None:
|
||||
root = Path(__file__).parents[2]
|
||||
focus = (root / "frontend" / "src" / "config" / "primaryFocus.ts").read_text(encoding="utf-8")
|
||||
workspace_hook = (root / "frontend" / "src" / "hooks" / "useProjectWorkspace.ts").read_text(encoding="utf-8")
|
||||
coverage_hook = (root / "frontend" / "src" / "hooks" / "useCoverageResolver.ts").read_text(encoding="utf-8")
|
||||
map_workspace = (root / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text(encoding="utf-8")
|
||||
|
||||
assert "Belgium and North Sea Workbench" in focus
|
||||
assert "nationalProject" in workspace_hook
|
||||
assert "data.areas.length > 0" in workspace_hook
|
||||
assert "dataset.status === 'ready'" in workspace_hook
|
||||
assert "externalApi.resolveCoverage" in coverage_hook
|
||||
assert "coverage.outside_supported_scope" in map_workspace
|
||||
assert "coverageStatusLabel" in map_workspace
|
||||
Reference in New Issue
Block a user