| """Integration tests: real HTTP layer with real sandbox, stores, and validation.""" |
|
|
| import asyncio |
|
|
| import httpx |
| import pytest |
|
|
| from arena.api import app, service |
| from arena.codebase_archive import LocalCodebaseArchiveStore |
| from arena.run_models import RunRequest |
| from arena.run_store import InMemoryRunStore |
| from arena.swarm_runtime import SwarmRuntime |
| from arena.validator_graph import DeterministicValidatorExecutor, ValidatorGraph |
|
|
| from conftest import make_local_session |
|
|
|
|
| def request(method: str, path: str, **kwargs) -> httpx.Response: |
| async def _request() -> httpx.Response: |
| transport = httpx.ASGITransport(app=app) |
| async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: |
| return await client.request(method, path, **kwargs) |
|
|
| return asyncio.run(_request()) |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def _reset_service() -> None: |
| from arena.event_bus import EventBus |
|
|
| store = LocalCodebaseArchiveStore() |
| event_bus = EventBus() |
| service._run_store = InMemoryRunStore() |
| service._archive_store = store |
| service._swarm_runtime = SwarmRuntime( |
| session_factory=make_local_session, |
| archive_store=store, |
| event_bus=event_bus, |
| ) |
| service._validator_graph = ValidatorGraph( |
| executor=DeterministicValidatorExecutor(), |
| rubric_enabled=False, |
| ) |
| from arena.run_flow import RunFlow |
| service._run_flow = RunFlow( |
| run_store=service._run_store, |
| swarm_runtime=service._swarm_runtime, |
| validator_graph=service._validator_graph, |
| archive_store=service._archive_store, |
| event_bus=event_bus, |
| ) |
|
|
|
|
| def test_health_endpoint_returns_ok() -> None: |
| response = request("GET", "/health") |
| assert response.status_code == 200 |
| assert response.json() == {"status": "ok"} |
|
|
|
|
| def test_skills_endpoint_returns_list() -> None: |
| response = request("GET", "/skills") |
| assert response.status_code == 200 |
| skills = response.json() |
| assert isinstance(skills, list) |
|
|
|
|
| def test_create_run_and_get_status() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": ["echo ok"]}, |
| ) |
| assert response.status_code == 200 |
| data = response.json() |
| assert "token" not in data |
| run_id = data["id"] |
|
|
| status_resp = request("GET", f"/runs/{run_id}") |
| assert status_resp.status_code == 200 |
| run = status_resp.json() |
| assert run["id"] == run_id |
| assert run["prompt"] == "Build a CLI" |
| assert run["status"] == "completed" |
| assert len(run["tasks"]) >= 1 |
| assert run["codebase_archive"] is not None |
| assert run["validation_report"] is not None |
|
|
|
|
| def test_rejoin_run() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": []}, |
| ) |
| run_id = response.json()["id"] |
|
|
| access = service._run_store.create_run(RunRequest(prompt="Private rejoin")) |
|
|
| rejoin_resp = request("POST", "/runs/rejoin", json={"token": access.token}) |
| assert rejoin_resp.status_code == 200 |
| assert "token" not in rejoin_resp.json() |
| assert rejoin_resp.json()["id"] == access.run.id |
| assert rejoin_resp.json()["id"] != run_id |
|
|
|
|
| def test_validate_run() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": ["echo ok"]}, |
| ) |
| run_id = response.json()["id"] |
|
|
| validate_resp = request("POST", f"/runs/{run_id}/validate") |
| assert validate_resp.status_code == 200 |
| report = validate_resp.json() |
| assert report["passed"] is True |
| assert report["run_id"] == run_id |
|
|
| final = request("GET", f"/runs/{run_id}") |
| assert final.json()["status"] == "completed" |
| assert final.json()["validation_report"] is not None |
|
|
|
|
| def test_download_codebase_zip() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": []}, |
| ) |
| run_id = response.json()["id"] |
|
|
| zip_resp = request("GET", f"/runs/{run_id}/codebase.zip") |
| assert zip_resp.status_code == 200 |
| assert zip_resp.headers["content-type"] == "application/zip" |
| assert len(zip_resp.content) > 0 |
| assert zip_resp.content[:2] == b"PK" |
|
|
|
|
| def test_start_run_nonblocking() -> None: |
| import time |
| response = request( |
| "POST", |
| "/runs/start", |
| json={"prompt": "Build a CLI", "user_tests": ["echo ok"]}, |
| ) |
| assert response.status_code == 200 |
| run_id = response.json()["id"] |
| assert response.json()["status"] in ("queued", "planning", "working", "validating", "completed") |
|
|
| time.sleep(0.2) |
|
|
| final = request("GET", f"/runs/{run_id}") |
| assert final.json()["status"] in ("working", "validating", "completed") |
|
|
|
|
| def test_cancel_run() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": []}, |
| ) |
| run_id = response.json()["id"] |
|
|
| cancel_resp = request("POST", f"/runs/{run_id}/cancel") |
| assert cancel_resp.status_code == 200 |
| assert cancel_resp.json()["status"] == "completed" |
|
|
|
|
| def test_add_run_message() -> None: |
| response = request( |
| "POST", |
| "/runs", |
| json={"prompt": "Build a CLI", "user_tests": []}, |
| ) |
| run_id = response.json()["id"] |
|
|
| message_resp = request( |
| "POST", |
| f"/runs/{run_id}/messages", |
| json={"content": "Please keep this active"}, |
| ) |
|
|
| assert message_resp.status_code == 200 |
| assert "token" not in message_resp.json() |
| assert message_resp.json()["events"][-1]["message"] == "User: Please keep this active" |
|
|
|
|
| def test_run_not_found_returns_404() -> None: |
| response = request("GET", "/runs/nonexistent") |
| assert response.status_code == 404 |
|
|
|
|
| def test_rejoin_bad_token_returns_404() -> None: |
| response = request("POST", "/runs/rejoin", json={"token": "not-a-real-token"}) |
| assert response.status_code == 404 |
|
|