forked from safishamsi/graphify
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_codebuddy.py
More file actions
341 lines (274 loc) · 13.5 KB
/
Copy pathtest_codebuddy.py
File metadata and controls
341 lines (274 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"""Tests for graphify codebuddy install / uninstall commands."""
import json
from pathlib import Path
import sys
from unittest.mock import patch
import pytest
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _codebuddy_install_user(tmp_path):
from graphify.__main__ import install
old_cwd = Path.cwd()
try:
import os
os.chdir(tmp_path)
with patch("graphify.__main__.Path.home", return_value=tmp_path):
install(platform="codebuddy")
finally:
import os
os.chdir(old_cwd)
def _skill_path_user(tmp_path):
return tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"
def _skill_path_project(project_dir):
return project_dir / ".codebuddy" / "skills" / "graphify" / "SKILL.md"
def _codebuddy_md_path(project_dir):
return project_dir / "CODEBUDDY.md"
def _settings_path(project_dir):
return project_dir / ".codebuddy" / "settings.json"
# ---------------------------------------------------------------------------
# User-scope install (graphify install --platform codebuddy)
# ---------------------------------------------------------------------------
def test_codebuddy_install_user_creates_skill_file(tmp_path):
"""User-scope install copies skill to ~/.codebuddy/skills/graphify/SKILL.md."""
_codebuddy_install_user(tmp_path)
skill_path = _skill_path_user(tmp_path)
assert skill_path.exists()
def test_codebuddy_skill_file_contains_frontmatter(tmp_path):
"""Installed skill file must include graphify YAML frontmatter."""
_codebuddy_install_user(tmp_path)
content = _skill_path_user(tmp_path).read_text()
assert "name: graphify" in content
assert "description:" in content
def test_codebuddy_skill_file_references_graphify_query(tmp_path):
"""/graphify skill must mention graphify query (query-first policy)."""
_codebuddy_install_user(tmp_path)
content = _skill_path_user(tmp_path).read_text()
assert "graphify query" in content or "/graphify query" in content
# ---------------------------------------------------------------------------
# Project-scope install (graphify codebuddy install)
# ---------------------------------------------------------------------------
def test_codebuddy_install_project_writes_codebuddy_md(tmp_path):
"""Project-scope install writes CODEBUDDY.md with graphify section."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
md = _codebuddy_md_path(tmp_path)
assert md.exists()
content = md.read_text()
assert "## graphify" in content
assert "graphify-out/" in content
def test_codebuddy_install_project_writes_hook(tmp_path):
"""Project-scope install registers PreToolUse hook in .codebuddy/settings.json."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
settings_path = _settings_path(tmp_path)
assert settings_path.exists()
settings = json.loads(settings_path.read_text())
hooks = settings["hooks"]["PreToolUse"]
assert any("graphify" in str(h) for h in hooks)
def test_codebuddy_install_hook_has_bash_matcher(tmp_path):
"""The installed hook must include Bash matcher for code search interception."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
settings = json.loads(_settings_path(tmp_path).read_text())
hooks = settings["hooks"]["PreToolUse"]
bash_hooks = [h for h in hooks if h.get("matcher") == "Bash"]
assert any("graphify" in str(h) for h in bash_hooks)
def test_codebuddy_install_hook_has_read_glob_matcher(tmp_path):
"""The installed hook must include Read|Glob matcher for file-read interception."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
settings = json.loads(_settings_path(tmp_path).read_text())
hooks = settings["hooks"]["PreToolUse"]
read_hooks = [h for h in hooks if h.get("matcher") == "Read|Glob"]
assert any("graphify" in str(h) for h in read_hooks)
def test_codebuddy_install_idempotent(tmp_path):
"""Re-install does not duplicate ## graphify sections."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
codebuddy_install(tmp_path)
md = _codebuddy_md_path(tmp_path)
assert md.read_text().count("## graphify") == 1
def test_codebuddy_install_upgrades_stale_section(tmp_path):
"""Re-install replaces an old graphify section with the current template."""
from graphify.__main__ import codebuddy_install, _CODEBUDDY_MD_MARKER
# Write a stale section manually
md = _codebuddy_md_path(tmp_path)
md.write_text("old content\n\n## graphify\nThis is old instructions\n")
codebuddy_install(tmp_path)
content = md.read_text()
assert _CODEBUDDY_MD_MARKER in content
assert "old content" in content
assert "This is old instructions" not in content
assert "graphify-out/" in content
assert content.count("## graphify") == 1
def test_codebuddy_install_merges_existing_codebuddy_md(tmp_path):
"""Install appends to an existing CODEBUDDY.md, preserving other content."""
from graphify.__main__ import codebuddy_install
_codebuddy_md_path(tmp_path).write_text("# My project rules\n")
codebuddy_install(tmp_path)
content = _codebuddy_md_path(tmp_path).read_text()
assert "# My project rules" in content
assert "## graphify" in content
assert "graphify-out/" in content
def test_codebuddy_install_prints_no_change_on_second_run(tmp_path, capsys):
"""Second install prints '(no change)' when content is identical."""
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
out1 = capsys.readouterr().out
codebuddy_install(tmp_path)
out2 = capsys.readouterr().out
assert "no change" in out2
def test_codebuddy_install_hint_git_add(tmp_path, capsys):
"""Project-scoped install via CLI prints a git add hint."""
from graphify.__main__ import main
home = tmp_path / "home"
project = tmp_path / "project"
project.mkdir()
old_cwd = Path.cwd()
try:
import os
os.chdir(project)
with patch("graphify.__main__.Path.home", return_value=home):
sys.argv = ["graphify", "codebuddy", "install"]
main()
finally:
import os
os.chdir(old_cwd)
# codebuddy_install calls print() directly, no git add hint printed there
# so this test checks that no errors occur
# ---------------------------------------------------------------------------
# Uninstall
# ---------------------------------------------------------------------------
def test_codebuddy_uninstall_removes_section(tmp_path):
"""Uninstall removes the ## graphify section from CODEBUDDY.md."""
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
md = _codebuddy_md_path(tmp_path)
assert not md.exists()
def test_codebuddy_uninstall_removes_hook(tmp_path):
"""Uninstall removes the PreToolUse hook from .codebuddy/settings.json."""
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
settings_path = _settings_path(tmp_path)
if settings_path.exists():
settings = json.loads(settings_path.read_text())
hooks = settings.get("hooks", {}).get("PreToolUse", [])
assert not any("graphify" in str(h) for h in hooks)
def test_codebuddy_uninstall_noop_if_not_installed(tmp_path):
"""Uninstall should not raise when CODEBUDDY.md doesn't exist."""
from graphify.__main__ import codebuddy_uninstall
codebuddy_uninstall(tmp_path) # should not raise
def test_codebuddy_uninstall_noop_if_no_section(tmp_path):
"""Uninstall should not error when CODEBUDDY.md exists but no graphify section."""
from graphify.__main__ import codebuddy_uninstall
_codebuddy_md_path(tmp_path).write_text("# Some other project\n")
codebuddy_uninstall(tmp_path)
content = _codebuddy_md_path(tmp_path).read_text()
assert "# Some other project" in content
def test_codebuddy_uninstall_preserves_other_content(tmp_path):
"""Uninstall preserves non-graphify content in CODEBUDDY.md."""
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
_codebuddy_md_path(tmp_path).write_text("# My project rules\n")
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
# When graphify section was appended, uninstall removes it and the file
# becomes the original content
content = _codebuddy_md_path(tmp_path).read_text()
assert "## graphify" not in content
assert "# My project rules" in content
# ---------------------------------------------------------------------------
# uninstall_all integration
# ---------------------------------------------------------------------------
def test_uninstall_all_removes_codebuddy_md(tmp_path, monkeypatch):
"""graphify uninstall must clean up CODEBUDDY.md."""
from graphify.__main__ import main
home = tmp_path / "home"
project = tmp_path / "project"
project.mkdir()
monkeypatch.chdir(project)
with patch("graphify.__main__.Path.home", return_value=home):
monkeypatch.setattr(sys, "argv", ["graphify", "codebuddy", "install"])
main()
md = _codebuddy_md_path(project)
assert md.exists()
monkeypatch.setattr(sys, "argv", ["graphify", "uninstall"])
main()
assert not md.exists()
def test_uninstall_all_removes_codebuddy_hook(tmp_path, monkeypatch):
"""graphify uninstall must clean up .codebuddy/settings.json hooks."""
from graphify.__main__ import main
home = tmp_path / "home"
project = tmp_path / "project"
project.mkdir()
monkeypatch.chdir(project)
with patch("graphify.__main__.Path.home", return_value=home):
monkeypatch.setattr(sys, "argv", ["graphify", "codebuddy", "install"])
main()
monkeypatch.setattr(sys, "argv", ["graphify", "uninstall"])
main()
settings_path = _settings_path(project)
if settings_path.exists():
settings = json.loads(settings_path.read_text())
hooks = settings.get("hooks", {}).get("PreToolUse", [])
assert not any("graphify" in str(h) for h in hooks)
# ---------------------------------------------------------------------------
# Platform config sanity
# ---------------------------------------------------------------------------
def test_codebuddy_in_platform_config():
"""codebuddy must be registered in _PLATFORM_CONFIG."""
from graphify.__main__ import _PLATFORM_CONFIG
assert "codebuddy" in _PLATFORM_CONFIG
assert _PLATFORM_CONFIG["codebuddy"]["skill_file"] == "skill.md"
assert _PLATFORM_CONFIG["codebuddy"]["claude_md"] is False
def test_codebuddy_platform_skill_destination_user_scope(tmp_path):
"""User-scope destination must be ~/.codebuddy/skills/graphify/SKILL.md."""
from graphify.__main__ import _platform_skill_destination
with patch("graphify.__main__.Path.home", return_value=tmp_path):
dst = _platform_skill_destination("codebuddy", project=False)
assert dst == tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"
def test_codebuddy_platform_skill_destination_project_scope(tmp_path):
"""Project-scope destination must be <project>/.codebuddy/skills/graphify/SKILL.md."""
from graphify.__main__ import _platform_skill_destination
dst = _platform_skill_destination("codebuddy", project=True, project_dir=tmp_path)
assert dst == tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md"
def test_codebuddy_in_main_help_text(capsys, monkeypatch):
"""`graphify --help` must list codebuddy in the platform list and per-platform section."""
from graphify.__main__ import main
monkeypatch.setattr(sys, "argv", ["graphify", "--help"])
main()
captured = capsys.readouterr().out
# codebuddy should appear in the top-level platform list
assert "|codebuddy)" in captured or "codebuddy" in captured, (
"codebuddy missing from `graphify --help` platform list"
)
# codebuddy install / uninstall should appear in the per-platform section
assert "codebuddy install" in captured, "`codebuddy install` line missing from help text"
assert "codebuddy uninstall" in captured, "`codebuddy uninstall` line missing from help text"
def test_codebuddy_skill_file_exists_in_package():
"""skill.md must be present in the installed package (shared with claude)."""
import graphify
skill = Path(graphify.__file__).parent / "skill.md"
assert skill.exists(), "skill.md missing from package"
def test_codebuddy_installation_roundtrip(tmp_path):
"""Install then uninstall leaves no trace of graphify CODEBUDDY.md or hook."""
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
# Pre-existing project file
_codebuddy_md_path(tmp_path).write_text("# My project\n")
# One section above graphify to test cleanup
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
# CODEBUDDY.md should exist with original content only
md = _codebuddy_md_path(tmp_path)
assert md.exists()
content = md.read_text()
assert "## graphify" not in content
assert "# My project" in content
# Hook should be removed
settings_path = _settings_path(tmp_path)
if settings_path.exists():
settings = json.loads(settings_path.read_text())
hooks = settings.get("hooks", {}).get("PreToolUse", [])
assert not any("graphify" in str(h) for h in hooks)