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