cgcardona / muse public
test_cli_remote.py python
181 lines 7.4 KB
99c4ff79 feat: add remote sync commands (remote, clone, fetch, pull, push, ls-remote) Gabriel Cardona <cgcardona@gmail.com> 1d ago
1 """Tests for muse remote — add, remove, rename, list, get-url, set-url."""
2
3 from __future__ import annotations
4
5 import json
6 import os
7 import pathlib
8
9 import pytest
10 from typer.testing import CliRunner
11
12 from muse.cli.app import cli
13 from muse.cli.config import get_remote, list_remotes
14
15
16 # ---------------------------------------------------------------------------
17 # Fixture — initialised repo
18 # ---------------------------------------------------------------------------
19
20
21 @pytest.fixture
22 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
23 """Minimal .muse/ repo; cwd and MUSE_REPO_ROOT set to tmp_path."""
24 muse_dir = tmp_path / ".muse"
25 (muse_dir / "refs" / "heads").mkdir(parents=True)
26 (muse_dir / "objects").mkdir()
27 (muse_dir / "commits").mkdir()
28 (muse_dir / "snapshots").mkdir()
29 (muse_dir / "repo.json").write_text(
30 json.dumps({"repo_id": "test-repo", "schema_version": "2", "domain": "midi"})
31 )
32 (muse_dir / "HEAD").write_text("refs/heads/main\n")
33 (muse_dir / "refs" / "heads" / "main").write_text("")
34 (muse_dir / "config.toml").write_text("")
35 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
36 monkeypatch.chdir(tmp_path)
37 return tmp_path
38
39
40 runner = CliRunner()
41
42
43 # ---------------------------------------------------------------------------
44 # remote add
45 # ---------------------------------------------------------------------------
46
47
48 class TestRemoteAdd:
49 def test_add_new_remote(self, repo: pathlib.Path) -> None:
50 result = runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
51 assert result.exit_code == 0
52 assert "origin" in result.output
53 assert get_remote("origin", repo) == "https://hub.muse.io/repos/r1"
54
55 def test_add_duplicate_remote_fails(self, repo: pathlib.Path) -> None:
56 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
57 result = runner.invoke(cli, ["remote", "add", "origin", "https://other.com/r"])
58 assert result.exit_code != 0
59 assert "already exists" in result.output
60
61 def test_add_multiple_remotes(self, repo: pathlib.Path) -> None:
62 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
63 runner.invoke(cli, ["remote", "add", "upstream", "https://hub.muse.io/repos/r2"])
64 remotes = {r["name"] for r in list_remotes(repo)}
65 assert remotes == {"origin", "upstream"}
66
67
68 # ---------------------------------------------------------------------------
69 # remote remove
70 # ---------------------------------------------------------------------------
71
72
73 class TestRemoteRemove:
74 def test_remove_existing_remote(self, repo: pathlib.Path) -> None:
75 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
76 result = runner.invoke(cli, ["remote", "remove", "origin"])
77 assert result.exit_code == 0
78 assert get_remote("origin", repo) is None
79
80 def test_remove_nonexistent_remote_fails(self, repo: pathlib.Path) -> None:
81 result = runner.invoke(cli, ["remote", "remove", "ghost"])
82 assert result.exit_code != 0
83 assert "does not exist" in result.output
84
85 def test_remove_cleans_tracking_refs(self, repo: pathlib.Path) -> None:
86 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
87 refs_dir = repo / ".muse" / "remotes" / "origin"
88 refs_dir.mkdir(parents=True)
89 (refs_dir / "main").write_text("abc123")
90 runner.invoke(cli, ["remote", "remove", "origin"])
91 assert not refs_dir.exists()
92
93
94 # ---------------------------------------------------------------------------
95 # remote rename
96 # ---------------------------------------------------------------------------
97
98
99 class TestRemoteRename:
100 def test_rename_existing_remote(self, repo: pathlib.Path) -> None:
101 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
102 result = runner.invoke(cli, ["remote", "rename", "origin", "upstream"])
103 assert result.exit_code == 0
104 assert get_remote("upstream", repo) == "https://hub.muse.io/repos/r1"
105 assert get_remote("origin", repo) is None
106
107 def test_rename_nonexistent_fails(self, repo: pathlib.Path) -> None:
108 result = runner.invoke(cli, ["remote", "rename", "ghost", "new"])
109 assert result.exit_code != 0
110 assert "does not exist" in result.output
111
112 def test_rename_to_existing_name_fails(self, repo: pathlib.Path) -> None:
113 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
114 runner.invoke(cli, ["remote", "add", "upstream", "https://hub.muse.io/repos/r2"])
115 result = runner.invoke(cli, ["remote", "rename", "origin", "upstream"])
116 assert result.exit_code != 0
117 assert "already exists" in result.output
118
119
120 # ---------------------------------------------------------------------------
121 # remote list
122 # ---------------------------------------------------------------------------
123
124
125 class TestRemoteList:
126 def test_list_empty(self, repo: pathlib.Path) -> None:
127 result = runner.invoke(cli, ["remote", "list"])
128 assert result.exit_code == 0
129 assert "No remotes" in result.output
130
131 def test_list_shows_names(self, repo: pathlib.Path) -> None:
132 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
133 runner.invoke(cli, ["remote", "add", "upstream", "https://hub.muse.io/repos/r2"])
134 result = runner.invoke(cli, ["remote", "list"])
135 assert result.exit_code == 0
136 assert "origin" in result.output
137 assert "upstream" in result.output
138
139 def test_list_verbose_shows_url(self, repo: pathlib.Path) -> None:
140 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
141 result = runner.invoke(cli, ["remote", "list", "-v"])
142 assert result.exit_code == 0
143 assert "https://hub.muse.io/repos/r1" in result.output
144
145
146 # ---------------------------------------------------------------------------
147 # remote get-url
148 # ---------------------------------------------------------------------------
149
150
151 class TestRemoteGetUrl:
152 def test_get_url_existing(self, repo: pathlib.Path) -> None:
153 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
154 result = runner.invoke(cli, ["remote", "get-url", "origin"])
155 assert result.exit_code == 0
156 assert "https://hub.muse.io/repos/r1" in result.output
157
158 def test_get_url_nonexistent_fails(self, repo: pathlib.Path) -> None:
159 result = runner.invoke(cli, ["remote", "get-url", "ghost"])
160 assert result.exit_code != 0
161 assert "does not exist" in result.output
162
163
164 # ---------------------------------------------------------------------------
165 # remote set-url
166 # ---------------------------------------------------------------------------
167
168
169 class TestRemoteSetUrl:
170 def test_set_url_updates_existing(self, repo: pathlib.Path) -> None:
171 runner.invoke(cli, ["remote", "add", "origin", "https://hub.muse.io/repos/r1"])
172 result = runner.invoke(
173 cli, ["remote", "set-url", "origin", "https://hub.muse.io/repos/r2"]
174 )
175 assert result.exit_code == 0
176 assert get_remote("origin", repo) == "https://hub.muse.io/repos/r2"
177
178 def test_set_url_nonexistent_fails(self, repo: pathlib.Path) -> None:
179 result = runner.invoke(cli, ["remote", "set-url", "ghost", "https://example.com"])
180 assert result.exit_code != 0
181 assert "does not exist" in result.output