Skip to content

Commit fff8d23

Browse files
Kasper JungeRalphify
authored andcommitted
test: add coverage for cli.py — add command and main callback
Cover the `add` command (single/multiple ralphs, parse errors, fetch errors, directory creation) and the no-subcommand banner path. CLI module coverage rises from 83% to 97%. Co-authored-by: Ralphify <noreply@ralphify.co>
1 parent d88ba39 commit fff8d23

2 files changed

Lines changed: 103 additions & 4 deletions

File tree

src/ralphify/cli.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def _validate_name(name: str, context: str) -> None:
9090

9191
TAGLINE = "Stop stressing over not having an agent running. Ralph is always running"
9292

93+
_RALPHIFY_RALPHS_DIR = Path(".ralphify") / "ralphs"
94+
"""Project-local directory for installed ralphs."""
95+
9396
_INIT_TEMPLATE = """\
9497
---
9598
agent: claude -p --dangerously-skip-permissions
@@ -352,10 +355,6 @@ def _validate_commands(raw_commands: Any) -> list[Command]:
352355
return _parse_command_items(raw_commands)
353356

354357

355-
_RALPHIFY_RALPHS_DIR = Path(".ralphify") / "ralphs"
356-
"""Project-local directory for installed ralphs."""
357-
358-
359358
def _installed_ralph_path(name: str) -> Path | None:
360359
"""Return the installed ralph directory if it exists, else *None*."""
361360
path = Path.cwd() / _RALPHIFY_RALPHS_DIR / name

tests/test_cli.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,106 @@ def test_credit_invalid_value_errors(self, mock_which, tmp_path, monkeypatch):
792792
assert "true or false" in result.output.lower()
793793

794794

795+
class TestMainCallback:
796+
def test_no_subcommand_prints_banner_and_help(self):
797+
result = runner.invoke(app, [])
798+
assert result.exit_code == 0
799+
# Banner contains the ASCII art — check a recognizable fragment
800+
assert "RALPHIFY" in result.output.upper() or "ralph" in result.output.lower()
801+
# Help text should be present
802+
assert "run" in result.output.lower()
803+
804+
def test_no_subcommand_shows_tagline(self):
805+
result = runner.invoke(app, [])
806+
assert result.exit_code == 0
807+
assert "Ralph is always running" in result.output
808+
809+
810+
class TestAdd:
811+
@patch("ralphify._source.fetch_ralphs")
812+
@patch("ralphify._source.parse_github_source")
813+
def test_add_single_ralph(self, mock_parse, mock_fetch, tmp_path, monkeypatch):
814+
monkeypatch.chdir(tmp_path)
815+
from ralphify._source import ParsedSource, FetchResult
816+
parsed = ParsedSource(
817+
repo_url="https://github.com/owner/repo.git",
818+
subpath="my-ralph",
819+
handle="owner/repo/my-ralph",
820+
name="my-ralph",
821+
)
822+
mock_parse.return_value = parsed
823+
dest = tmp_path / ".ralphify" / "ralphs" / "my-ralph"
824+
mock_fetch.return_value = FetchResult(installed=[("my-ralph", dest)])
825+
826+
result = runner.invoke(app, ["add", "owner/repo/my-ralph"])
827+
assert result.exit_code == 0
828+
assert "Added" in result.output
829+
assert "my-ralph" in result.output
830+
assert "ralph run my-ralph" in result.output
831+
832+
@patch("ralphify._source.fetch_ralphs")
833+
@patch("ralphify._source.parse_github_source")
834+
def test_add_multiple_ralphs(self, mock_parse, mock_fetch, tmp_path, monkeypatch):
835+
monkeypatch.chdir(tmp_path)
836+
from ralphify._source import ParsedSource, FetchResult
837+
parsed = ParsedSource(
838+
repo_url="https://github.com/owner/repo.git",
839+
subpath=None,
840+
handle="owner/repo",
841+
name="repo",
842+
)
843+
mock_parse.return_value = parsed
844+
mock_fetch.return_value = FetchResult(installed=[
845+
("ralph-a", tmp_path / "a"),
846+
("ralph-b", tmp_path / "b"),
847+
])
848+
849+
result = runner.invoke(app, ["add", "owner/repo"])
850+
assert result.exit_code == 0
851+
assert "Added 2 ralphs" in result.output
852+
assert "ralph-a" in result.output
853+
assert "ralph-b" in result.output
854+
assert "ralph run <name>" in result.output
855+
856+
@patch("ralphify._source.parse_github_source", side_effect=ValueError("Cannot parse source 'bad'"))
857+
def test_add_invalid_source_errors(self, mock_parse, tmp_path, monkeypatch):
858+
monkeypatch.chdir(tmp_path)
859+
result = runner.invoke(app, ["add", "bad"])
860+
assert result.exit_code == 1
861+
assert "Cannot parse source" in result.output
862+
863+
@patch("ralphify._source.fetch_ralphs", side_effect=RuntimeError("git clone failed"))
864+
@patch("ralphify._source.parse_github_source")
865+
def test_add_fetch_failure_errors(self, mock_parse, mock_fetch, tmp_path, monkeypatch):
866+
monkeypatch.chdir(tmp_path)
867+
from ralphify._source import ParsedSource
868+
mock_parse.return_value = ParsedSource(
869+
repo_url="https://github.com/owner/repo.git",
870+
subpath=None,
871+
handle="owner/repo",
872+
name="repo",
873+
)
874+
result = runner.invoke(app, ["add", "owner/repo"])
875+
assert result.exit_code == 1
876+
assert "git clone failed" in result.output
877+
878+
@patch("ralphify._source.fetch_ralphs")
879+
@patch("ralphify._source.parse_github_source")
880+
def test_add_creates_ralphs_dir(self, mock_parse, mock_fetch, tmp_path, monkeypatch):
881+
monkeypatch.chdir(tmp_path)
882+
from ralphify._source import ParsedSource, FetchResult
883+
mock_parse.return_value = ParsedSource(
884+
repo_url="https://github.com/owner/repo.git",
885+
subpath="x",
886+
handle="owner/repo/x",
887+
name="x",
888+
)
889+
mock_fetch.return_value = FetchResult(installed=[("x", tmp_path / "x")])
890+
result = runner.invoke(app, ["add", "owner/repo/x"])
891+
assert result.exit_code == 0
892+
assert (tmp_path / ".ralphify" / "ralphs").is_dir()
893+
894+
795895
class TestTwoStageCtrlC:
796896
"""Test the two-stage Ctrl+C signal handler installed by the run command."""
797897

0 commit comments

Comments
 (0)