Close full operational audit findings
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
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_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")
|
||||
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 "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
|
||||
Reference in New Issue
Block a user