|
1
|
"""Tests for editor integration — EditorIntegration class and CLI command.""" |
|
2
|
|
|
3
|
import json |
|
4
|
from pathlib import Path |
|
5
|
from unittest.mock import MagicMock, patch |
|
6
|
|
|
7
|
import pytest |
|
8
|
from click.testing import CliRunner |
|
9
|
|
|
10
|
from navegador.cli.commands import main |
|
11
|
from navegador.editor import SUPPORTED_EDITORS, EditorIntegration |
|
12
|
|
|
13
|
# ── EditorIntegration unit tests ────────────────────────────────────────────── |
|
14
|
|
|
15
|
|
|
16
|
class TestEditorIntegration: |
|
17
|
def setup_method(self): |
|
18
|
self.integration = EditorIntegration(db=".navegador/graph.db") |
|
19
|
|
|
20
|
# config_for |
|
21
|
|
|
22
|
def test_config_for_claude_code(self): |
|
23
|
cfg = self.integration.config_for("claude-code") |
|
24
|
assert cfg["mcpServers"]["navegador"]["command"] == "navegador" |
|
25
|
assert cfg["mcpServers"]["navegador"]["args"] == ["mcp", "--db", ".navegador/graph.db"] |
|
26
|
|
|
27
|
def test_config_for_cursor(self): |
|
28
|
cfg = self.integration.config_for("cursor") |
|
29
|
assert cfg["mcpServers"]["navegador"]["command"] == "navegador" |
|
30
|
assert cfg["mcpServers"]["navegador"]["args"] == ["mcp", "--db", ".navegador/graph.db"] |
|
31
|
|
|
32
|
def test_config_for_codex(self): |
|
33
|
cfg = self.integration.config_for("codex") |
|
34
|
assert cfg["mcpServers"]["navegador"]["command"] == "navegador" |
|
35
|
assert cfg["mcpServers"]["navegador"]["args"] == ["mcp", "--db", ".navegador/graph.db"] |
|
36
|
|
|
37
|
def test_config_for_windsurf(self): |
|
38
|
cfg = self.integration.config_for("windsurf") |
|
39
|
assert cfg["mcpServers"]["navegador"]["command"] == "navegador" |
|
40
|
assert cfg["mcpServers"]["navegador"]["args"] == ["mcp", "--db", ".navegador/graph.db"] |
|
41
|
|
|
42
|
def test_config_for_invalid_editor_raises(self): |
|
43
|
with pytest.raises(ValueError, match="Unsupported editor"): |
|
44
|
self.integration.config_for("vscode") |
|
45
|
|
|
46
|
# custom db path |
|
47
|
|
|
48
|
def test_custom_db_path_reflected_in_config(self): |
|
49
|
integration = EditorIntegration(db="/custom/path/graph.db") |
|
50
|
cfg = integration.config_for("cursor") |
|
51
|
assert cfg["mcpServers"]["navegador"]["args"][2] == "/custom/path/graph.db" |
|
52
|
|
|
53
|
# config_json |
|
54
|
|
|
55
|
def test_config_json_is_valid_json(self): |
|
56
|
raw = self.integration.config_json("claude-code") |
|
57
|
parsed = json.loads(raw) |
|
58
|
assert "mcpServers" in parsed |
|
59
|
|
|
60
|
def test_config_json_is_pretty_printed(self): |
|
61
|
raw = self.integration.config_json("cursor") |
|
62
|
assert "\n" in raw # indented |
|
63
|
|
|
64
|
# config_path |
|
65
|
|
|
66
|
def test_config_path_claude_code(self): |
|
67
|
assert self.integration.config_path("claude-code") == ".claude/mcp.json" |
|
68
|
|
|
69
|
def test_config_path_cursor(self): |
|
70
|
assert self.integration.config_path("cursor") == ".cursor/mcp.json" |
|
71
|
|
|
72
|
def test_config_path_codex(self): |
|
73
|
assert self.integration.config_path("codex") == ".codex/config.json" |
|
74
|
|
|
75
|
def test_config_path_windsurf(self): |
|
76
|
assert self.integration.config_path("windsurf") == ".windsurf/mcp.json" |
|
77
|
|
|
78
|
def test_config_path_invalid_editor_raises(self): |
|
79
|
with pytest.raises(ValueError, match="Unsupported editor"): |
|
80
|
self.integration.config_path("sublime") |
|
81
|
|
|
82
|
# write_config |
|
83
|
|
|
84
|
def test_write_config_creates_file(self, tmp_path): |
|
85
|
written = self.integration.write_config("claude-code", base_dir=str(tmp_path)) |
|
86
|
assert written.exists() |
|
87
|
parsed = json.loads(written.read_text()) |
|
88
|
assert "mcpServers" in parsed |
|
89
|
|
|
90
|
def test_write_config_creates_parent_dirs(self, tmp_path): |
|
91
|
written = self.integration.write_config("windsurf", base_dir=str(tmp_path)) |
|
92
|
assert (tmp_path / ".windsurf").is_dir() |
|
93
|
assert written.name == "mcp.json" |
|
94
|
|
|
95
|
def test_write_config_returns_path_object(self, tmp_path): |
|
96
|
result = self.integration.write_config("cursor", base_dir=str(tmp_path)) |
|
97
|
assert isinstance(result, Path) |
|
98
|
|
|
99
|
def test_write_config_content_matches_config_json(self, tmp_path): |
|
100
|
written = self.integration.write_config("codex", base_dir=str(tmp_path)) |
|
101
|
assert written.read_text() == self.integration.config_json("codex") |
|
102
|
|
|
103
|
# all editors covered |
|
104
|
|
|
105
|
def test_all_editors_supported(self): |
|
106
|
for ed in SUPPORTED_EDITORS: |
|
107
|
cfg = self.integration.config_for(ed) |
|
108
|
assert "mcpServers" in cfg |
|
109
|
|
|
110
|
|
|
111
|
# ── CLI tests ───────────────────────────────────────────────────────────────── |
|
112
|
|
|
113
|
|
|
114
|
class TestEditorSetupCommand: |
|
115
|
# Basic output |
|
116
|
|
|
117
|
def test_claude_code_outputs_json(self): |
|
118
|
runner = CliRunner() |
|
119
|
result = runner.invoke(main, ["editor", "setup", "claude-code"]) |
|
120
|
assert result.exit_code == 0 |
|
121
|
parsed = json.loads(result.output) |
|
122
|
assert "mcpServers" in parsed |
|
123
|
assert parsed["mcpServers"]["navegador"]["command"] == "navegador" |
|
124
|
|
|
125
|
def test_cursor_outputs_json(self): |
|
126
|
runner = CliRunner() |
|
127
|
result = runner.invoke(main, ["editor", "setup", "cursor"]) |
|
128
|
assert result.exit_code == 0 |
|
129
|
parsed = json.loads(result.output) |
|
130
|
assert "mcpServers" in parsed |
|
131
|
|
|
132
|
def test_codex_outputs_json(self): |
|
133
|
runner = CliRunner() |
|
134
|
result = runner.invoke(main, ["editor", "setup", "codex"]) |
|
135
|
assert result.exit_code == 0 |
|
136
|
parsed = json.loads(result.output) |
|
137
|
assert "mcpServers" in parsed |
|
138
|
|
|
139
|
def test_windsurf_outputs_json(self): |
|
140
|
runner = CliRunner() |
|
141
|
result = runner.invoke(main, ["editor", "setup", "windsurf"]) |
|
142
|
assert result.exit_code == 0 |
|
143
|
parsed = json.loads(result.output) |
|
144
|
assert "mcpServers" in parsed |
|
145
|
|
|
146
|
# --db option |
|
147
|
|
|
148
|
def test_custom_db_reflected_in_output(self): |
|
149
|
runner = CliRunner() |
|
150
|
result = runner.invoke(main, ["editor", "setup", "cursor", "--db", "/custom/graph.db"]) |
|
151
|
assert result.exit_code == 0 |
|
152
|
parsed = json.loads(result.output) |
|
153
|
assert parsed["mcpServers"]["navegador"]["args"][2] == "/custom/graph.db" |
|
154
|
|
|
155
|
# 'all' generates for all editors |
|
156
|
|
|
157
|
def test_all_generates_for_all_editors(self): |
|
158
|
runner = CliRunner() |
|
159
|
result = runner.invoke(main, ["editor", "setup", "all"]) |
|
160
|
assert result.exit_code == 0 |
|
161
|
# Each editor name should appear in the output header |
|
162
|
for ed in SUPPORTED_EDITORS: |
|
163
|
assert ed in result.output |
|
164
|
|
|
165
|
def test_all_output_contains_multiple_json_blocks(self): |
|
166
|
runner = CliRunner() |
|
167
|
result = runner.invoke(main, ["editor", "setup", "all"]) |
|
168
|
assert result.exit_code == 0 |
|
169
|
# Count occurrences of "mcpServers" — one per editor |
|
170
|
assert result.output.count("mcpServers") == len(SUPPORTED_EDITORS) |
|
171
|
|
|
172
|
# Invalid editor name |
|
173
|
|
|
174
|
def test_invalid_editor_exits_nonzero(self): |
|
175
|
runner = CliRunner() |
|
176
|
result = runner.invoke(main, ["editor", "setup", "vscode"]) |
|
177
|
assert result.exit_code != 0 |
|
178
|
|
|
179
|
def test_invalid_editor_shows_error(self): |
|
180
|
runner = CliRunner() |
|
181
|
result = runner.invoke(main, ["editor", "setup", "vim"]) |
|
182
|
assert "vim" in result.output or "vim" in (result.exception or "") |
|
183
|
|
|
184
|
# --write flag |
|
185
|
|
|
186
|
def test_write_flag_creates_file(self): |
|
187
|
runner = CliRunner() |
|
188
|
with runner.isolated_filesystem(): |
|
189
|
result = runner.invoke(main, ["editor", "setup", "claude-code", "--write"]) |
|
190
|
assert result.exit_code == 0 |
|
191
|
written = Path(".claude/mcp.json") |
|
192
|
assert written.exists() |
|
193
|
parsed = json.loads(written.read_text()) |
|
194
|
assert "mcpServers" in parsed |
|
195
|
|
|
196
|
def test_write_flag_cursor_creates_correct_file(self): |
|
197
|
runner = CliRunner() |
|
198
|
with runner.isolated_filesystem(): |
|
199
|
result = runner.invoke(main, ["editor", "setup", "cursor", "--write"]) |
|
200
|
assert result.exit_code == 0 |
|
201
|
assert Path(".cursor/mcp.json").exists() |
|
202
|
|
|
203
|
def test_write_flag_all_creates_all_files(self): |
|
204
|
runner = CliRunner() |
|
205
|
with runner.isolated_filesystem(): |
|
206
|
result = runner.invoke(main, ["editor", "setup", "all", "--write"]) |
|
207
|
assert result.exit_code == 0 |
|
208
|
assert Path(".claude/mcp.json").exists() |
|
209
|
assert Path(".cursor/mcp.json").exists() |
|
210
|
assert Path(".codex/config.json").exists() |
|
211
|
assert Path(".windsurf/mcp.json").exists() |
|
212
|
|
|
213
|
def test_write_flag_shows_written_path(self): |
|
214
|
runner = CliRunner() |
|
215
|
with runner.isolated_filesystem(): |
|
216
|
result = runner.invoke(main, ["editor", "setup", "windsurf", "--write"]) |
|
217
|
assert result.exit_code == 0 |
|
218
|
assert "Written" in result.output or ".windsurf" in result.output |
|
219
|
|