"""Governance promotion flow: train → evaluate → request → approve.""" import shutil from pathlib import Path import pytest from fastapi.testclient import TestClient from forja_core.api import deps from forja_core.main import create_app FIXTURES = Path(__file__).parent.parent / "fixtures" @pytest.fixture(autouse=True) def patch_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: agents_dir = tmp_path / "agents" shutil.copytree(FIXTURES / "agents", agents_dir) monkeypatch.setenv("DATA_DIR", str(tmp_path)) monkeypatch.setenv("AGENTS_DIR", str(agents_dir)) monkeypatch.setenv("POLICIES_DIR", str(FIXTURES / "policies")) monkeypatch.setenv("DATASETS_DIR", str(FIXTURES / "datasets")) monkeypatch.setenv("MODELS_DIR", str(FIXTURES / "models")) monkeypatch.setenv("TRAINING_BACKEND", "mock") for fn in ( deps.get_settings, deps.get_registry, deps.get_policy_store, deps.get_dataset_store, deps.get_model_store, deps.get_training_backend, deps.get_orchestrator, deps.get_llm_provider, deps.get_guardrail_engine, ): fn.cache_clear() def test_promotion_flow_approve_activates_draft_version() -> None: client = TestClient(create_app()) run = client.post( "/api/training/runs", json={ "dataset_name": "incident_sft", "model_name": "gpt4o_lora_base", "agent_name": "test_agent", "agent_version": "v2", }, ).json() run_id = run["id"] # Evaluation must cover the candidate version, not the active one. eval_report = client.post(f"/api/training/runs/{run_id}/evaluate").json() assert eval_report["passed"] is True assert eval_report["agent_version"] == "v2" promo = client.post( "/api/promotions", json={ "agent_name": "test_agent", "agent_version": "v2", "training_run_id": run_id, "evaluation_report_id": eval_report["id"], }, ) assert promo.status_code == 201 request_id = promo.json()["id"] approve = client.post( f"/api/promotions/{request_id}/approve", json={"approved_by": "ops-lead", "comment": "Eval gate passed"}, ) assert approve.status_code == 200 agent = client.get("/api/agents/test_agent").json() assert agent["version"] == "v2" assert agent["state"] == "active" def test_promotion_blocked_when_eval_failed(tmp_path: Path) -> None: """Promotion returns 422 if linked evaluation did not pass.""" from datetime import UTC, datetime from uuid import uuid4 from forja_core.api.evaluation_persistence import save_evaluation from forja_core.api.training_persistence import save_training_run from forja_core.domain.training import ( EvaluationReport, TrainingHyperparameters, TrainingJobSpec, TrainingRun, ) client = TestClient(create_app()) settings = deps.get_settings() run_id = uuid4() now = datetime.now(UTC) spec = TrainingJobSpec( dataset_name="incident_sft", dataset_version="v1", model_name="gpt4o_lora_base", model_version="v1", agent_name="test_agent", hyperparameters=TrainingHyperparameters(), ) save_training_run( settings.data_dir, TrainingRun( id=run_id, backend="mock", status="succeeded", spec=spec, external_job_id="mock:x", created_at=now, updated_at=now, ), ) report = EvaluationReport( id=uuid4(), training_run_id=run_id, agent_name="test_agent", agent_version="v1", passed=False, scenario_count=1, violation_count=1, created_at=now, notes="forced failure", ) save_evaluation(settings.data_dir, report) r = client.post( "/api/promotions", json={ "agent_name": "test_agent", "agent_version": "v2", "training_run_id": str(run_id), "evaluation_report_id": str(report.id), }, ) assert r.status_code == 422 def test_promotion_blocked_when_eval_covers_other_version() -> None: """Evaluating the active version must not unlock promoting a different draft.""" client = TestClient(create_app()) # No agent_version on the run → evaluation covers the active version (v1). run = client.post( "/api/training/runs", json={ "dataset_name": "incident_sft", "model_name": "gpt4o_lora_base", "agent_name": "test_agent", }, ).json() run_id = run["id"] eval_report = client.post(f"/api/training/runs/{run_id}/evaluate").json() assert eval_report["passed"] is True assert eval_report["agent_version"] == "v1" r = client.post( "/api/promotions", json={ "agent_name": "test_agent", "agent_version": "v2", "training_run_id": run_id, "evaluation_report_id": eval_report["id"], }, ) assert r.status_code == 422 assert "evaluate the candidate version" in r.json()["detail"]