"""Phase 6 — RL_28–RL_30: ``muse reflog exists`` subcommand. TDD: tests written before implementation. Coverage tiers -------------- RL_28 muse reflog exists --json returns {"exists": true, "count": N}; exit 0 RL_29 muse reflog exists --branch dev --json returns exists:false, count:0; exit 1 RL_30 exit code 0 for exists, 1 for does-not-exist — scriptable without --json """ from __future__ import annotations import json import pathlib import time import pytest from muse.core.paths import muse_dir, logs_dir from muse.core.types import fake_id from tests.cli_test_helper import CliRunner runner = CliRunner() cli = None # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _env(root: pathlib.Path) -> dict[str, str]: return {"MUSE_REPO_ROOT": str(root)} def _make_repo(tmp_path: pathlib.Path, branch: str = "main") -> pathlib.Path: """Minimal .muse/ repo with no commits and an empty config.""" from muse._version import __version__ dot = muse_dir(tmp_path) for sub in ( "refs/heads", "objects/sha256", "commits", "snapshots", "logs/refs/heads", "logs", ): (dot / sub).mkdir(parents=True, exist_ok=True) (dot / "repo.json").write_text( json.dumps({"repo_id": "test-repo", "schema_version": __version__, "domain": "code"}) ) (dot / "HEAD").write_text(f"ref: refs/heads/{branch}\n") (dot / "config.toml").write_text("") return tmp_path def _head_log(root: pathlib.Path) -> pathlib.Path: return logs_dir(root) / "HEAD" def _branch_log(root: pathlib.Path, branch: str) -> pathlib.Path: return logs_dir(root) / "refs" / "heads" / branch def _write_entries(log_path: pathlib.Path, n: int = 3) -> None: """Write n reflog entries to log_path.""" log_path.parent.mkdir(parents=True, exist_ok=True) old = fake_id("old") new = fake_id("new") ts = int(time.time()) lines = [f"{old} {new} user {ts} +0000\tcommit: entry-{i}\n" for i in range(n)] log_path.write_text("".join(lines), encoding="utf-8") # --------------------------------------------------------------------------- # RL_28 — muse reflog exists --json: exists=true, count=N, exit 0 # --------------------------------------------------------------------------- class TestReflogExistsTrue: """RL_28: HEAD log has entries → exists=true, count=N, exit 0.""" def test_json_exists_true_when_head_log_has_entries( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=5) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) assert r.exit_code == 0, r.output data = json.loads(r.output) assert data["exists"] is True assert data["count"] == 5 def test_json_schema_has_all_required_fields( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=1) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) data = json.loads(r.output) for field in ("exists", "count", "ref", "exit_code", "duration_ms"): assert field in data, f"missing key {field!r}" def test_ref_field_is_head_by_default(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=2) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) data = json.loads(r.output) assert data["ref"] == "HEAD" def test_count_matches_actual_entry_count(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=7) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) data = json.loads(r.output) assert data["count"] == 7 def test_branch_log_exists_with_entries(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_branch_log(root, "dev"), n=3) r = runner.invoke( cli, ["reflog", "exists", "--branch", "dev", "--json"], env=_env(root) ) assert r.exit_code == 0, r.output data = json.loads(r.output) assert data["exists"] is True assert data["count"] == 3 def test_branch_log_ref_field_uses_full_path( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) _write_entries(_branch_log(root, "main"), n=1) r = runner.invoke( cli, ["reflog", "exists", "--branch", "main", "--json"], env=_env(root) ) data = json.loads(r.output) assert data["ref"] == "refs/heads/main" def test_single_entry_reports_count_one(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=1) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) data = json.loads(r.output) assert data["exists"] is True assert data["count"] == 1 # --------------------------------------------------------------------------- # RL_29 — muse reflog exists --branch dev: exists=false, count=0, exit 1 # --------------------------------------------------------------------------- class TestReflogExistsFalse: """RL_29: no log file → exists=false, count=0, exit 1.""" def test_json_exists_false_when_no_head_log( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) # No log written r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) assert r.exit_code == 1 data = json.loads(r.output) assert data["exists"] is False assert data["count"] == 0 def test_json_exists_false_when_branch_has_no_log( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) # HEAD has entries but dev branch does not _write_entries(_head_log(root), n=3) r = runner.invoke( cli, ["reflog", "exists", "--branch", "dev", "--json"], env=_env(root) ) assert r.exit_code == 1 data = json.loads(r.output) assert data["exists"] is False assert data["count"] == 0 def test_json_exists_false_after_all_entries_deleted( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) log = _head_log(root) _write_entries(log, n=2) # Delete all entries runner.invoke(cli, ["reflog", "delete", "--all"], env=_env(root)) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) assert r.exit_code == 1 data = json.loads(r.output) assert data["exists"] is False def test_ref_field_is_head_when_no_log(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) data = json.loads(r.output) assert data["ref"] == "HEAD" def test_branch_ref_field_on_missing_branch_log( self, tmp_path: pathlib.Path ) -> None: root = _make_repo(tmp_path) r = runner.invoke( cli, ["reflog", "exists", "--branch", "feat", "--json"], env=_env(root) ) data = json.loads(r.output) assert data["ref"] == "refs/heads/feat" # --------------------------------------------------------------------------- # RL_30 — exit codes without --json; scriptable interface # --------------------------------------------------------------------------- class TestReflogExistsExitCode: """RL_30: exit 0 when entries exist; exit 1 when none — no --json needed.""" def test_exit_zero_when_entries_exist(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_head_log(root), n=3) r = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) assert r.exit_code == 0 def test_exit_one_when_no_entries(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) r = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) assert r.exit_code == 1 def test_exit_zero_branch_exists(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) _write_entries(_branch_log(root, "main"), n=1) r = runner.invoke(cli, ["reflog", "exists", "--branch", "main"], env=_env(root)) assert r.exit_code == 0 def test_exit_one_branch_missing(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) r = runner.invoke(cli, ["reflog", "exists", "--branch", "missing-branch"], env=_env(root)) assert r.exit_code == 1 def test_text_output_reports_count(self, tmp_path: pathlib.Path) -> None: """Human output should mention the count when entries exist.""" root = _make_repo(tmp_path) _write_entries(_head_log(root), n=4) r = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) # The count should appear in human output. assert "4" in r.output def test_text_output_reports_no_entries(self, tmp_path: pathlib.Path) -> None: """Human output should indicate no entries when the log is absent.""" root = _make_repo(tmp_path) r = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) combined = r.output + (r.stderr or "") assert "no" in combined.lower() or "0" in combined def test_exit_code_matches_json_exists_field( self, tmp_path: pathlib.Path ) -> None: """JSON exists=true → exit 0; exists=false → exit 1.""" root = _make_repo(tmp_path) _write_entries(_head_log(root), n=2) r_json = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root)) r_plain = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) data = json.loads(r_json.output) assert data["exists"] is True assert r_json.exit_code == 0 assert r_plain.exit_code == 0 def test_empty_repo_always_exits_one(self, tmp_path: pathlib.Path) -> None: root = _make_repo(tmp_path) r = runner.invoke(cli, ["reflog", "exists"], env=_env(root)) assert r.exit_code == 1