cgcardona / muse public
test_core_workspace.py python
200 lines 7.3 KB
e0353dfe feat: muse reflog, gc, archive, bisect, blame, worktree, workspace Gabriel Cardona <cgcardona@gmail.com> 7h ago
1 """Tests for muse/core/workspace.py — multi-repository workspace management."""
2
3 from __future__ import annotations
4
5 import json
6 import pathlib
7
8 import pytest
9
10 from muse.core.workspace import (
11 WorkspaceMemberStatus,
12 add_workspace_member,
13 list_workspace_members,
14 remove_workspace_member,
15 )
16
17
18 # ---------------------------------------------------------------------------
19 # Helpers
20 # ---------------------------------------------------------------------------
21
22
23 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
24 muse = tmp_path / ".muse"
25 for d in ("objects", "commits", "snapshots", "refs/heads"):
26 (muse / d).mkdir(parents=True, exist_ok=True)
27 (muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo"}))
28 (muse / "HEAD").write_text("refs/heads/main\n")
29 (muse / "refs" / "heads" / "main").write_text("0" * 64)
30 return tmp_path
31
32
33 # ---------------------------------------------------------------------------
34 # add_workspace_member
35 # ---------------------------------------------------------------------------
36
37
38 def test_add_member_creates_manifest(tmp_path: pathlib.Path) -> None:
39 repo = _make_repo(tmp_path)
40 add_workspace_member(repo, "core", "https://musehub.ai/acme/core")
41 manifest_path = repo / ".muse" / "workspace.toml"
42 assert manifest_path.exists()
43
44
45 def test_add_member_stores_name_and_url(tmp_path: pathlib.Path) -> None:
46 repo = _make_repo(tmp_path)
47 add_workspace_member(repo, "sounds", "https://musehub.ai/acme/sounds")
48 members = list_workspace_members(repo)
49 assert len(members) == 1
50 assert members[0].name == "sounds"
51 assert members[0].url == "https://musehub.ai/acme/sounds"
52
53
54 def test_add_member_default_path(tmp_path: pathlib.Path) -> None:
55 repo = _make_repo(tmp_path)
56 add_workspace_member(repo, "core", "https://example.com/core")
57 members = list_workspace_members(repo)
58 assert "repos/core" in str(members[0].path)
59
60
61 def test_add_member_custom_path(tmp_path: pathlib.Path) -> None:
62 repo = _make_repo(tmp_path)
63 add_workspace_member(repo, "core", "https://example.com/core", path="vendor/core")
64 members = list_workspace_members(repo)
65 assert "vendor/core" in str(members[0].path)
66
67
68 def test_add_member_default_branch_is_main(tmp_path: pathlib.Path) -> None:
69 repo = _make_repo(tmp_path)
70 add_workspace_member(repo, "core", "https://example.com/core")
71 members = list_workspace_members(repo)
72 assert members[0].branch == "main"
73
74
75 def test_add_member_custom_branch(tmp_path: pathlib.Path) -> None:
76 repo = _make_repo(tmp_path)
77 add_workspace_member(repo, "data", "https://example.com/data", branch="v2")
78 members = list_workspace_members(repo)
79 assert members[0].branch == "v2"
80
81
82 def test_add_duplicate_member_raises(tmp_path: pathlib.Path) -> None:
83 repo = _make_repo(tmp_path)
84 add_workspace_member(repo, "core", "https://example.com/core")
85 with pytest.raises(ValueError, match="already exists"):
86 add_workspace_member(repo, "core", "https://example.com/other")
87
88
89 def test_add_multiple_members(tmp_path: pathlib.Path) -> None:
90 repo = _make_repo(tmp_path)
91 add_workspace_member(repo, "core", "https://example.com/core")
92 add_workspace_member(repo, "sounds", "https://example.com/sounds")
93 add_workspace_member(repo, "docs", "https://example.com/docs")
94 members = list_workspace_members(repo)
95 assert len(members) == 3
96
97
98 # ---------------------------------------------------------------------------
99 # remove_workspace_member
100 # ---------------------------------------------------------------------------
101
102
103 def test_remove_member_removes_from_manifest(tmp_path: pathlib.Path) -> None:
104 repo = _make_repo(tmp_path)
105 add_workspace_member(repo, "core", "https://example.com/core")
106 remove_workspace_member(repo, "core")
107 members = list_workspace_members(repo)
108 assert len(members) == 0
109
110
111 def test_remove_nonexistent_member_raises(tmp_path: pathlib.Path) -> None:
112 repo = _make_repo(tmp_path)
113 # First add a member so the manifest exists, then try to remove a nonexistent one.
114 add_workspace_member(repo, "core", "https://example.com/core")
115 with pytest.raises(ValueError, match="not found"):
116 remove_workspace_member(repo, "nonexistent")
117
118
119 def test_remove_no_manifest_raises(tmp_path: pathlib.Path) -> None:
120 repo = _make_repo(tmp_path)
121 with pytest.raises(ValueError, match="No workspace manifest"):
122 remove_workspace_member(repo, "anything")
123
124
125 def test_remove_only_removes_named_member(tmp_path: pathlib.Path) -> None:
126 repo = _make_repo(tmp_path)
127 add_workspace_member(repo, "core", "https://example.com/core")
128 add_workspace_member(repo, "sounds", "https://example.com/sounds")
129 remove_workspace_member(repo, "core")
130 members = list_workspace_members(repo)
131 assert len(members) == 1
132 assert members[0].name == "sounds"
133
134
135 # ---------------------------------------------------------------------------
136 # list_workspace_members
137 # ---------------------------------------------------------------------------
138
139
140 def test_list_returns_empty_when_no_manifest(tmp_path: pathlib.Path) -> None:
141 repo = _make_repo(tmp_path)
142 assert list_workspace_members(repo) == []
143
144
145 def test_list_present_false_when_not_cloned(tmp_path: pathlib.Path) -> None:
146 repo = _make_repo(tmp_path)
147 add_workspace_member(repo, "core", "https://example.com/core")
148 members = list_workspace_members(repo)
149 assert members[0].present is False
150
151
152 def test_list_present_true_when_cloned(tmp_path: pathlib.Path) -> None:
153 repo = _make_repo(tmp_path)
154 add_workspace_member(repo, "local", str(tmp_path / "local_clone"), path="local_clone")
155 # Simulate a cloned repo at the expected path.
156 clone_path = tmp_path / "local_clone"
157 (clone_path / ".muse").mkdir(parents=True, exist_ok=True)
158 members = list_workspace_members(repo)
159 assert members[0].present is True
160
161
162 def test_list_head_none_when_not_cloned(tmp_path: pathlib.Path) -> None:
163 repo = _make_repo(tmp_path)
164 add_workspace_member(repo, "core", "https://example.com/core")
165 members = list_workspace_members(repo)
166 assert members[0].head_commit is None
167
168
169 def test_list_returns_workspace_member_status(tmp_path: pathlib.Path) -> None:
170 repo = _make_repo(tmp_path)
171 add_workspace_member(repo, "core", "https://example.com/core")
172 members = list_workspace_members(repo)
173 assert isinstance(members[0], WorkspaceMemberStatus)
174
175
176 # ---------------------------------------------------------------------------
177 # Stress
178 # ---------------------------------------------------------------------------
179
180
181 def test_stress_50_members(tmp_path: pathlib.Path) -> None:
182 """Adding 50 members should all be preserved and listed correctly."""
183 repo = _make_repo(tmp_path)
184 for i in range(50):
185 add_workspace_member(repo, f"svc{i}", f"https://example.com/svc{i}")
186 members = list_workspace_members(repo)
187 assert len(members) == 50
188 names = {m.name for m in members}
189 for i in range(50):
190 assert f"svc{i}" in names
191
192
193 def test_stress_add_remove_cycle(tmp_path: pathlib.Path) -> None:
194 """Add and remove 20 members; manifest should be empty at the end."""
195 repo = _make_repo(tmp_path)
196 for i in range(20):
197 add_workspace_member(repo, f"repo{i}", f"https://example.com/repo{i}")
198 for i in range(20):
199 remove_workspace_member(repo, f"repo{i}")
200 assert list_workspace_members(repo) == []