"""POST /api/runs + /api/runs/{id}/resume + /api/runs/{id}/abort — D2 write routes.""" from __future__ import annotations from collections.abc import AsyncIterator from pathlib import Path from unittest.mock import AsyncMock, patch import pytest from httpx import ASGITransport, AsyncClient from my_deepagent.api.app import create_app from my_deepagent.config import load_config from my_deepagent.persistence.db import Database @pytest.fixture async def app_client(tmp_path: Path) -> AsyncIterator[AsyncClient]: db_url = f"sqlite+aiosqlite:///{tmp_path / 'api_write.sqlite3'}" cfg = load_config( workspace_root=tmp_path, data_dir=tmp_path / "data", database_url=db_url, ) db = Database(db_url) await db.init_schema() await db.dispose() app = create_app(cfg) transport = ASGITransport(app=app) async with app.router.lifespan_context(app): async with AsyncClient(transport=transport, base_url="http://test") as client: yield client # --------------------------------------------------------------------------- # POST /api/runs validation # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_start_run_missing_template_returns_400( app_client: AsyncClient, tmp_path: Path ) -> None: r = await app_client.post( "/api/runs", json={ "template_path": "/this/does/not/exist.yaml", "repo_path": str(tmp_path), "base_branch": "main", "requirements_md": "test", }, ) assert r.status_code == 400 assert "not found" in r.json()["detail"] @pytest.mark.asyncio async def test_start_run_extra_field_returns_422(app_client: AsyncClient, tmp_path: Path) -> None: r = await app_client.post( "/api/runs", json={ "template_path": "spec-and-review@1.yaml", "repo_path": str(tmp_path), "rogue_field": "should-fail", }, ) # pydantic extra=forbid → 422 unprocessable entity assert r.status_code == 422 # --------------------------------------------------------------------------- # POST /api/runs/{id}/resume — 404 + 409 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_resume_unknown_run_404(app_client: AsyncClient) -> None: r = await app_client.post("/api/runs/00000000-0000-0000-0000-000000000000/resume") assert r.status_code == 404 # --------------------------------------------------------------------------- # POST /api/runs/{id}/abort — 404 + 409 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_abort_unknown_run_404(app_client: AsyncClient) -> None: r = await app_client.post("/api/runs/00000000-0000-0000-0000-000000000000/abort") assert r.status_code == 404 # --------------------------------------------------------------------------- # POST /api/runs success — mock the runner so we don't actually invoke OpenRouter # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_start_run_success_returns_run_id(app_client: AsyncClient, tmp_path: Path) -> None: from uuid import UUID fake_run_id = UUID("11111111-2222-3333-4444-555555555555") with patch( "my_deepagent.api.routes.runs.runner.start_new_run", new=AsyncMock(return_value=fake_run_id), ): r = await app_client.post( "/api/runs", json={ "template_path": "spec-and-review@1.yaml", "repo_path": str(tmp_path), "base_branch": "main", "requirements_md": "x", }, ) assert r.status_code == 200 body = r.json() assert body["run_id"] == str(fake_run_id) assert body["state"] == "executing"