from arena.codebase_archive import LocalCodebaseArchiveStore import pytest from arena.models import Criteria, ModelProviderConfig, ValidationCheck from arena.run_models import RunRequest from arena.run_store import InMemoryRunStore from arena.service import ArenaService, ArenaServiceError from arena.swarm_runtime import SwarmRuntime from arena.validator_graph import DeterministicValidatorExecutor, ValidatorGraph from conftest import make_local_session def make_service(archive_store=None): store = archive_store or LocalCodebaseArchiveStore() return ArenaService( session_factory=make_local_session, run_store=InMemoryRunStore(), swarm_runtime=SwarmRuntime( session_factory=make_local_session, archive_store=store, ), validator_graph=ValidatorGraph( executor=DeterministicValidatorExecutor(), rubric_enabled=False, ), archive_store=store, ) def test_service_creates_run_and_starts_swarm() -> None: service = make_service() access = service.create_run( RunRequest( prompt="Build a small FastAPI app", criteria=[Criteria(text="pytest must pass")], user_tests=["echo ok"], ) ) assert access.token assert access.run.prompt == "Build a small FastAPI app" assert access.run.criteria[0].text == "pytest must pass" view = service.get_run(access.run.id) assert view.status == "completed" assert view.tasks[0].status == "completed" assert "Built the app" in view.summary assert view.codebase_archive is not None assert view.validation_report is not None def test_service_accepts_model_provider_when_model_is_within_limit() -> None: service = make_service() access = service.create_run( RunRequest( prompt="Build a CLI", user_tests=[], model_provider=ModelProviderConfig(provider="custom", model="qwen2.5-7b-instruct"), ) ) assert access.run.model_provider is not None assert access.run.model_provider.model == "qwen2.5-7b-instruct" def test_service_rejects_model_provider_when_size_unknown() -> None: service = make_service() with pytest.raises(ArenaServiceError, match="unknown"): service.create_run( RunRequest( prompt="Build a CLI", model_provider=ModelProviderConfig(provider="gemini", model="gemini-2.5-flash"), ) ) def test_service_rejects_model_provider_over_32b() -> None: service = make_service() with pytest.raises(ArenaServiceError, match="<=32B"): service.create_run( RunRequest( prompt="Build a CLI", model_provider=ModelProviderConfig(provider="openrouter", model="llama-3.1-70b-instruct"), ) ) def test_service_rejoins_run_by_token() -> None: service = make_service() access = service.create_run(RunRequest(prompt="Build a CLI", user_tests=[])) rejoined = service.rejoin_run(access.token) assert rejoined.run.id == access.run.id assert service.get_run(access.run.id).id == access.run.id def test_service_add_run_message_records_user_activity() -> None: service = make_service() access = service.create_run(RunRequest(prompt="Build a CLI", user_tests=[])) view = service.add_run_message(access.run.id, "Please keep the sandbox warm") assert view.events[-1].message == "User: Please keep the sandbox warm" def test_service_downloads_codebase_zip() -> None: service = make_service() access = service.create_run(RunRequest(prompt="Build a CLI", user_tests=[])) payload = service.get_run_codebase_zip(access.run.id) assert payload.filename.endswith(".zip") assert payload.content_type == "application/zip" assert payload.data.startswith(b"PK") def test_service_exports_field_notes_and_trace() -> None: service = make_service() access = service.create_run(RunRequest(prompt="Build a CLI", user_tests=["echo ok"])) notes = service.get_run_field_notes(access.run.id) trace = service.get_run_trace_json(access.run.id) submission = service.get_run_submission_markdown(access.run.id) space_readme = service.get_run_space_readme(access.run.id) assert notes.filename.endswith("-field-notes.md") assert "Mom Test Questions" in notes.data assert "Scrap or scale" in notes.data assert trace.filename.endswith("-trace.json") assert '"prompt": "Build a CLI"' in trace.data assert submission.filename.endswith("-submission.md") assert "Build Small Hackathon Chapter 1" in submission.data assert "port 7860" in submission.data assert space_readme.filename.endswith("-space-README.md") assert "sdk: gradio" in space_readme.data assert "7860" in space_readme.data def test_service_validates_and_marks_completed() -> None: service = make_service() access = service.create_run( RunRequest(prompt="Build a CLI", user_tests=["echo ok"]) ) report = service.validate_run(access.run.id) view = service.get_run(access.run.id) assert report.passed is True assert view.status == "completed" assert view.validation_report is not None assert view.validation_report.passed is True assert view.validation_checks == [ ValidationCheck(id="user-test-1", name="User test 1", command="echo ok", source="user") ] def test_arena_service_does_not_expose_thread_inspection_methods() -> None: service = make_service() forbidden = [ "create_thread", "send_thread_message", "list_thread_files", "read_thread_file", "delete_thread", "sessions", ] leaked = [name for name in forbidden if hasattr(service, name)] assert leaked == [], f"ArenaService leaked Thread surface: {leaked}"