pagina unica
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user