hud-python 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/cli/tests/test_cursor.py
CHANGED
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
"""Tests for hud.cli.cursor module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from unittest.mock import mock_open, patch
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
|
|
12
|
-
from hud.cli.cursor import get_cursor_config_path, list_cursor_servers, parse_cursor_config
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestParseCursorConfig:
|
|
16
|
-
"""Test Cursor config parsing."""
|
|
17
|
-
|
|
18
|
-
def test_parse_cursor_config_success(self) -> None:
|
|
19
|
-
"""Test successful parsing of Cursor config."""
|
|
20
|
-
config_data = {
|
|
21
|
-
"mcpServers": {
|
|
22
|
-
"test-server": {
|
|
23
|
-
"command": "python",
|
|
24
|
-
"args": ["server.py", "--port", "8080"],
|
|
25
|
-
"env": {"KEY": "value"},
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
with (
|
|
31
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
32
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
33
|
-
):
|
|
34
|
-
command, error = parse_cursor_config("test-server")
|
|
35
|
-
assert error is None
|
|
36
|
-
assert command == ["python", "server.py", "--port", "8080"]
|
|
37
|
-
|
|
38
|
-
def test_parse_cursor_config_not_found(self) -> None:
|
|
39
|
-
"""Test parsing when config file doesn't exist."""
|
|
40
|
-
with patch("pathlib.Path.exists", return_value=False):
|
|
41
|
-
command, error = parse_cursor_config("test-server")
|
|
42
|
-
assert command is None
|
|
43
|
-
assert error is not None
|
|
44
|
-
assert "Cursor config not found" in error
|
|
45
|
-
|
|
46
|
-
def test_parse_cursor_config_server_not_found(self) -> None:
|
|
47
|
-
"""Test parsing when server doesn't exist in config."""
|
|
48
|
-
config_data = {"mcpServers": {"other-server": {"command": "node", "args": ["server.js"]}}}
|
|
49
|
-
|
|
50
|
-
with (
|
|
51
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
52
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
53
|
-
):
|
|
54
|
-
command, error = parse_cursor_config("test-server")
|
|
55
|
-
assert command is None
|
|
56
|
-
assert error is not None
|
|
57
|
-
assert "Server 'test-server' not found" in error
|
|
58
|
-
assert "Available: other-server" in error
|
|
59
|
-
|
|
60
|
-
def test_parse_cursor_config_reloaderoo(self) -> None:
|
|
61
|
-
"""Test parsing config with reloaderoo wrapper."""
|
|
62
|
-
config_data = {
|
|
63
|
-
"mcpServers": {
|
|
64
|
-
"test-server": {
|
|
65
|
-
"command": "npx",
|
|
66
|
-
"args": ["reloaderoo", "--watch", "src", "--", "python", "server.py"],
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
with (
|
|
72
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
73
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
74
|
-
):
|
|
75
|
-
command, error = parse_cursor_config("test-server")
|
|
76
|
-
assert error is None
|
|
77
|
-
# Should extract command after --
|
|
78
|
-
assert command == ["python", "server.py"]
|
|
79
|
-
|
|
80
|
-
def test_parse_cursor_config_reloaderoo_no_dash(self) -> None:
|
|
81
|
-
"""Test parsing reloaderoo without -- separator."""
|
|
82
|
-
config_data = {
|
|
83
|
-
"mcpServers": {
|
|
84
|
-
"test-server": {"command": "npx", "args": ["reloaderoo", "python", "server.py"]}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
with (
|
|
89
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
90
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
91
|
-
):
|
|
92
|
-
command, error = parse_cursor_config("test-server")
|
|
93
|
-
assert error is None
|
|
94
|
-
# Should return full command since no -- found
|
|
95
|
-
assert command == ["npx", "reloaderoo", "python", "server.py"]
|
|
96
|
-
|
|
97
|
-
def test_parse_cursor_config_windows_path(self) -> None:
|
|
98
|
-
"""Test parsing with Windows user profile path."""
|
|
99
|
-
config_data = {"mcpServers": {"test": {"command": "cmd"}}}
|
|
100
|
-
|
|
101
|
-
# First path doesn't exist, try Windows path
|
|
102
|
-
with (
|
|
103
|
-
patch("pathlib.Path.exists", side_effect=[False, True]),
|
|
104
|
-
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
105
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
106
|
-
):
|
|
107
|
-
command, error = parse_cursor_config("test")
|
|
108
|
-
assert error is None
|
|
109
|
-
assert command == ["cmd"]
|
|
110
|
-
|
|
111
|
-
def test_parse_cursor_config_json_error(self) -> None:
|
|
112
|
-
"""Test parsing with invalid JSON."""
|
|
113
|
-
with (
|
|
114
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
115
|
-
patch("builtins.open", mock_open(read_data="invalid json")),
|
|
116
|
-
):
|
|
117
|
-
command, error = parse_cursor_config("test-server")
|
|
118
|
-
assert command is None
|
|
119
|
-
assert error is not None
|
|
120
|
-
assert "Error reading config" in error
|
|
121
|
-
|
|
122
|
-
def test_parse_cursor_config_no_command(self) -> None:
|
|
123
|
-
"""Test parsing server with no command."""
|
|
124
|
-
config_data = {"mcpServers": {"test-server": {"args": ["--port", "8080"]}}}
|
|
125
|
-
|
|
126
|
-
with (
|
|
127
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
128
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
129
|
-
):
|
|
130
|
-
command, error = parse_cursor_config("test-server")
|
|
131
|
-
assert error is None
|
|
132
|
-
assert command == ["", "--port", "8080"] # Empty command
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class TestListCursorServers:
|
|
136
|
-
"""Test listing Cursor servers."""
|
|
137
|
-
|
|
138
|
-
def test_list_cursor_servers_success(self) -> None:
|
|
139
|
-
"""Test successful listing of servers."""
|
|
140
|
-
config_data = {
|
|
141
|
-
"mcpServers": {
|
|
142
|
-
"server1": {"command": "python"},
|
|
143
|
-
"server2": {"command": "node"},
|
|
144
|
-
"server3": {"command": "ruby"},
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
with (
|
|
149
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
150
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
151
|
-
):
|
|
152
|
-
servers, error = list_cursor_servers()
|
|
153
|
-
assert error is None
|
|
154
|
-
assert servers == ["server1", "server2", "server3"]
|
|
155
|
-
|
|
156
|
-
def test_list_cursor_servers_empty(self) -> None:
|
|
157
|
-
"""Test listing when no servers configured."""
|
|
158
|
-
config_data = {"mcpServers": {}}
|
|
159
|
-
|
|
160
|
-
with (
|
|
161
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
162
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
163
|
-
):
|
|
164
|
-
servers, error = list_cursor_servers()
|
|
165
|
-
assert error is None
|
|
166
|
-
assert servers == []
|
|
167
|
-
|
|
168
|
-
def test_list_cursor_servers_no_mcp_section(self) -> None:
|
|
169
|
-
"""Test listing when mcpServers section missing."""
|
|
170
|
-
config_data = {"otherConfig": {}}
|
|
171
|
-
|
|
172
|
-
with (
|
|
173
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
174
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
175
|
-
):
|
|
176
|
-
servers, error = list_cursor_servers()
|
|
177
|
-
assert error is None
|
|
178
|
-
assert servers == []
|
|
179
|
-
|
|
180
|
-
def test_list_cursor_servers_file_not_found(self) -> None:
|
|
181
|
-
"""Test listing when config file doesn't exist."""
|
|
182
|
-
with patch("pathlib.Path.exists", return_value=False):
|
|
183
|
-
servers, error = list_cursor_servers()
|
|
184
|
-
assert servers is None
|
|
185
|
-
assert error is not None
|
|
186
|
-
assert "Cursor config not found" in error
|
|
187
|
-
|
|
188
|
-
def test_list_cursor_servers_windows_path(self) -> None:
|
|
189
|
-
"""Test listing with Windows path fallback."""
|
|
190
|
-
config_data = {"mcpServers": {"winserver": {"command": "cmd"}}}
|
|
191
|
-
|
|
192
|
-
# First path doesn't exist, second (Windows) does
|
|
193
|
-
with (
|
|
194
|
-
patch("pathlib.Path.exists", side_effect=[False, True]),
|
|
195
|
-
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
196
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
197
|
-
):
|
|
198
|
-
servers, error = list_cursor_servers()
|
|
199
|
-
assert error is None
|
|
200
|
-
assert servers == ["winserver"]
|
|
201
|
-
|
|
202
|
-
def test_list_cursor_servers_read_error(self) -> None:
|
|
203
|
-
"""Test listing with file read error."""
|
|
204
|
-
with (
|
|
205
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
206
|
-
patch("builtins.open", side_effect=PermissionError("Access denied")),
|
|
207
|
-
):
|
|
208
|
-
servers, error = list_cursor_servers()
|
|
209
|
-
assert servers is None
|
|
210
|
-
assert error is not None
|
|
211
|
-
assert "Error reading config" in error
|
|
212
|
-
assert "Access denied" in error
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
class TestGetCursorConfigPath:
|
|
216
|
-
"""Test getting Cursor config path."""
|
|
217
|
-
|
|
218
|
-
def test_get_cursor_config_path_unix(self) -> None:
|
|
219
|
-
"""Test getting config path on Unix-like systems."""
|
|
220
|
-
with (
|
|
221
|
-
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
222
|
-
patch("pathlib.Path.exists", return_value=True),
|
|
223
|
-
):
|
|
224
|
-
path = get_cursor_config_path()
|
|
225
|
-
assert str(path) == str(Path("/home/user/.cursor/mcp.json"))
|
|
226
|
-
|
|
227
|
-
def test_get_cursor_config_path_windows(self) -> None:
|
|
228
|
-
"""Test getting config path on Windows."""
|
|
229
|
-
with (
|
|
230
|
-
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
231
|
-
patch("pathlib.Path.exists", return_value=False),
|
|
232
|
-
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
233
|
-
):
|
|
234
|
-
path = get_cursor_config_path()
|
|
235
|
-
assert "Test" in str(path)
|
|
236
|
-
assert ".cursor" in str(path)
|
|
237
|
-
assert "mcp.json" in str(path)
|
|
238
|
-
|
|
239
|
-
def test_get_cursor_config_path_no_userprofile(self) -> None:
|
|
240
|
-
"""Test getting config path when USERPROFILE not set."""
|
|
241
|
-
with (
|
|
242
|
-
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
243
|
-
patch("pathlib.Path.exists", return_value=False),
|
|
244
|
-
patch.dict(os.environ, {}, clear=True),
|
|
245
|
-
):
|
|
246
|
-
path = get_cursor_config_path()
|
|
247
|
-
# Should still return something based on empty USERPROFILE
|
|
248
|
-
assert ".cursor" in str(path)
|
|
249
|
-
assert "mcp.json" in str(path)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if __name__ == "__main__":
|
|
253
|
-
pytest.main([__file__])
|
|
1
|
+
"""Tests for hud.cli.cursor module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import mock_open, patch
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from hud.cli.cursor import get_cursor_config_path, list_cursor_servers, parse_cursor_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestParseCursorConfig:
|
|
16
|
+
"""Test Cursor config parsing."""
|
|
17
|
+
|
|
18
|
+
def test_parse_cursor_config_success(self) -> None:
|
|
19
|
+
"""Test successful parsing of Cursor config."""
|
|
20
|
+
config_data = {
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"test-server": {
|
|
23
|
+
"command": "python",
|
|
24
|
+
"args": ["server.py", "--port", "8080"],
|
|
25
|
+
"env": {"KEY": "value"},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
with (
|
|
31
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
32
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
33
|
+
):
|
|
34
|
+
command, error = parse_cursor_config("test-server")
|
|
35
|
+
assert error is None
|
|
36
|
+
assert command == ["python", "server.py", "--port", "8080"]
|
|
37
|
+
|
|
38
|
+
def test_parse_cursor_config_not_found(self) -> None:
|
|
39
|
+
"""Test parsing when config file doesn't exist."""
|
|
40
|
+
with patch("pathlib.Path.exists", return_value=False):
|
|
41
|
+
command, error = parse_cursor_config("test-server")
|
|
42
|
+
assert command is None
|
|
43
|
+
assert error is not None
|
|
44
|
+
assert "Cursor config not found" in error
|
|
45
|
+
|
|
46
|
+
def test_parse_cursor_config_server_not_found(self) -> None:
|
|
47
|
+
"""Test parsing when server doesn't exist in config."""
|
|
48
|
+
config_data = {"mcpServers": {"other-server": {"command": "node", "args": ["server.js"]}}}
|
|
49
|
+
|
|
50
|
+
with (
|
|
51
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
52
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
53
|
+
):
|
|
54
|
+
command, error = parse_cursor_config("test-server")
|
|
55
|
+
assert command is None
|
|
56
|
+
assert error is not None
|
|
57
|
+
assert "Server 'test-server' not found" in error
|
|
58
|
+
assert "Available: other-server" in error
|
|
59
|
+
|
|
60
|
+
def test_parse_cursor_config_reloaderoo(self) -> None:
|
|
61
|
+
"""Test parsing config with reloaderoo wrapper."""
|
|
62
|
+
config_data = {
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"test-server": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["reloaderoo", "--watch", "src", "--", "python", "server.py"],
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
with (
|
|
72
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
73
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
74
|
+
):
|
|
75
|
+
command, error = parse_cursor_config("test-server")
|
|
76
|
+
assert error is None
|
|
77
|
+
# Should extract command after --
|
|
78
|
+
assert command == ["python", "server.py"]
|
|
79
|
+
|
|
80
|
+
def test_parse_cursor_config_reloaderoo_no_dash(self) -> None:
|
|
81
|
+
"""Test parsing reloaderoo without -- separator."""
|
|
82
|
+
config_data = {
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"test-server": {"command": "npx", "args": ["reloaderoo", "python", "server.py"]}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
with (
|
|
89
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
90
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
91
|
+
):
|
|
92
|
+
command, error = parse_cursor_config("test-server")
|
|
93
|
+
assert error is None
|
|
94
|
+
# Should return full command since no -- found
|
|
95
|
+
assert command == ["npx", "reloaderoo", "python", "server.py"]
|
|
96
|
+
|
|
97
|
+
def test_parse_cursor_config_windows_path(self) -> None:
|
|
98
|
+
"""Test parsing with Windows user profile path."""
|
|
99
|
+
config_data = {"mcpServers": {"test": {"command": "cmd"}}}
|
|
100
|
+
|
|
101
|
+
# First path doesn't exist, try Windows path
|
|
102
|
+
with (
|
|
103
|
+
patch("pathlib.Path.exists", side_effect=[False, True]),
|
|
104
|
+
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
105
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
106
|
+
):
|
|
107
|
+
command, error = parse_cursor_config("test")
|
|
108
|
+
assert error is None
|
|
109
|
+
assert command == ["cmd"]
|
|
110
|
+
|
|
111
|
+
def test_parse_cursor_config_json_error(self) -> None:
|
|
112
|
+
"""Test parsing with invalid JSON."""
|
|
113
|
+
with (
|
|
114
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
115
|
+
patch("builtins.open", mock_open(read_data="invalid json")),
|
|
116
|
+
):
|
|
117
|
+
command, error = parse_cursor_config("test-server")
|
|
118
|
+
assert command is None
|
|
119
|
+
assert error is not None
|
|
120
|
+
assert "Error reading config" in error
|
|
121
|
+
|
|
122
|
+
def test_parse_cursor_config_no_command(self) -> None:
|
|
123
|
+
"""Test parsing server with no command."""
|
|
124
|
+
config_data = {"mcpServers": {"test-server": {"args": ["--port", "8080"]}}}
|
|
125
|
+
|
|
126
|
+
with (
|
|
127
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
128
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
129
|
+
):
|
|
130
|
+
command, error = parse_cursor_config("test-server")
|
|
131
|
+
assert error is None
|
|
132
|
+
assert command == ["", "--port", "8080"] # Empty command
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestListCursorServers:
|
|
136
|
+
"""Test listing Cursor servers."""
|
|
137
|
+
|
|
138
|
+
def test_list_cursor_servers_success(self) -> None:
|
|
139
|
+
"""Test successful listing of servers."""
|
|
140
|
+
config_data = {
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"server1": {"command": "python"},
|
|
143
|
+
"server2": {"command": "node"},
|
|
144
|
+
"server3": {"command": "ruby"},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
with (
|
|
149
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
150
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
151
|
+
):
|
|
152
|
+
servers, error = list_cursor_servers()
|
|
153
|
+
assert error is None
|
|
154
|
+
assert servers == ["server1", "server2", "server3"]
|
|
155
|
+
|
|
156
|
+
def test_list_cursor_servers_empty(self) -> None:
|
|
157
|
+
"""Test listing when no servers configured."""
|
|
158
|
+
config_data = {"mcpServers": {}}
|
|
159
|
+
|
|
160
|
+
with (
|
|
161
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
162
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
163
|
+
):
|
|
164
|
+
servers, error = list_cursor_servers()
|
|
165
|
+
assert error is None
|
|
166
|
+
assert servers == []
|
|
167
|
+
|
|
168
|
+
def test_list_cursor_servers_no_mcp_section(self) -> None:
|
|
169
|
+
"""Test listing when mcpServers section missing."""
|
|
170
|
+
config_data = {"otherConfig": {}}
|
|
171
|
+
|
|
172
|
+
with (
|
|
173
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
174
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
175
|
+
):
|
|
176
|
+
servers, error = list_cursor_servers()
|
|
177
|
+
assert error is None
|
|
178
|
+
assert servers == []
|
|
179
|
+
|
|
180
|
+
def test_list_cursor_servers_file_not_found(self) -> None:
|
|
181
|
+
"""Test listing when config file doesn't exist."""
|
|
182
|
+
with patch("pathlib.Path.exists", return_value=False):
|
|
183
|
+
servers, error = list_cursor_servers()
|
|
184
|
+
assert servers is None
|
|
185
|
+
assert error is not None
|
|
186
|
+
assert "Cursor config not found" in error
|
|
187
|
+
|
|
188
|
+
def test_list_cursor_servers_windows_path(self) -> None:
|
|
189
|
+
"""Test listing with Windows path fallback."""
|
|
190
|
+
config_data = {"mcpServers": {"winserver": {"command": "cmd"}}}
|
|
191
|
+
|
|
192
|
+
# First path doesn't exist, second (Windows) does
|
|
193
|
+
with (
|
|
194
|
+
patch("pathlib.Path.exists", side_effect=[False, True]),
|
|
195
|
+
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
196
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
197
|
+
):
|
|
198
|
+
servers, error = list_cursor_servers()
|
|
199
|
+
assert error is None
|
|
200
|
+
assert servers == ["winserver"]
|
|
201
|
+
|
|
202
|
+
def test_list_cursor_servers_read_error(self) -> None:
|
|
203
|
+
"""Test listing with file read error."""
|
|
204
|
+
with (
|
|
205
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
206
|
+
patch("builtins.open", side_effect=PermissionError("Access denied")),
|
|
207
|
+
):
|
|
208
|
+
servers, error = list_cursor_servers()
|
|
209
|
+
assert servers is None
|
|
210
|
+
assert error is not None
|
|
211
|
+
assert "Error reading config" in error
|
|
212
|
+
assert "Access denied" in error
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TestGetCursorConfigPath:
|
|
216
|
+
"""Test getting Cursor config path."""
|
|
217
|
+
|
|
218
|
+
def test_get_cursor_config_path_unix(self) -> None:
|
|
219
|
+
"""Test getting config path on Unix-like systems."""
|
|
220
|
+
with (
|
|
221
|
+
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
222
|
+
patch("pathlib.Path.exists", return_value=True),
|
|
223
|
+
):
|
|
224
|
+
path = get_cursor_config_path()
|
|
225
|
+
assert str(path) == str(Path("/home/user/.cursor/mcp.json"))
|
|
226
|
+
|
|
227
|
+
def test_get_cursor_config_path_windows(self) -> None:
|
|
228
|
+
"""Test getting config path on Windows."""
|
|
229
|
+
with (
|
|
230
|
+
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
231
|
+
patch("pathlib.Path.exists", return_value=False),
|
|
232
|
+
patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\Test"}),
|
|
233
|
+
):
|
|
234
|
+
path = get_cursor_config_path()
|
|
235
|
+
assert "Test" in str(path)
|
|
236
|
+
assert ".cursor" in str(path)
|
|
237
|
+
assert "mcp.json" in str(path)
|
|
238
|
+
|
|
239
|
+
def test_get_cursor_config_path_no_userprofile(self) -> None:
|
|
240
|
+
"""Test getting config path when USERPROFILE not set."""
|
|
241
|
+
with (
|
|
242
|
+
patch("pathlib.Path.home", return_value=Path("/home/user")),
|
|
243
|
+
patch("pathlib.Path.exists", return_value=False),
|
|
244
|
+
patch.dict(os.environ, {}, clear=True),
|
|
245
|
+
):
|
|
246
|
+
path = get_cursor_config_path()
|
|
247
|
+
# Should still return something based on empty USERPROFILE
|
|
248
|
+
assert ".cursor" in str(path)
|
|
249
|
+
assert "mcp.json" in str(path)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
if __name__ == "__main__":
|
|
253
|
+
pytest.main([__file__])
|