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_cli_init.py
CHANGED
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
"""Tests for hud.cli.__init__ module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
import tempfile
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from unittest.mock import patch
|
|
10
|
-
|
|
11
|
-
import pytest
|
|
12
|
-
from typer.testing import CliRunner
|
|
13
|
-
|
|
14
|
-
from hud.cli import app, main
|
|
15
|
-
|
|
16
|
-
runner = CliRunner()
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class TestCLICommands:
|
|
22
|
-
"""Test CLI command handling."""
|
|
23
|
-
|
|
24
|
-
def test_main_shows_help_when_no_args(self) -> None:
|
|
25
|
-
"""Test that main() shows help when no arguments provided."""
|
|
26
|
-
result = runner.invoke(app)
|
|
27
|
-
# When no args, typer shows help but exits with code 2 (usage error)
|
|
28
|
-
assert result.exit_code == 2
|
|
29
|
-
assert "Usage:" in result.output
|
|
30
|
-
|
|
31
|
-
def test_analyze_docker_image(self) -> None:
|
|
32
|
-
"""Test analyze command with Docker image."""
|
|
33
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
34
|
-
result = runner.invoke(app, ["analyze", "test-image:latest"])
|
|
35
|
-
assert result.exit_code == 0
|
|
36
|
-
mock_run.assert_called_once()
|
|
37
|
-
# Get the coroutine that was passed to asyncio.run
|
|
38
|
-
coro = mock_run.call_args[0][0]
|
|
39
|
-
assert coro.__name__ == "analyze_from_metadata"
|
|
40
|
-
|
|
41
|
-
def test_analyze_with_docker_args(self) -> None:
|
|
42
|
-
"""Test analyze command with additional Docker arguments."""
|
|
43
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
44
|
-
# Docker args need to come after -- to avoid being parsed as CLI options
|
|
45
|
-
result = runner.invoke(
|
|
46
|
-
app, ["analyze", "test-image", "--", "-e", "KEY=value", "-p", "8080:8080"]
|
|
47
|
-
)
|
|
48
|
-
assert result.exit_code == 0
|
|
49
|
-
mock_run.assert_called_once()
|
|
50
|
-
|
|
51
|
-
def test_analyze_with_config_file(self) -> None:
|
|
52
|
-
"""Test analyze command with config file."""
|
|
53
|
-
import os
|
|
54
|
-
|
|
55
|
-
fd, temp_path = tempfile.mkstemp(suffix=".json")
|
|
56
|
-
try:
|
|
57
|
-
with os.fdopen(fd, "w") as f:
|
|
58
|
-
json.dump({"test": {"command": "python", "args": ["server.py"]}}, f)
|
|
59
|
-
|
|
60
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
61
|
-
# Need to provide a dummy positional arg since params is required
|
|
62
|
-
result = runner.invoke(app, ["analyze", "dummy", "--config", temp_path])
|
|
63
|
-
assert result.exit_code == 0
|
|
64
|
-
mock_run.assert_called_once()
|
|
65
|
-
coro = mock_run.call_args[0][0]
|
|
66
|
-
assert coro.__name__ == "analyze_environment_from_config"
|
|
67
|
-
finally:
|
|
68
|
-
try:
|
|
69
|
-
os.unlink(temp_path)
|
|
70
|
-
except Exception:
|
|
71
|
-
logger.exception("Error deleting temp file")
|
|
72
|
-
|
|
73
|
-
def test_analyze_with_cursor_server(self) -> None:
|
|
74
|
-
"""Test analyze command with Cursor server."""
|
|
75
|
-
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
76
|
-
mock_parse.return_value = (["python", "server.py"], None)
|
|
77
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
78
|
-
# Need to provide a dummy positional arg since params is required
|
|
79
|
-
result = runner.invoke(app, ["analyze", "dummy", "--cursor", "test-server"])
|
|
80
|
-
assert result.exit_code == 0
|
|
81
|
-
mock_run.assert_called_once()
|
|
82
|
-
|
|
83
|
-
def test_analyze_cursor_server_not_found(self) -> None:
|
|
84
|
-
"""Test analyze with non-existent Cursor server."""
|
|
85
|
-
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
86
|
-
mock_parse.return_value = (None, "Server 'test' not found")
|
|
87
|
-
result = runner.invoke(app, ["analyze", "--cursor", "test"])
|
|
88
|
-
assert result.exit_code == 1
|
|
89
|
-
assert "Server 'test' not found" in result.output
|
|
90
|
-
|
|
91
|
-
def test_analyze_no_arguments_shows_error(self) -> None:
|
|
92
|
-
"""Test analyze without arguments shows error."""
|
|
93
|
-
result = runner.invoke(app, ["analyze"])
|
|
94
|
-
assert result.exit_code == 1
|
|
95
|
-
assert "Error" in result.output
|
|
96
|
-
|
|
97
|
-
def test_analyze_output_formats(self) -> None:
|
|
98
|
-
"""Test analyze with different output formats."""
|
|
99
|
-
for format_type in ["interactive", "json", "markdown"]:
|
|
100
|
-
with patch("hud.cli.asyncio.run"):
|
|
101
|
-
result = runner.invoke(app, ["analyze", "test-image", "--format", format_type])
|
|
102
|
-
assert result.exit_code == 0
|
|
103
|
-
|
|
104
|
-
def test_debug_docker_image(self) -> None:
|
|
105
|
-
"""Test debug command with Docker image."""
|
|
106
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
107
|
-
mock_run.return_value = 5 # All phases completed
|
|
108
|
-
result = runner.invoke(app, ["debug", "test-image:latest"])
|
|
109
|
-
assert result.exit_code == 0
|
|
110
|
-
mock_run.assert_called_once()
|
|
111
|
-
|
|
112
|
-
def test_debug_with_max_phase(self) -> None:
|
|
113
|
-
"""Test debug command with max phase limit."""
|
|
114
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
115
|
-
mock_run.return_value = 3 # Completed 3 phases
|
|
116
|
-
result = runner.invoke(app, ["debug", "test-image", "--max-phase", "3"])
|
|
117
|
-
assert result.exit_code == 0 # Exit code 0 when phases_completed == max_phase
|
|
118
|
-
|
|
119
|
-
def test_debug_with_config_file(self) -> None:
|
|
120
|
-
"""Test debug command with config file."""
|
|
121
|
-
import os
|
|
122
|
-
|
|
123
|
-
fd, temp_path = tempfile.mkstemp(suffix=".json")
|
|
124
|
-
try:
|
|
125
|
-
with os.fdopen(fd, "w") as f:
|
|
126
|
-
json.dump({"test": {"command": "python", "args": ["server.py"]}}, f)
|
|
127
|
-
|
|
128
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
129
|
-
mock_run.return_value = 5
|
|
130
|
-
# Need to provide a dummy positional arg since params is required
|
|
131
|
-
result = runner.invoke(app, ["debug", "dummy", "--config", temp_path])
|
|
132
|
-
assert result.exit_code == 0
|
|
133
|
-
finally:
|
|
134
|
-
try:
|
|
135
|
-
os.unlink(temp_path)
|
|
136
|
-
except Exception:
|
|
137
|
-
logger.exception("Error deleting temp file")
|
|
138
|
-
|
|
139
|
-
def test_debug_with_cursor_server(self) -> None:
|
|
140
|
-
"""Test debug command with Cursor server."""
|
|
141
|
-
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
142
|
-
mock_parse.return_value = (["python", "server.py"], None)
|
|
143
|
-
with patch("hud.cli.asyncio.run") as mock_run:
|
|
144
|
-
mock_run.return_value = 5
|
|
145
|
-
# Need to provide a dummy positional arg since params is required
|
|
146
|
-
result = runner.invoke(app, ["debug", "dummy", "--cursor", "test-server"])
|
|
147
|
-
assert result.exit_code == 0
|
|
148
|
-
|
|
149
|
-
def test_debug_no_arguments_shows_error(self) -> None:
|
|
150
|
-
"""Test debug without arguments shows error."""
|
|
151
|
-
result = runner.invoke(app, ["debug"])
|
|
152
|
-
assert result.exit_code == 1
|
|
153
|
-
assert "Error" in result.output
|
|
154
|
-
|
|
155
|
-
def test_cursor_list_command(self) -> None:
|
|
156
|
-
"""Test cursor-list command."""
|
|
157
|
-
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
158
|
-
mock_list.return_value = (["server1", "server2"], None)
|
|
159
|
-
with patch("hud.cli.get_cursor_config_path") as mock_path:
|
|
160
|
-
mock_path.return_value = Path("/home/user/.cursor/mcp.json")
|
|
161
|
-
with patch("pathlib.Path.exists") as mock_exists:
|
|
162
|
-
mock_exists.return_value = True
|
|
163
|
-
with patch("builtins.open", create=True) as mock_open:
|
|
164
|
-
mock_open.return_value.__enter__.return_value.read.return_value = (
|
|
165
|
-
json.dumps(
|
|
166
|
-
{
|
|
167
|
-
"mcpServers": {
|
|
168
|
-
"server1": {"command": "python", "args": ["srv1.py"]},
|
|
169
|
-
"server2": {"command": "node", "args": ["srv2.js"]},
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
result = runner.invoke(app, ["cursor-list"])
|
|
175
|
-
assert result.exit_code == 0
|
|
176
|
-
assert "Available Servers" in result.output
|
|
177
|
-
|
|
178
|
-
def test_cursor_list_no_servers(self) -> None:
|
|
179
|
-
"""Test cursor-list with no servers."""
|
|
180
|
-
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
181
|
-
mock_list.return_value = ([], None)
|
|
182
|
-
result = runner.invoke(app, ["cursor-list"])
|
|
183
|
-
assert result.exit_code == 0
|
|
184
|
-
assert "No servers found" in result.output
|
|
185
|
-
|
|
186
|
-
def test_cursor_list_error(self) -> None:
|
|
187
|
-
"""Test cursor-list with error."""
|
|
188
|
-
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
189
|
-
mock_list.return_value = (None, "Config not found")
|
|
190
|
-
result = runner.invoke(app, ["cursor-list"])
|
|
191
|
-
assert result.exit_code == 1
|
|
192
|
-
assert "Config not found" in result.output
|
|
193
|
-
|
|
194
|
-
def test_version_command(self) -> None:
|
|
195
|
-
"""Test version command."""
|
|
196
|
-
with patch("hud.__version__", "1.2.3"):
|
|
197
|
-
result = runner.invoke(app, ["version"])
|
|
198
|
-
assert result.exit_code == 0
|
|
199
|
-
assert "1.2.3" in result.output
|
|
200
|
-
|
|
201
|
-
def test_version_import_error(self) -> None:
|
|
202
|
-
"""Test version command when version unavailable."""
|
|
203
|
-
# Patch the specific import of __version__ from hud
|
|
204
|
-
with patch.dict("sys.modules", {"hud": None}):
|
|
205
|
-
result = runner.invoke(app, ["version"])
|
|
206
|
-
assert result.exit_code == 0
|
|
207
|
-
assert "HUD CLI version: unknown" in result.output
|
|
208
|
-
|
|
209
|
-
def test_mcp_command(self) -> None:
|
|
210
|
-
"""Test mcp server command."""
|
|
211
|
-
# MCP command has been removed from the CLI
|
|
212
|
-
result = runner.invoke(app, ["mcp"])
|
|
213
|
-
assert result.exit_code == 2 # Command not found
|
|
214
|
-
|
|
215
|
-
def test_help_command(self) -> None:
|
|
216
|
-
"""Test help command shows proper info."""
|
|
217
|
-
result = runner.invoke(app, ["--help"])
|
|
218
|
-
assert result.exit_code == 0
|
|
219
|
-
assert "HUD CLI for MCP environment analysis" in result.output
|
|
220
|
-
assert "analyze" in result.output
|
|
221
|
-
assert "debug" in result.output
|
|
222
|
-
# assert "mcp" in result.output # mcp command has been removed
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
class TestMainFunction:
|
|
226
|
-
"""Test the main() function specifically."""
|
|
227
|
-
|
|
228
|
-
def test_main_with_help_flag(self) -> None:
|
|
229
|
-
"""Test main() with --help flag."""
|
|
230
|
-
import sys
|
|
231
|
-
|
|
232
|
-
original_argv = sys.argv
|
|
233
|
-
try:
|
|
234
|
-
sys.argv = ["hud", "--help"]
|
|
235
|
-
with (
|
|
236
|
-
patch("hud.cli.console") as mock_console,
|
|
237
|
-
patch("hud.cli.app") as mock_app,
|
|
238
|
-
):
|
|
239
|
-
main()
|
|
240
|
-
# Should print the header panel
|
|
241
|
-
# Check that either console was used or app was called
|
|
242
|
-
assert mock_console.print.called or mock_app.called
|
|
243
|
-
finally:
|
|
244
|
-
sys.argv = original_argv
|
|
245
|
-
|
|
246
|
-
def test_main_with_no_args(self) -> None:
|
|
247
|
-
"""Test main() with no arguments."""
|
|
248
|
-
import sys
|
|
249
|
-
|
|
250
|
-
original_argv = sys.argv
|
|
251
|
-
try:
|
|
252
|
-
sys.argv = ["hud"]
|
|
253
|
-
with patch("hud.cli.console") as mock_console:
|
|
254
|
-
with pytest.raises(SystemExit) as exc_info:
|
|
255
|
-
main()
|
|
256
|
-
# Should exit with code 2 (missing command)
|
|
257
|
-
assert exc_info.value.code == 2
|
|
258
|
-
# Should print Quick Start guide before exiting
|
|
259
|
-
assert any("Quick Start" in str(call) for call in mock_console.print.call_args_list)
|
|
260
|
-
finally:
|
|
261
|
-
sys.argv = original_argv
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if __name__ == "__main__":
|
|
265
|
-
pytest.main([__file__])
|
|
1
|
+
"""Tests for hud.cli.__init__ module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from typer.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
from hud.cli import app, main
|
|
15
|
+
|
|
16
|
+
runner = CliRunner()
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestCLICommands:
|
|
22
|
+
"""Test CLI command handling."""
|
|
23
|
+
|
|
24
|
+
def test_main_shows_help_when_no_args(self) -> None:
|
|
25
|
+
"""Test that main() shows help when no arguments provided."""
|
|
26
|
+
result = runner.invoke(app)
|
|
27
|
+
# When no args, typer shows help but exits with code 2 (usage error)
|
|
28
|
+
assert result.exit_code == 2
|
|
29
|
+
assert "Usage:" in result.output
|
|
30
|
+
|
|
31
|
+
def test_analyze_docker_image(self) -> None:
|
|
32
|
+
"""Test analyze command with Docker image."""
|
|
33
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
34
|
+
result = runner.invoke(app, ["analyze", "test-image:latest"])
|
|
35
|
+
assert result.exit_code == 0
|
|
36
|
+
mock_run.assert_called_once()
|
|
37
|
+
# Get the coroutine that was passed to asyncio.run
|
|
38
|
+
coro = mock_run.call_args[0][0]
|
|
39
|
+
assert coro.__name__ == "analyze_from_metadata"
|
|
40
|
+
|
|
41
|
+
def test_analyze_with_docker_args(self) -> None:
|
|
42
|
+
"""Test analyze command with additional Docker arguments."""
|
|
43
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
44
|
+
# Docker args need to come after -- to avoid being parsed as CLI options
|
|
45
|
+
result = runner.invoke(
|
|
46
|
+
app, ["analyze", "test-image", "--", "-e", "KEY=value", "-p", "8080:8080"]
|
|
47
|
+
)
|
|
48
|
+
assert result.exit_code == 0
|
|
49
|
+
mock_run.assert_called_once()
|
|
50
|
+
|
|
51
|
+
def test_analyze_with_config_file(self) -> None:
|
|
52
|
+
"""Test analyze command with config file."""
|
|
53
|
+
import os
|
|
54
|
+
|
|
55
|
+
fd, temp_path = tempfile.mkstemp(suffix=".json")
|
|
56
|
+
try:
|
|
57
|
+
with os.fdopen(fd, "w") as f:
|
|
58
|
+
json.dump({"test": {"command": "python", "args": ["server.py"]}}, f)
|
|
59
|
+
|
|
60
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
61
|
+
# Need to provide a dummy positional arg since params is required
|
|
62
|
+
result = runner.invoke(app, ["analyze", "dummy", "--config", temp_path])
|
|
63
|
+
assert result.exit_code == 0
|
|
64
|
+
mock_run.assert_called_once()
|
|
65
|
+
coro = mock_run.call_args[0][0]
|
|
66
|
+
assert coro.__name__ == "analyze_environment_from_config"
|
|
67
|
+
finally:
|
|
68
|
+
try:
|
|
69
|
+
os.unlink(temp_path)
|
|
70
|
+
except Exception:
|
|
71
|
+
logger.exception("Error deleting temp file")
|
|
72
|
+
|
|
73
|
+
def test_analyze_with_cursor_server(self) -> None:
|
|
74
|
+
"""Test analyze command with Cursor server."""
|
|
75
|
+
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
76
|
+
mock_parse.return_value = (["python", "server.py"], None)
|
|
77
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
78
|
+
# Need to provide a dummy positional arg since params is required
|
|
79
|
+
result = runner.invoke(app, ["analyze", "dummy", "--cursor", "test-server"])
|
|
80
|
+
assert result.exit_code == 0
|
|
81
|
+
mock_run.assert_called_once()
|
|
82
|
+
|
|
83
|
+
def test_analyze_cursor_server_not_found(self) -> None:
|
|
84
|
+
"""Test analyze with non-existent Cursor server."""
|
|
85
|
+
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
86
|
+
mock_parse.return_value = (None, "Server 'test' not found")
|
|
87
|
+
result = runner.invoke(app, ["analyze", "--cursor", "test"])
|
|
88
|
+
assert result.exit_code == 1
|
|
89
|
+
assert "Server 'test' not found" in result.output
|
|
90
|
+
|
|
91
|
+
def test_analyze_no_arguments_shows_error(self) -> None:
|
|
92
|
+
"""Test analyze without arguments shows error."""
|
|
93
|
+
result = runner.invoke(app, ["analyze"])
|
|
94
|
+
assert result.exit_code == 1
|
|
95
|
+
assert "Error" in result.output
|
|
96
|
+
|
|
97
|
+
def test_analyze_output_formats(self) -> None:
|
|
98
|
+
"""Test analyze with different output formats."""
|
|
99
|
+
for format_type in ["interactive", "json", "markdown"]:
|
|
100
|
+
with patch("hud.cli.asyncio.run"):
|
|
101
|
+
result = runner.invoke(app, ["analyze", "test-image", "--format", format_type])
|
|
102
|
+
assert result.exit_code == 0
|
|
103
|
+
|
|
104
|
+
def test_debug_docker_image(self) -> None:
|
|
105
|
+
"""Test debug command with Docker image."""
|
|
106
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
107
|
+
mock_run.return_value = 5 # All phases completed
|
|
108
|
+
result = runner.invoke(app, ["debug", "test-image:latest"])
|
|
109
|
+
assert result.exit_code == 0
|
|
110
|
+
mock_run.assert_called_once()
|
|
111
|
+
|
|
112
|
+
def test_debug_with_max_phase(self) -> None:
|
|
113
|
+
"""Test debug command with max phase limit."""
|
|
114
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
115
|
+
mock_run.return_value = 3 # Completed 3 phases
|
|
116
|
+
result = runner.invoke(app, ["debug", "test-image", "--max-phase", "3"])
|
|
117
|
+
assert result.exit_code == 0 # Exit code 0 when phases_completed == max_phase
|
|
118
|
+
|
|
119
|
+
def test_debug_with_config_file(self) -> None:
|
|
120
|
+
"""Test debug command with config file."""
|
|
121
|
+
import os
|
|
122
|
+
|
|
123
|
+
fd, temp_path = tempfile.mkstemp(suffix=".json")
|
|
124
|
+
try:
|
|
125
|
+
with os.fdopen(fd, "w") as f:
|
|
126
|
+
json.dump({"test": {"command": "python", "args": ["server.py"]}}, f)
|
|
127
|
+
|
|
128
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
129
|
+
mock_run.return_value = 5
|
|
130
|
+
# Need to provide a dummy positional arg since params is required
|
|
131
|
+
result = runner.invoke(app, ["debug", "dummy", "--config", temp_path])
|
|
132
|
+
assert result.exit_code == 0
|
|
133
|
+
finally:
|
|
134
|
+
try:
|
|
135
|
+
os.unlink(temp_path)
|
|
136
|
+
except Exception:
|
|
137
|
+
logger.exception("Error deleting temp file")
|
|
138
|
+
|
|
139
|
+
def test_debug_with_cursor_server(self) -> None:
|
|
140
|
+
"""Test debug command with Cursor server."""
|
|
141
|
+
with patch("hud.cli.parse_cursor_config") as mock_parse:
|
|
142
|
+
mock_parse.return_value = (["python", "server.py"], None)
|
|
143
|
+
with patch("hud.cli.asyncio.run") as mock_run:
|
|
144
|
+
mock_run.return_value = 5
|
|
145
|
+
# Need to provide a dummy positional arg since params is required
|
|
146
|
+
result = runner.invoke(app, ["debug", "dummy", "--cursor", "test-server"])
|
|
147
|
+
assert result.exit_code == 0
|
|
148
|
+
|
|
149
|
+
def test_debug_no_arguments_shows_error(self) -> None:
|
|
150
|
+
"""Test debug without arguments shows error."""
|
|
151
|
+
result = runner.invoke(app, ["debug"])
|
|
152
|
+
assert result.exit_code == 1
|
|
153
|
+
assert "Error" in result.output
|
|
154
|
+
|
|
155
|
+
def test_cursor_list_command(self) -> None:
|
|
156
|
+
"""Test cursor-list command."""
|
|
157
|
+
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
158
|
+
mock_list.return_value = (["server1", "server2"], None)
|
|
159
|
+
with patch("hud.cli.get_cursor_config_path") as mock_path:
|
|
160
|
+
mock_path.return_value = Path("/home/user/.cursor/mcp.json")
|
|
161
|
+
with patch("pathlib.Path.exists") as mock_exists:
|
|
162
|
+
mock_exists.return_value = True
|
|
163
|
+
with patch("builtins.open", create=True) as mock_open:
|
|
164
|
+
mock_open.return_value.__enter__.return_value.read.return_value = (
|
|
165
|
+
json.dumps(
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"server1": {"command": "python", "args": ["srv1.py"]},
|
|
169
|
+
"server2": {"command": "node", "args": ["srv2.js"]},
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
result = runner.invoke(app, ["cursor-list"])
|
|
175
|
+
assert result.exit_code == 0
|
|
176
|
+
assert "Available Servers" in result.output
|
|
177
|
+
|
|
178
|
+
def test_cursor_list_no_servers(self) -> None:
|
|
179
|
+
"""Test cursor-list with no servers."""
|
|
180
|
+
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
181
|
+
mock_list.return_value = ([], None)
|
|
182
|
+
result = runner.invoke(app, ["cursor-list"])
|
|
183
|
+
assert result.exit_code == 0
|
|
184
|
+
assert "No servers found" in result.output
|
|
185
|
+
|
|
186
|
+
def test_cursor_list_error(self) -> None:
|
|
187
|
+
"""Test cursor-list with error."""
|
|
188
|
+
with patch("hud.cli.list_cursor_servers") as mock_list:
|
|
189
|
+
mock_list.return_value = (None, "Config not found")
|
|
190
|
+
result = runner.invoke(app, ["cursor-list"])
|
|
191
|
+
assert result.exit_code == 1
|
|
192
|
+
assert "Config not found" in result.output
|
|
193
|
+
|
|
194
|
+
def test_version_command(self) -> None:
|
|
195
|
+
"""Test version command."""
|
|
196
|
+
with patch("hud.__version__", "1.2.3"):
|
|
197
|
+
result = runner.invoke(app, ["version"])
|
|
198
|
+
assert result.exit_code == 0
|
|
199
|
+
assert "1.2.3" in result.output
|
|
200
|
+
|
|
201
|
+
def test_version_import_error(self) -> None:
|
|
202
|
+
"""Test version command when version unavailable."""
|
|
203
|
+
# Patch the specific import of __version__ from hud
|
|
204
|
+
with patch.dict("sys.modules", {"hud": None}):
|
|
205
|
+
result = runner.invoke(app, ["version"])
|
|
206
|
+
assert result.exit_code == 0
|
|
207
|
+
assert "HUD CLI version: unknown" in result.output
|
|
208
|
+
|
|
209
|
+
def test_mcp_command(self) -> None:
|
|
210
|
+
"""Test mcp server command."""
|
|
211
|
+
# MCP command has been removed from the CLI
|
|
212
|
+
result = runner.invoke(app, ["mcp"])
|
|
213
|
+
assert result.exit_code == 2 # Command not found
|
|
214
|
+
|
|
215
|
+
def test_help_command(self) -> None:
|
|
216
|
+
"""Test help command shows proper info."""
|
|
217
|
+
result = runner.invoke(app, ["--help"])
|
|
218
|
+
assert result.exit_code == 0
|
|
219
|
+
assert "HUD CLI for MCP environment analysis" in result.output
|
|
220
|
+
assert "analyze" in result.output
|
|
221
|
+
assert "debug" in result.output
|
|
222
|
+
# assert "mcp" in result.output # mcp command has been removed
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TestMainFunction:
|
|
226
|
+
"""Test the main() function specifically."""
|
|
227
|
+
|
|
228
|
+
def test_main_with_help_flag(self) -> None:
|
|
229
|
+
"""Test main() with --help flag."""
|
|
230
|
+
import sys
|
|
231
|
+
|
|
232
|
+
original_argv = sys.argv
|
|
233
|
+
try:
|
|
234
|
+
sys.argv = ["hud", "--help"]
|
|
235
|
+
with (
|
|
236
|
+
patch("hud.cli.console") as mock_console,
|
|
237
|
+
patch("hud.cli.app") as mock_app,
|
|
238
|
+
):
|
|
239
|
+
main()
|
|
240
|
+
# Should print the header panel
|
|
241
|
+
# Check that either console was used or app was called
|
|
242
|
+
assert mock_console.print.called or mock_app.called
|
|
243
|
+
finally:
|
|
244
|
+
sys.argv = original_argv
|
|
245
|
+
|
|
246
|
+
def test_main_with_no_args(self) -> None:
|
|
247
|
+
"""Test main() with no arguments."""
|
|
248
|
+
import sys
|
|
249
|
+
|
|
250
|
+
original_argv = sys.argv
|
|
251
|
+
try:
|
|
252
|
+
sys.argv = ["hud"]
|
|
253
|
+
with patch("hud.cli.console") as mock_console:
|
|
254
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
255
|
+
main()
|
|
256
|
+
# Should exit with code 2 (missing command)
|
|
257
|
+
assert exc_info.value.code == 2
|
|
258
|
+
# Should print Quick Start guide before exiting
|
|
259
|
+
assert any("Quick Start" in str(call) for call in mock_console.print.call_args_list)
|
|
260
|
+
finally:
|
|
261
|
+
sys.argv = original_argv
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
pytest.main([__file__])
|
hud/cli/tests/test_cli_main.py
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
"""Tests for hud.cli.__main__ module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestCLIMain:
|
|
9
|
-
"""Test the __main__ module."""
|
|
10
|
-
|
|
11
|
-
def test_main_module_exists(self) -> None:
|
|
12
|
-
"""Test that __main__.py exists and can be imported."""
|
|
13
|
-
# Just verify the module can be imported
|
|
14
|
-
import hud.cli.__main__
|
|
15
|
-
|
|
16
|
-
assert hud.cli.__main__ is not None
|
|
17
|
-
|
|
18
|
-
def test_main_module_has_main_import(self) -> None:
|
|
19
|
-
"""Test that __main__.py imports main from the package."""
|
|
20
|
-
import hud.cli.__main__
|
|
21
|
-
|
|
22
|
-
# The module should have imported main
|
|
23
|
-
assert hasattr(hud.cli.__main__, "main")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if __name__ == "__main__":
|
|
27
|
-
pytest.main([__file__])
|
|
1
|
+
"""Tests for hud.cli.__main__ module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestCLIMain:
|
|
9
|
+
"""Test the __main__ module."""
|
|
10
|
+
|
|
11
|
+
def test_main_module_exists(self) -> None:
|
|
12
|
+
"""Test that __main__.py exists and can be imported."""
|
|
13
|
+
# Just verify the module can be imported
|
|
14
|
+
import hud.cli.__main__
|
|
15
|
+
|
|
16
|
+
assert hud.cli.__main__ is not None
|
|
17
|
+
|
|
18
|
+
def test_main_module_has_main_import(self) -> None:
|
|
19
|
+
"""Test that __main__.py imports main from the package."""
|
|
20
|
+
import hud.cli.__main__
|
|
21
|
+
|
|
22
|
+
# The module should have imported main
|
|
23
|
+
assert hasattr(hud.cli.__main__, "main")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
pytest.main([__file__])
|