"""Unit tests for `mydeepagent runs list / show / resume` CLI commands.""" from __future__ import annotations import asyncio import uuid from pathlib import Path from unittest.mock import MagicMock, patch from typer.testing import CliRunner from my_deepagent.cli.main import app from my_deepagent.enums import RunState from my_deepagent.persistence.db import Database from my_deepagent.persistence.models import RunRow, WorkflowTemplateRow runner = CliRunner() _NOW = "2026-05-14T00:00:00+00:00" def _make_id() -> str: return str(uuid.uuid4()) def _template_row(template_id: str) -> WorkflowTemplateRow: return WorkflowTemplateRow( id=template_id, name="test-wf", version=1, hash=template_id, definition={}, created_at=_NOW, ) def _run_row( *, run_id: str | None = None, template_id: str, state: str = RunState.COMPLETED.value, repo_path: str = "/my/repo", base_branch: str = "main", ) -> RunRow: rid = run_id or _make_id() return RunRow( id=rid, template_id=template_id, template_hash="a" * 64, state=state, repo_path=repo_path, base_branch=base_branch, worktree_root="/wt", created_at=_NOW, updated_at=_NOW, ) # --------------------------------------------------------------------------- # Helpers: set up in-memory DB and patch load_config + Database # --------------------------------------------------------------------------- def _setup_db_with_run( tmp_path: Path, state: str = RunState.COMPLETED.value, repo_path: str = "/my/repo", ) -> tuple[str, str]: """Create a fresh DB with one run. Returns (db_url, run_id).""" db_url = f"sqlite+aiosqlite:///{tmp_path / 'test.db'}" async def _init() -> str: db = Database(db_url) await db.init_schema() tid = _make_id() run_id = _make_id() async with db.session() as s: s.add(_template_row(tid)) async with db.session() as s: s.add( _run_row( run_id=run_id, template_id=tid, state=state, repo_path=repo_path, ) ) await db.dispose() return run_id return db_url, asyncio.run(_init()) def _setup_empty_db(tmp_path: Path) -> str: """Create a fresh empty DB. Returns db_url.""" db_url = f"sqlite+aiosqlite:///{tmp_path / 'empty.db'}" async def _init() -> None: db = Database(db_url) await db.init_schema() await db.dispose() asyncio.run(_init()) return db_url # --------------------------------------------------------------------------- # Tests: runs list # --------------------------------------------------------------------------- def test_runs_list_empty_db(tmp_path: Path) -> None: """``runs list`` on empty DB prints '(no runs)'.""" db_url = _setup_empty_db(tmp_path) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "list"]) assert result.exit_code == 0, result.output assert "(no runs)" in result.output def test_runs_list_with_one_run(tmp_path: Path) -> None: """``runs list`` shows a table row when one run exists.""" db_url, run_id = _setup_db_with_run(tmp_path) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "list"]) assert result.exit_code == 0, result.output # Table should contain the first 8 chars of the run_id and the state. assert run_id[:8] in result.output assert RunState.COMPLETED.value in result.output def test_runs_list_state_filter(tmp_path: Path) -> None: """``runs list --state completed`` only shows completed runs.""" db_url, _run_id = _setup_db_with_run(tmp_path, state=RunState.COMPLETED.value) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) # Filter for failed → should return nothing. result = runner.invoke(app, ["runs", "list", "--state", "failed"]) assert result.exit_code == 0, result.output assert "(no runs)" in result.output # --------------------------------------------------------------------------- # Tests: runs show # --------------------------------------------------------------------------- def test_runs_show_unknown_run_id(tmp_path: Path) -> None: """``runs show `` exits with code 1.""" db_url = _setup_empty_db(tmp_path) fake_id = _make_id() with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "show", fake_id]) assert result.exit_code == 1 def test_runs_show_with_full_id(tmp_path: Path) -> None: """``runs show `` displays run details.""" db_url, run_id = _setup_db_with_run(tmp_path) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "show", run_id]) assert result.exit_code == 0, result.output assert run_id in result.output assert RunState.COMPLETED.value in result.output def test_runs_show_with_prefix(tmp_path: Path) -> None: """``runs show <6+ char prefix>`` resolves to the correct run.""" db_url, run_id = _setup_db_with_run(tmp_path) prefix = run_id[:8] with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "show", prefix]) assert result.exit_code == 0, result.output assert run_id in result.output # --------------------------------------------------------------------------- # Tests: runs resume # --------------------------------------------------------------------------- def test_runs_resume_completed_run_exits_one(tmp_path: Path) -> None: """``runs resume`` on a completed run exits 1 and says 'already terminal'.""" db_url, run_id = _setup_db_with_run(tmp_path, state=RunState.COMPLETED.value) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "resume", run_id]) assert result.exit_code == 1 assert "already terminal" in result.output def test_runs_resume_failed_run_exits_one(tmp_path: Path) -> None: """``runs resume`` on a failed run exits 1 and says 'already terminal'.""" db_url, run_id = _setup_db_with_run(tmp_path, state=RunState.FAILED.value) with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "resume", run_id]) assert result.exit_code == 1 assert "already terminal" in result.output def test_runs_resume_unknown_id_exits_one(tmp_path: Path) -> None: """``runs resume `` exits 1.""" db_url = _setup_empty_db(tmp_path) fake_id = _make_id() with patch("my_deepagent.cli.runs.load_config") as mock_cfg: mock_cfg.return_value = MagicMock(database_url=db_url) result = runner.invoke(app, ["runs", "resume", fake_id]) assert result.exit_code == 1