171 lines
6.2 KiB
Python
171 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from types import SimpleNamespace
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.routes import projects as project_routes
|
|
from app.main import app
|
|
from app.schemas.project import ProjectRead, ProjectUpdate
|
|
from app.services.project_service import ProjectService
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def load_cleanup_module():
|
|
script = ROOT / "scripts" / "archive_technical_projects.py"
|
|
spec = importlib.util.spec_from_file_location("archive_technical_projects_test", script)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_project_list_defaults_to_active_and_can_request_archived(monkeypatch) -> None:
|
|
captured: list[str] = []
|
|
|
|
def fake_list(_db, *, limit, offset, name, project_status):
|
|
del limit, offset, name
|
|
captured.append(project_status)
|
|
return [
|
|
ProjectRead(
|
|
id=uuid4(),
|
|
name=f"{project_status} project",
|
|
region="Kempen",
|
|
status=project_status if project_status != "all" else "active",
|
|
)
|
|
], 1
|
|
|
|
monkeypatch.setattr(ProjectService, "list_projects", fake_list)
|
|
client = TestClient(app)
|
|
|
|
assert client.get("/api/v1/projects").status_code == 200
|
|
assert client.get("/api/v1/projects", params={"status": "archived"}).status_code == 200
|
|
assert captured == ["active", "archived"]
|
|
|
|
|
|
def test_project_update_schema_allows_only_active_or_archived() -> None:
|
|
from pydantic import ValidationError
|
|
|
|
from app.schemas.project import ProjectUpdate
|
|
|
|
assert ProjectUpdate(status="archived").status == "archived"
|
|
try:
|
|
ProjectUpdate(status="deleted")
|
|
except ValidationError:
|
|
pass
|
|
else:
|
|
raise AssertionError("ProjectUpdate must not expose deleted as an ordinary lifecycle state")
|
|
|
|
|
|
def test_project_update_returns_404_when_project_is_missing(monkeypatch) -> None:
|
|
monkeypatch.setattr(ProjectService, "update_project", lambda *_args, **_kwargs: None)
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
project_routes.update_project(uuid4(), ProjectUpdate(status="archived"), db=SimpleNamespace())
|
|
|
|
assert exc_info.value.status_code == 404
|
|
|
|
|
|
def test_cleanup_allowlist_preserves_real_workspaces() -> None:
|
|
module = load_cleanup_module()
|
|
|
|
assert module.is_technical_project_name("GeoIntel Detection Quality Matrix 42")
|
|
assert module.is_technical_project_name("GeoIntel hard-negative Mol 20260709")
|
|
assert module.is_technical_project_name("GeoIntel Detection Calibration 0.15 20260709T090018Z")
|
|
assert module.is_technical_project_name("GeoIntel Real Data Validation 20260707T000620Z")
|
|
assert module.is_technical_project_name("GeoIntel Operational YOLO Geel Smoke 20260711T133656Z")
|
|
assert module.is_technical_project_name("GeoIntel Demo - Building QA")
|
|
assert not module.is_technical_project_name("Kempen Regional Workbench")
|
|
assert not module.is_technical_project_name("Mol Municipality Workbench")
|
|
assert not module.is_technical_project_name("Vrij project van een gebruiker")
|
|
|
|
|
|
def test_cleanup_plan_selects_active_allowlisted_projects_only() -> None:
|
|
module = load_cleanup_module()
|
|
rows = [
|
|
SimpleNamespace(
|
|
id=uuid4(),
|
|
name="GeoIntel Detection Quality Matrix 1",
|
|
status="active",
|
|
),
|
|
SimpleNamespace(
|
|
id=uuid4(),
|
|
name="Kempen Regional Workbench",
|
|
status="active",
|
|
),
|
|
]
|
|
|
|
class Query:
|
|
def filter(self, *_args):
|
|
return self
|
|
|
|
def order_by(self, *_args):
|
|
return self
|
|
|
|
def all(self):
|
|
return rows
|
|
|
|
class Session:
|
|
def query(self, _model):
|
|
return Query()
|
|
|
|
plan = module.build_archive_plan(Session())
|
|
|
|
assert plan.count == 1
|
|
assert plan.names == ("GeoIntel Detection Quality Matrix 1",)
|
|
|
|
|
|
def test_cleanup_script_is_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")
|
|
|
|
assert "COPY scripts/archive_technical_projects.py" in dockerfile
|
|
assert "py_compile scripts/archive_technical_projects.py" in readiness
|
|
|
|
|
|
def test_cleanup_script_can_start_as_a_direct_operator_command() -> None:
|
|
result = subprocess.run(
|
|
[sys.executable, str(ROOT / "scripts/archive_technical_projects.py"), "--help"],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
check=False,
|
|
text=True,
|
|
timeout=20,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "--apply" in result.stdout
|
|
assert "--show-names" in result.stdout
|
|
|
|
|
|
def test_frontend_lifecycle_and_component_boundaries_are_wired() -> None:
|
|
app_source = (ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
|
|
project_panel = (ROOT / "frontend/src/components/project/ProjectPanel.tsx").read_text(encoding="utf-8")
|
|
detection_lab = (ROOT / "frontend/src/components/detection/DetectionLab.tsx").read_text(encoding="utf-8")
|
|
segmentation_lab = (ROOT / "frontend/src/components/segmentation/SegmentationLab.tsx").read_text(encoding="utf-8")
|
|
map_workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
|
|
premium_css = (ROOT / "frontend/src/styles/premium.css").read_text(encoding="utf-8")
|
|
|
|
assert "OverviewWorkspace" in app_source
|
|
assert "onArchiveProject={archiveProject}" in app_source
|
|
assert "DetectionModelManagement" in detection_lab
|
|
assert "persistedDetectionModelLabel" in detection_lab
|
|
assert "Lokaal gebouwmodel" in detection_lab
|
|
assert "persistedSegmentationModelLabel" in segmentation_lab
|
|
assert "Testsegmentatie" in segmentation_lab
|
|
assert "from './mapWorkspaceUtils'" in map_workspace
|
|
assert "Werkruimte archiveren" in project_panel
|
|
assert "PROTECTED_PROJECT_NAMES" in project_panel
|
|
assert ".technical-inline-details" in premium_css
|
|
assert ".workspace-grid-ai" in premium_css
|