Automate RC8 release journeys
This commit is contained in:
@@ -1889,3 +1889,34 @@ python scripts/verify_python_lock.py
|
||||
|
||||
The complete gate and vulnerability/SBOM policy are documented in
|
||||
`docs/CI_SUPPLY_CHAIN.md`.
|
||||
|
||||
## Release golden areas
|
||||
|
||||
The RC browser suite uses seven deterministic, bounded regression areas across
|
||||
Belgium and the Belgian North Sea. Preview the required changes without
|
||||
mutating the runtime:
|
||||
|
||||
```bash
|
||||
python scripts/provision_release_golden_areas.py \
|
||||
--base-url http://127.0.0.1:8000 \
|
||||
--output artifacts/rc8-golden-areas.json
|
||||
```
|
||||
|
||||
Create only missing Areas through the canonical project/area APIs:
|
||||
|
||||
```bash
|
||||
python scripts/provision_release_golden_areas.py \
|
||||
--base-url http://127.0.0.1:8000 \
|
||||
--output artifacts/rc8-golden-areas.json \
|
||||
--apply
|
||||
```
|
||||
|
||||
The operator copies the governed Mol and Kempen geometries into the national
|
||||
workbench with their source project/Area identifiers and provisions bounded
|
||||
Wallonia, Brussels, language-boundary, coast and offshore multi-zone Areas.
|
||||
Every geometry receives a deterministic SHA-256 fingerprint in the evidence
|
||||
file. It never imports provider data or writes directly to database tables.
|
||||
|
||||
Explicit demo seeding also reactivates its own archived technical project.
|
||||
This keeps the opt-in fixture workflow selectable without changing the normal
|
||||
active-project lifecycle.
|
||||
|
||||
@@ -102,6 +102,16 @@ class DemoWorkflowService:
|
||||
return project
|
||||
return projects[0] if projects else None
|
||||
|
||||
@staticmethod
|
||||
def _activate_explicit_demo_project(db: Session, project: Project | None) -> Project | None:
|
||||
if project is None or project.status == "active":
|
||||
return project
|
||||
project.status = "active"
|
||||
db.add(project)
|
||||
db.commit()
|
||||
db.refresh(project)
|
||||
return project
|
||||
|
||||
@staticmethod
|
||||
def _has_complete_demo_state(db: Session, project_id: UUID) -> bool:
|
||||
area = db.query(Area).filter(Area.project_id == project_id).first()
|
||||
@@ -398,7 +408,10 @@ class DemoWorkflowService:
|
||||
|
||||
@staticmethod
|
||||
def seed(db: Session) -> DemoWorkflowResponse:
|
||||
existing = DemoWorkflowService._find_existing_project(db)
|
||||
existing = DemoWorkflowService._activate_explicit_demo_project(
|
||||
db,
|
||||
DemoWorkflowService._find_existing_project(db),
|
||||
)
|
||||
reference_payload, reference_raw = DemoWorkflowService._load_fixture("reference_buildings.geojson")
|
||||
candidate_payload, candidate_raw = DemoWorkflowService._load_fixture("predicted_buildings.geojson")
|
||||
if existing:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT_PATH = ROOT / "scripts" / "provision_release_golden_areas.py"
|
||||
|
||||
|
||||
def load_operator():
|
||||
spec = importlib.util.spec_from_file_location("provision_release_golden_areas_test", SCRIPT_PATH)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_release_golden_area_contract_covers_belgium_and_north_sea() -> None:
|
||||
module = load_operator()
|
||||
created_keys = {item["key"] for item in module.GOLDEN_AREAS}
|
||||
source_keys = {item["key"] for item in module.SOURCE_AREAS}
|
||||
|
||||
assert created_keys == {
|
||||
"wallonia_urban_rural",
|
||||
"brussels_urban",
|
||||
"language_boundary",
|
||||
"coast_land_sea",
|
||||
"north_sea_multi_zone",
|
||||
}
|
||||
assert source_keys == {"mol_municipality", "kempen_region"}
|
||||
assert len(created_keys | source_keys) == 7
|
||||
assert all(item["source_project"] != module.NATIONAL_PROJECT for item in module.SOURCE_AREAS)
|
||||
|
||||
expected_zones = {
|
||||
zone
|
||||
for definition in (*module.GOLDEN_AREAS, *module.SOURCE_AREAS)
|
||||
for zone in definition["expected_zones"]
|
||||
}
|
||||
assert {
|
||||
"flanders",
|
||||
"wallonia",
|
||||
"brussels",
|
||||
"territorial_sea",
|
||||
"exclusive_economic_zone",
|
||||
"continental_shelf",
|
||||
} <= expected_zones
|
||||
|
||||
|
||||
def test_release_golden_area_geometries_are_bounded_and_fingerprintable() -> None:
|
||||
module = load_operator()
|
||||
hashes = set()
|
||||
for definition in module.GOLDEN_AREAS:
|
||||
bbox = module.geometry_bbox(definition["geometry"])
|
||||
assert -180 <= bbox["minx"] < bbox["maxx"] <= 180
|
||||
assert -90 <= bbox["miny"] < bbox["maxy"] <= 90
|
||||
assert bbox["maxx"] - bbox["minx"] <= 0.25
|
||||
assert bbox["maxy"] - bbox["miny"] <= 0.20
|
||||
digest = module.canonical_hash(definition["geometry"])
|
||||
assert len(digest) == 64
|
||||
hashes.add(digest)
|
||||
assert len(hashes) == len(module.GOLDEN_AREAS)
|
||||
|
||||
|
||||
def test_rc8_runner_and_container_operator_are_release_wired() -> None:
|
||||
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
|
||||
dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8")
|
||||
package = (ROOT / "frontend" / "package.json").read_text(encoding="utf-8")
|
||||
|
||||
assert "npm run test:unit" in readiness
|
||||
assert '--check frontend/e2e/releaseJourneys.mjs' in readiness
|
||||
assert "bash -n scripts/run_rc8_release_journeys.sh" in readiness
|
||||
assert "COPY scripts/provision_release_golden_areas.py" in dockerfile
|
||||
assert '"test:e2e": "node e2e/releaseJourneys.mjs"' in package
|
||||
@@ -5,6 +5,7 @@ from uuid import uuid4
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
from app.models import Project
|
||||
from app.schemas.demo import DemoWorkflowResponse
|
||||
from app.services.demo_workflow_service import DemoWorkflowService
|
||||
|
||||
@@ -80,3 +81,30 @@ def test_demo_workflow_prefers_complete_existing_demo_project() -> None:
|
||||
assert "order_by(Project.created_at.asc())" in service
|
||||
assert "if DemoWorkflowService._has_complete_demo_state(db, project.id):" in service
|
||||
assert "return projects[0] if projects else None" in service
|
||||
|
||||
|
||||
def test_explicit_demo_seed_reactivates_an_archived_fixture_project() -> None:
|
||||
project = Project(id=uuid4(), name=DemoWorkflowService.PROJECT_NAME, status="archived")
|
||||
|
||||
class Session:
|
||||
added = []
|
||||
commits = 0
|
||||
refreshed = []
|
||||
|
||||
def add(self, value) -> None:
|
||||
self.added.append(value)
|
||||
|
||||
def commit(self) -> None:
|
||||
self.commits += 1
|
||||
|
||||
def refresh(self, value) -> None:
|
||||
self.refreshed.append(value)
|
||||
|
||||
db = Session()
|
||||
result = DemoWorkflowService._activate_explicit_demo_project(db, project)
|
||||
|
||||
assert result is project
|
||||
assert project.status == "active"
|
||||
assert db.added == [project]
|
||||
assert db.commits == 1
|
||||
assert db.refreshed == [project]
|
||||
|
||||
Reference in New Issue
Block a user