feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run
Some checks are pending
Qualité / tests (push) Waiting to run
This commit is contained in:
119
tests/test_orchestration_workflow.py
Normal file
119
tests/test_orchestration_workflow.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def disposable_repository(logged_in):
|
||||
client, app, projects, _, headers = logged_in
|
||||
root = projects / "orchestration-repo"
|
||||
root.mkdir()
|
||||
subprocess.run(["git", "init", "-b", "main"], cwd=root, check=True, capture_output=True)
|
||||
(root / "app.txt").write_text("base\n", encoding="utf-8")
|
||||
subprocess.run(["git", "add", "app.txt"], cwd=root, check=True)
|
||||
subprocess.run(
|
||||
["git", "-c", "user.name=Test", "-c", "user.email=test@example.invalid", "commit", "-m", "Base"],
|
||||
cwd=root, check=True, capture_output=True,
|
||||
)
|
||||
owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"]
|
||||
project_id = str(uuid.uuid4())
|
||||
app.state.db.execute(
|
||||
"INSERT INTO projects(id,name,path,owner_id,full_access,merge_confirmation_required,created_at) VALUES(?,?,?,?,?,?,?)",
|
||||
(project_id, "Disposable", str(root), owner, 1, 1, "2026-01-01"),
|
||||
)
|
||||
return client, app, headers, root, project_id
|
||||
|
||||
|
||||
def finish_agent(app, orchestration: dict, content: str) -> Path:
|
||||
worktree = Path(orchestration["worktree_path"])
|
||||
(worktree / "app.txt").write_text(content, encoding="utf-8")
|
||||
app.state.db.execute("UPDATE prompt_queue SET status='done',finished_at='2026-01-02' WHERE id=?", (orchestration["queue_id"],))
|
||||
app.state.db.execute("UPDATE conversations SET status='completed',worker_pid=NULL WHERE id=?", (orchestration["conversation_id"],))
|
||||
return worktree
|
||||
|
||||
|
||||
def test_complete_orchestration_refusal_correction_resume_and_merge(logged_in, monkeypatch):
|
||||
client, app, headers, root, project_id = disposable_repository(logged_in)
|
||||
monkeypatch.setattr(app.state.codex, "detect", lambda *args: "/synthetic-agent")
|
||||
monkeypatch.setattr(app.state.codex, "_spawn", lambda *args: 4242)
|
||||
created = client.post(
|
||||
"/api/orchestrations", headers=headers,
|
||||
json={
|
||||
"project_id": project_id, "name": "Refonte jetable",
|
||||
"request": "Modifier le fichier de démonstration sans toucher au dépôt principal avant validation.",
|
||||
"acceptance_criteria": ["Le contenu final vaut approved", "La vérification dédiée réussit"],
|
||||
"tests": [["python3", "-c", "from pathlib import Path; assert Path('app.txt').read_text().strip() in {'draft','approved'}"]],
|
||||
"engine": "codex",
|
||||
},
|
||||
)
|
||||
assert created.status_code == 201, created.text
|
||||
flow = created.json()
|
||||
assert flow["status"] == "DRAFT_REVIEW"
|
||||
|
||||
rejected = client.post(
|
||||
f"/api/orchestrations/{flow['id']}/draft/reject", headers=headers,
|
||||
json={"feedback": "Préciser la preuve attendue et conserver la branche dédiée."},
|
||||
)
|
||||
assert rejected.status_code == 200
|
||||
flow = rejected.json()
|
||||
assert flow["draft_version"] == 2
|
||||
assert "preuve attendue" in flow["draft"]
|
||||
|
||||
approved = client.post(
|
||||
f"/api/orchestrations/{flow['id']}/draft/approve", headers=headers,
|
||||
json={"draft_hash": flow["draft_hash"]},
|
||||
)
|
||||
assert approved.status_code == 200, approved.text
|
||||
flow = approved.json()
|
||||
worktree = finish_agent(app, flow, "draft\n")
|
||||
assert not (root / "app.txt").read_text().startswith("draft")
|
||||
|
||||
first_detail = client.get(f"/api/orchestrations/{flow['id']}").json()
|
||||
second_detail = client.get(f"/api/orchestrations/{flow['id']}").json()
|
||||
assert first_detail["status"] == second_detail["status"] == "AWAITING_VERIFICATION"
|
||||
assert len([event for event in second_detail["events"] if event["kind"] == "agent.finished"]) == 1
|
||||
|
||||
verified = client.post(f"/api/orchestrations/{flow['id']}/verify", headers=headers)
|
||||
assert verified.status_code == 200, verified.text
|
||||
flow = verified.json()
|
||||
assert flow["status"] == "USER_REVIEW"
|
||||
first_revision = flow["verified_revision"]
|
||||
|
||||
corrections = client.post(
|
||||
f"/api/orchestrations/{flow['id']}/corrections", headers=headers,
|
||||
json={"feedback": "La recette refuse draft : remplacer par approved."},
|
||||
)
|
||||
assert corrections.status_code == 200, corrections.text
|
||||
flow = corrections.json()
|
||||
assert flow["conversation_id"] == first_detail["conversation_id"]
|
||||
assert Path(flow["worktree_path"]) == worktree
|
||||
finish_agent(app, flow, "approved\n")
|
||||
assert client.get(f"/api/orchestrations/{flow['id']}").json()["status"] == "AWAITING_VERIFICATION"
|
||||
|
||||
flow = client.post(f"/api/orchestrations/{flow['id']}/verify", headers=headers).json()
|
||||
assert flow["status"] == "USER_REVIEW"
|
||||
assert flow["verified_revision"] != first_revision
|
||||
flow = client.post(
|
||||
f"/api/orchestrations/{flow['id']}/approve", headers=headers,
|
||||
json={"revision": flow["verified_revision"]},
|
||||
).json()
|
||||
assert flow["status"] == "AWAITING_MERGE_CONFIRMATION"
|
||||
assert client.post(f"/api/orchestrations/{flow['id']}/merge", headers=headers, json={"confirm": False}).status_code == 409
|
||||
|
||||
merged = client.post(f"/api/orchestrations/{flow['id']}/merge", headers=headers, json={"confirm": True})
|
||||
assert merged.status_code == 200, merged.text
|
||||
assert merged.json()["status"] == "MERGED"
|
||||
assert (root / "app.txt").read_text(encoding="utf-8") == "approved\n"
|
||||
|
||||
|
||||
def test_orchestration_cannot_be_started_by_unauthorized_user(logged_in):
|
||||
client, app, headers, _, project_id = disposable_repository(logged_in)
|
||||
app.state.auth.create_user("other", "a-different-long-test-password", is_admin=False)
|
||||
client.post("/api/auth/logout", headers=headers)
|
||||
login = client.post("/api/auth/login", json={"username": "other", "password": "a-different-long-test-password"}).json()
|
||||
response = client.post(
|
||||
"/api/orchestrations", headers={"X-CSRF-Token": login["csrf_token"]},
|
||||
json={"project_id": project_id, "name": "Interdit", "request": "Cette demande ne doit jamais démarrer dans le projet privé.", "acceptance_criteria": ["Refus"], "tests": []},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
Reference in New Issue
Block a user