"""End-to-end CLI tests for ``muse code test``. Coverage: - ``muse code test --history`` prints history header (no test runs). - ``muse code test --flaky`` prints "No flaky tests found" when history empty. - ``muse code test --dry-run`` prints targets without running pytest. - ``muse code test --dry-run --json`` emits valid JSON. - ``muse code test --all`` runs full pytest discovery on an explicit file. - ``muse code test `` runs a specific file. - ``muse code test --ci`` executes CI gate suite. - ``muse code test --ci --json`` emits valid JSON CI result. - ``muse code test --json`` emits valid JSON run result. - History is persisted to .muse/cache/test_history.json after a run. - ``--no-save`` does not write history. """ from __future__ import annotations import hashlib import json import pathlib import sys import pytest from tests.cli_test_helper import CliRunner from muse.core.paths import muse_dir, test_history_path as _test_history_path runner = CliRunner() cli = None # argparse migration — CliRunner ignores this arg def _env(root: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(root)} # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture() def repo(tmp_path: pathlib.Path) -> pathlib.Path: """Create a minimal Muse repo with .muse/ and a passing test file.""" import datetime from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.core.ids import hash_commit, hash_snapshot from muse.core.object_store import write_object dot_muse = muse_dir(tmp_path) dot_muse.mkdir() repo_id = "test-repo" (dot_muse / "repo.json").write_text( '{"id": "test-repo", "name": "test"}' ) from muse.core.types import blob_id test_src = b"def test_passes() -> None:\n assert True\n" oid = blob_id(test_src) write_object(tmp_path, oid, test_src) tests_dir = tmp_path / "tests" tests_dir.mkdir() (tests_dir / "test_simple.py").write_bytes(test_src) manifest: Manifest = {"tests/test_simple.py": oid} snap_id = hash_snapshot(manifest) snap = SnapshotRecord( snapshot_id=snap_id, manifest=manifest, ) write_snapshot(tmp_path, snap) committed_at = datetime.datetime(2026, 3, 26, 12, 0, 0, tzinfo=datetime.timezone.utc) commit_id = hash_commit( parent_ids=[], snapshot_id=snap_id, message="init", committed_at_iso=committed_at.isoformat(), author="test", ) commit = CommitRecord( commit_id=commit_id, branch="main", snapshot_id=snap_id, message="init", committed_at=committed_at, author="test", ) write_commit(tmp_path, commit) refs_dir = dot_muse / "refs" / "heads" refs_dir.mkdir(parents=True) (refs_dir / "main").write_text(commit_id) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") return tmp_path # --------------------------------------------------------------------------- # History mode # --------------------------------------------------------------------------- class TestHistoryCommand: def test_history_empty(self, repo: pathlib.Path) -> None: """--history with no runs prints a sensible message.""" result = runner.invoke(cli, ["code", "test", "--history"], env=_env(repo)) assert result.exit_code == 0 assert "No test history recorded." in result.output def test_history_json(self, repo: pathlib.Path) -> None: """--history --json emits a JSON object.""" result = runner.invoke(cli, ["code", "test", "--history", "--json"], env=_env(repo)) assert result.exit_code == 0 data = json.loads(result.output) assert data["mode"] == "history" assert "history" in data def test_flaky_empty(self, repo: pathlib.Path) -> None: """--flaky with no history prints no-flaky message.""" result = runner.invoke(cli, ["code", "test", "--flaky"], env=_env(repo)) assert result.exit_code == 0 assert "No flaky tests found." in result.output # --------------------------------------------------------------------------- # Dry-run mode # --------------------------------------------------------------------------- class TestDryRun: def test_dry_run_text(self, repo: pathlib.Path) -> None: """--dry-run --all prints targets without executing pytest.""" result = runner.invoke( cli, ["code", "test", "--dry-run", "--all"], env=_env(repo) ) assert result.exit_code == 0 assert "Would run" in result.output or "pytest" in result.output.lower() def test_dry_run_json(self, repo: pathlib.Path) -> None: """--dry-run --json emits valid JSON.""" result = runner.invoke( cli, ["code", "test", "--dry-run", "--all", "--json"], env=_env(repo) ) assert result.exit_code == 0 data = json.loads(result.output) assert data["mode"] == "dry-run" def test_dry_run_with_symbol(self, repo: pathlib.Path) -> None: """--dry-run --symbol emits valid JSON with selection.""" result = runner.invoke( cli, [ "code", "test", "--dry-run", "--symbol", "tests/test_simple.py::test_passes", "--json", ], env=_env(repo), ) assert result.exit_code == 0 data = json.loads(result.output) assert data["mode"] == "dry-run" # --------------------------------------------------------------------------- # Execution mode # --------------------------------------------------------------------------- class TestRunTests: def test_run_specific_file_passes(self, repo: pathlib.Path) -> None: """Running a specific passing test file exits 0.""" result = runner.invoke( cli, ["code", "test", str(repo / "tests" / "test_simple.py")], env=_env(repo), ) assert result.exit_code == 0 def test_run_json_mode(self, repo: pathlib.Path) -> None: """--json emits a valid JSON run result.""" result = runner.invoke( cli, [ "code", "test", str(repo / "tests" / "test_simple.py"), "--json", ], env=_env(repo), ) assert result.exit_code == 0 # CliRunner combines stdout + stderr; _progress_cb writes dots to stderr # after the JSON block. Extract the JSON object directly. raw = result.output json_start = raw.index("{") json_end = raw.rindex("}") + 1 data = json.loads(raw[json_start:json_end]) assert data["mode"] == "run" assert "run" in data run_data = data["run"] assert run_data["passed"] >= 1 assert run_data["exit_code"] == 0 def test_failing_test_exits_nonzero(self, repo: pathlib.Path) -> None: """A failing test produces exit_code != 0.""" fail_file = repo / "tests" / "test_fail.py" fail_file.write_text( "def test_intentional_fail() -> None:\n assert False\n" ) result = runner.invoke( cli, ["code", "test", str(fail_file)], env=_env(repo), ) assert result.exit_code != 0 # --------------------------------------------------------------------------- # History persistence # --------------------------------------------------------------------------- class TestHistoryPersistence: def test_history_saved_after_run(self, repo: pathlib.Path) -> None: """After a successful run, .muse/cache/test_history.json exists.""" runner.invoke( cli, ["code", "test", str(repo / "tests" / "test_simple.py")], env=_env(repo), ) assert _test_history_path(repo).exists() def test_no_save_skips_history(self, repo: pathlib.Path) -> None: """--no-save prevents history file creation.""" runner.invoke( cli, ["code", "test", str(repo / "tests" / "test_simple.py"), "--no-save"], env=_env(repo), ) assert not _test_history_path(repo).exists() # --------------------------------------------------------------------------- # CI mode # --------------------------------------------------------------------------- class TestCiMode: def test_ci_with_passing_gate(self, repo: pathlib.Path) -> None: """--ci with an echo gate passes.""" toml = ( "version = 1\n\n" "[[gate]]\n" 'name = "echo"\n' 'command = ["echo", "hello"]\n' "timeout_s = 5\n" "required = true\n" ) (muse_dir(repo) / "ci.toml").write_text(toml) result = runner.invoke(cli, ["code", "test", "--ci"], env=_env(repo)) assert result.exit_code == 0 def test_ci_json_structure(self, repo: pathlib.Path) -> None: """--ci --json emits a valid CI result.""" toml = ( "version = 1\n\n" "[[gate]]\n" 'name = "echo"\n' 'command = ["echo", "ok"]\n' "timeout_s = 5\n" "required = true\n" ) (muse_dir(repo) / "ci.toml").write_text(toml) result = runner.invoke( cli, ["code", "test", "--ci", "--json"], env=_env(repo) ) assert result.exit_code == 0 data = json.loads(result.output) assert data["mode"] == "ci" assert "ci" in data ci = data["ci"] assert isinstance(ci["passed"], bool) assert isinstance(ci["gates"], list) def test_ci_failing_gate_exits_nonzero(self, repo: pathlib.Path) -> None: """--ci with a failing gate exits non-zero.""" toml = ( "version = 1\n\n" "[[gate]]\n" 'name = "fail"\n' f'command = ["{sys.executable}", "-c", "raise SystemExit(1)"]\n' "timeout_s = 5\n" "required = true\n" ) (muse_dir(repo) / "ci.toml").write_text(toml) result = runner.invoke(cli, ["code", "test", "--ci"], env=_env(repo)) assert result.exit_code != 0