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_analyze.py
CHANGED
|
@@ -1,284 +1,284 @@
|
|
|
1
|
-
"""Tests for hud.cli.analyze module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
|
|
8
|
-
|
|
9
|
-
import pytest
|
|
10
|
-
|
|
11
|
-
from hud.cli.analyze import (
|
|
12
|
-
_analyze_with_config,
|
|
13
|
-
analyze_environment,
|
|
14
|
-
analyze_environment_from_config,
|
|
15
|
-
analyze_environment_from_mcp_config,
|
|
16
|
-
display_interactive,
|
|
17
|
-
display_markdown,
|
|
18
|
-
parse_docker_command,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class TestParseDockerCommand:
|
|
23
|
-
"""Test Docker command parsing."""
|
|
24
|
-
|
|
25
|
-
def test_parse_simple_docker_command(self) -> None:
|
|
26
|
-
"""Test parsing simple Docker command."""
|
|
27
|
-
docker_cmd = ["docker", "run", "image:latest"]
|
|
28
|
-
result = parse_docker_command(docker_cmd)
|
|
29
|
-
assert result == {"local": {"command": "docker", "args": ["run", "image:latest"]}}
|
|
30
|
-
|
|
31
|
-
def test_parse_docker_command_no_args(self) -> None:
|
|
32
|
-
"""Test parsing Docker command with no arguments."""
|
|
33
|
-
docker_cmd = ["docker"]
|
|
34
|
-
result = parse_docker_command(docker_cmd)
|
|
35
|
-
assert result == {"local": {"command": "docker", "args": []}}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class TestAnalyzeEnvironment:
|
|
39
|
-
"""Test main analyze_environment function."""
|
|
40
|
-
|
|
41
|
-
@pytest.mark.asyncio
|
|
42
|
-
async def test_analyze_environment_success(self) -> None:
|
|
43
|
-
"""Test successful environment analysis."""
|
|
44
|
-
mock_analysis = {
|
|
45
|
-
"metadata": {"servers": ["test"], "initialized": True},
|
|
46
|
-
"tools": [{"name": "tool1", "description": "Test tool"}],
|
|
47
|
-
"hub_tools": {},
|
|
48
|
-
"resources": [],
|
|
49
|
-
"telemetry": {},
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
with (
|
|
53
|
-
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
54
|
-
patch("hud.cli.analyze.console"),
|
|
55
|
-
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
56
|
-
):
|
|
57
|
-
# Setup mock client - return an instance with async methods
|
|
58
|
-
mock_client = MagicMock()
|
|
59
|
-
mock_client.initialize = AsyncMock()
|
|
60
|
-
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
61
|
-
mock_client.shutdown = AsyncMock()
|
|
62
|
-
MockClient.return_value = mock_client
|
|
63
|
-
|
|
64
|
-
await analyze_environment(
|
|
65
|
-
["docker", "run", "test"],
|
|
66
|
-
output_format="interactive",
|
|
67
|
-
verbose=False,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Check client was used correctly
|
|
71
|
-
MockClient.assert_called_once()
|
|
72
|
-
mock_client.initialize.assert_called_once()
|
|
73
|
-
mock_client.analyze_environment.assert_called_once()
|
|
74
|
-
mock_client.shutdown.assert_called_once()
|
|
75
|
-
|
|
76
|
-
# Check interactive display was called
|
|
77
|
-
mock_interactive.assert_called_once_with(mock_analysis)
|
|
78
|
-
|
|
79
|
-
@pytest.mark.asyncio
|
|
80
|
-
async def test_analyze_environment_failure(self) -> None:
|
|
81
|
-
"""Test handling analysis failure."""
|
|
82
|
-
with (
|
|
83
|
-
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
84
|
-
patch("hud.cli.analyze.console") as mock_console,
|
|
85
|
-
):
|
|
86
|
-
# Setup mock client that will raise exception during initialization
|
|
87
|
-
mock_client = MagicMock()
|
|
88
|
-
mock_client.initialize = AsyncMock(side_effect=RuntimeError("Connection failed"))
|
|
89
|
-
mock_client.shutdown = AsyncMock()
|
|
90
|
-
MockClient.return_value = mock_client
|
|
91
|
-
|
|
92
|
-
# Test should not raise exception
|
|
93
|
-
await analyze_environment(
|
|
94
|
-
["docker", "run", "test"],
|
|
95
|
-
output_format="json",
|
|
96
|
-
verbose=False,
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Check error was handled
|
|
100
|
-
mock_client.initialize.assert_called_once()
|
|
101
|
-
mock_client.shutdown.assert_called_once()
|
|
102
|
-
|
|
103
|
-
# Check console printed error hints
|
|
104
|
-
calls = mock_console.print.call_args_list
|
|
105
|
-
assert any("Docker logs may not show on Windows" in str(call) for call in calls)
|
|
106
|
-
|
|
107
|
-
@pytest.mark.asyncio
|
|
108
|
-
async def test_analyze_environment_formats(self) -> None:
|
|
109
|
-
"""Test different output formats."""
|
|
110
|
-
mock_analysis = {
|
|
111
|
-
"metadata": {"servers": ["test"], "initialized": True},
|
|
112
|
-
"tools": [],
|
|
113
|
-
"hub_tools": {},
|
|
114
|
-
"resources": [],
|
|
115
|
-
"telemetry": {},
|
|
116
|
-
"verbose": False,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for output_format in ["json", "markdown", "interactive"]:
|
|
120
|
-
with (
|
|
121
|
-
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
122
|
-
patch("hud.cli.analyze.console") as mock_console,
|
|
123
|
-
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
124
|
-
patch("hud.cli.analyze.display_markdown") as mock_markdown,
|
|
125
|
-
):
|
|
126
|
-
# Setup mock client
|
|
127
|
-
mock_client = MagicMock()
|
|
128
|
-
mock_client.initialize = AsyncMock()
|
|
129
|
-
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
130
|
-
mock_client.shutdown = AsyncMock()
|
|
131
|
-
MockClient.return_value = mock_client
|
|
132
|
-
|
|
133
|
-
# Run analysis
|
|
134
|
-
await analyze_environment(
|
|
135
|
-
["docker", "run", "test"],
|
|
136
|
-
output_format=output_format,
|
|
137
|
-
verbose=False,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
# Check correct display function was called
|
|
141
|
-
if output_format == "json":
|
|
142
|
-
mock_console.print_json.assert_called()
|
|
143
|
-
elif output_format == "markdown":
|
|
144
|
-
mock_markdown.assert_called_once_with(mock_analysis)
|
|
145
|
-
else: # interactive
|
|
146
|
-
mock_interactive.assert_called_once_with(mock_analysis)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
class TestAnalyzeWithConfig:
|
|
150
|
-
"""Test config-based analysis functions."""
|
|
151
|
-
|
|
152
|
-
@pytest.mark.asyncio
|
|
153
|
-
async def test_analyze_with_config_success(self) -> None:
|
|
154
|
-
"""Test successful config-based analysis."""
|
|
155
|
-
mock_config = {"server": {"command": "test", "args": ["--arg"]}}
|
|
156
|
-
mock_analysis = {
|
|
157
|
-
"metadata": {"servers": ["server"], "initialized": True},
|
|
158
|
-
"tools": [],
|
|
159
|
-
"hub_tools": {},
|
|
160
|
-
"resources": [],
|
|
161
|
-
"telemetry": {},
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
with (
|
|
165
|
-
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
166
|
-
patch("hud.cli.analyze.console"),
|
|
167
|
-
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
168
|
-
):
|
|
169
|
-
# Setup mock client
|
|
170
|
-
mock_client = MagicMock()
|
|
171
|
-
mock_client.initialize = AsyncMock()
|
|
172
|
-
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
173
|
-
mock_client.shutdown = AsyncMock()
|
|
174
|
-
MockClient.return_value = mock_client
|
|
175
|
-
|
|
176
|
-
await _analyze_with_config(
|
|
177
|
-
mock_config,
|
|
178
|
-
output_format="interactive",
|
|
179
|
-
verbose=False,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
# Check client was created with correct config
|
|
183
|
-
MockClient.assert_called_once_with(mcp_config=mock_config, verbose=False)
|
|
184
|
-
mock_interactive.assert_called_once_with(mock_analysis)
|
|
185
|
-
|
|
186
|
-
@pytest.mark.asyncio
|
|
187
|
-
async def test_analyze_with_config_exception(self) -> None:
|
|
188
|
-
"""Test config analysis handles exceptions gracefully."""
|
|
189
|
-
mock_config = {"server": {"command": "test"}}
|
|
190
|
-
|
|
191
|
-
with (
|
|
192
|
-
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
193
|
-
patch("hud.cli.analyze.console"),
|
|
194
|
-
):
|
|
195
|
-
# Setup mock client that fails
|
|
196
|
-
mock_client = MagicMock()
|
|
197
|
-
mock_client.initialize = AsyncMock(side_effect=Exception("Test error"))
|
|
198
|
-
mock_client.shutdown = AsyncMock()
|
|
199
|
-
MockClient.return_value = mock_client
|
|
200
|
-
|
|
201
|
-
# Should not raise
|
|
202
|
-
await _analyze_with_config(
|
|
203
|
-
mock_config,
|
|
204
|
-
output_format="json",
|
|
205
|
-
verbose=False,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
mock_client.shutdown.assert_called_once()
|
|
209
|
-
|
|
210
|
-
@pytest.mark.asyncio
|
|
211
|
-
async def test_analyze_environment_from_config(self) -> None:
|
|
212
|
-
"""Test analyze_environment_from_config."""
|
|
213
|
-
config_data = {"server": {"command": "test"}}
|
|
214
|
-
mock_path = Path("test.json")
|
|
215
|
-
|
|
216
|
-
with (
|
|
217
|
-
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
218
|
-
patch("hud.cli.analyze._analyze_with_config") as mock_analyze,
|
|
219
|
-
):
|
|
220
|
-
await analyze_environment_from_config(mock_path, "json", False)
|
|
221
|
-
|
|
222
|
-
mock_analyze.assert_called_once_with(config_data, "json", False)
|
|
223
|
-
|
|
224
|
-
@pytest.mark.asyncio
|
|
225
|
-
async def test_analyze_environment_from_mcp_config(self) -> None:
|
|
226
|
-
"""Test analyze_environment_from_mcp_config."""
|
|
227
|
-
config_data = {"server": {"command": "test"}}
|
|
228
|
-
|
|
229
|
-
with patch("hud.cli.analyze._analyze_with_config") as mock_analyze:
|
|
230
|
-
await analyze_environment_from_mcp_config(config_data, "markdown", True)
|
|
231
|
-
|
|
232
|
-
mock_analyze.assert_called_once_with(config_data, "markdown", True)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
class TestDisplayFunctions:
|
|
236
|
-
"""Test display formatting functions."""
|
|
237
|
-
|
|
238
|
-
def test_display_interactive_basic(self) -> None:
|
|
239
|
-
"""Test basic interactive display."""
|
|
240
|
-
analysis = {
|
|
241
|
-
"metadata": {"servers": ["test"], "initialized": True},
|
|
242
|
-
"tools": [{"name": "tool1", "description": "Test tool"}],
|
|
243
|
-
"hub_tools": {"hub1": ["func1", "func2"]},
|
|
244
|
-
"resources": [{"uri": "file:///test", "name": "Test", "description": "Resource"}],
|
|
245
|
-
"telemetry": {"status": "running", "live_url": "http://test"},
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
with patch("hud.cli.analyze.console") as mock_console:
|
|
249
|
-
display_interactive(analysis)
|
|
250
|
-
|
|
251
|
-
# Check console was called multiple times
|
|
252
|
-
assert mock_console.print.call_count > 0
|
|
253
|
-
# The design.section_title uses its own console, not the patched one
|
|
254
|
-
# Just verify the function ran without errors
|
|
255
|
-
|
|
256
|
-
def test_display_markdown_basic(self) -> None:
|
|
257
|
-
"""Test basic markdown display."""
|
|
258
|
-
analysis = {
|
|
259
|
-
"metadata": {"servers": ["test1", "test2"], "initialized": True},
|
|
260
|
-
"tools": [
|
|
261
|
-
{"name": "tool1", "description": "Tool 1"},
|
|
262
|
-
{"name": "setup", "description": "Hub tool"},
|
|
263
|
-
],
|
|
264
|
-
"hub_tools": {"setup": ["init", "config"]},
|
|
265
|
-
"resources": [{"uri": "telemetry://live", "name": "Telemetry"}],
|
|
266
|
-
"telemetry": {"status": "active"},
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
with patch("hud.cli.analyze.console") as mock_console:
|
|
270
|
-
display_markdown(analysis)
|
|
271
|
-
|
|
272
|
-
# Get the markdown output
|
|
273
|
-
mock_console.print.assert_called_once()
|
|
274
|
-
markdown = mock_console.print.call_args[0][0]
|
|
275
|
-
|
|
276
|
-
# Check markdown structure
|
|
277
|
-
assert "# MCP Environment Analysis" in markdown
|
|
278
|
-
assert "## Environment Overview" in markdown
|
|
279
|
-
assert "## Available Tools" in markdown
|
|
280
|
-
assert "### Regular Tools" in markdown
|
|
281
|
-
assert "### Hub Tools" in markdown
|
|
282
|
-
assert "- **tool1**: Tool 1" in markdown
|
|
283
|
-
assert "- **setup**" in markdown
|
|
284
|
-
assert " - init" in markdown
|
|
1
|
+
"""Tests for hud.cli.analyze module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from hud.cli.analyze import (
|
|
12
|
+
_analyze_with_config,
|
|
13
|
+
analyze_environment,
|
|
14
|
+
analyze_environment_from_config,
|
|
15
|
+
analyze_environment_from_mcp_config,
|
|
16
|
+
display_interactive,
|
|
17
|
+
display_markdown,
|
|
18
|
+
parse_docker_command,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestParseDockerCommand:
|
|
23
|
+
"""Test Docker command parsing."""
|
|
24
|
+
|
|
25
|
+
def test_parse_simple_docker_command(self) -> None:
|
|
26
|
+
"""Test parsing simple Docker command."""
|
|
27
|
+
docker_cmd = ["docker", "run", "image:latest"]
|
|
28
|
+
result = parse_docker_command(docker_cmd)
|
|
29
|
+
assert result == {"local": {"command": "docker", "args": ["run", "image:latest"]}}
|
|
30
|
+
|
|
31
|
+
def test_parse_docker_command_no_args(self) -> None:
|
|
32
|
+
"""Test parsing Docker command with no arguments."""
|
|
33
|
+
docker_cmd = ["docker"]
|
|
34
|
+
result = parse_docker_command(docker_cmd)
|
|
35
|
+
assert result == {"local": {"command": "docker", "args": []}}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestAnalyzeEnvironment:
|
|
39
|
+
"""Test main analyze_environment function."""
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_analyze_environment_success(self) -> None:
|
|
43
|
+
"""Test successful environment analysis."""
|
|
44
|
+
mock_analysis = {
|
|
45
|
+
"metadata": {"servers": ["test"], "initialized": True},
|
|
46
|
+
"tools": [{"name": "tool1", "description": "Test tool"}],
|
|
47
|
+
"hub_tools": {},
|
|
48
|
+
"resources": [],
|
|
49
|
+
"telemetry": {},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
with (
|
|
53
|
+
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
54
|
+
patch("hud.cli.analyze.console"),
|
|
55
|
+
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
56
|
+
):
|
|
57
|
+
# Setup mock client - return an instance with async methods
|
|
58
|
+
mock_client = MagicMock()
|
|
59
|
+
mock_client.initialize = AsyncMock()
|
|
60
|
+
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
61
|
+
mock_client.shutdown = AsyncMock()
|
|
62
|
+
MockClient.return_value = mock_client
|
|
63
|
+
|
|
64
|
+
await analyze_environment(
|
|
65
|
+
["docker", "run", "test"],
|
|
66
|
+
output_format="interactive",
|
|
67
|
+
verbose=False,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Check client was used correctly
|
|
71
|
+
MockClient.assert_called_once()
|
|
72
|
+
mock_client.initialize.assert_called_once()
|
|
73
|
+
mock_client.analyze_environment.assert_called_once()
|
|
74
|
+
mock_client.shutdown.assert_called_once()
|
|
75
|
+
|
|
76
|
+
# Check interactive display was called
|
|
77
|
+
mock_interactive.assert_called_once_with(mock_analysis)
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_analyze_environment_failure(self) -> None:
|
|
81
|
+
"""Test handling analysis failure."""
|
|
82
|
+
with (
|
|
83
|
+
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
84
|
+
patch("hud.cli.analyze.console") as mock_console,
|
|
85
|
+
):
|
|
86
|
+
# Setup mock client that will raise exception during initialization
|
|
87
|
+
mock_client = MagicMock()
|
|
88
|
+
mock_client.initialize = AsyncMock(side_effect=RuntimeError("Connection failed"))
|
|
89
|
+
mock_client.shutdown = AsyncMock()
|
|
90
|
+
MockClient.return_value = mock_client
|
|
91
|
+
|
|
92
|
+
# Test should not raise exception
|
|
93
|
+
await analyze_environment(
|
|
94
|
+
["docker", "run", "test"],
|
|
95
|
+
output_format="json",
|
|
96
|
+
verbose=False,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Check error was handled
|
|
100
|
+
mock_client.initialize.assert_called_once()
|
|
101
|
+
mock_client.shutdown.assert_called_once()
|
|
102
|
+
|
|
103
|
+
# Check console printed error hints
|
|
104
|
+
calls = mock_console.print.call_args_list
|
|
105
|
+
assert any("Docker logs may not show on Windows" in str(call) for call in calls)
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_analyze_environment_formats(self) -> None:
|
|
109
|
+
"""Test different output formats."""
|
|
110
|
+
mock_analysis = {
|
|
111
|
+
"metadata": {"servers": ["test"], "initialized": True},
|
|
112
|
+
"tools": [],
|
|
113
|
+
"hub_tools": {},
|
|
114
|
+
"resources": [],
|
|
115
|
+
"telemetry": {},
|
|
116
|
+
"verbose": False,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for output_format in ["json", "markdown", "interactive"]:
|
|
120
|
+
with (
|
|
121
|
+
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
122
|
+
patch("hud.cli.analyze.console") as mock_console,
|
|
123
|
+
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
124
|
+
patch("hud.cli.analyze.display_markdown") as mock_markdown,
|
|
125
|
+
):
|
|
126
|
+
# Setup mock client
|
|
127
|
+
mock_client = MagicMock()
|
|
128
|
+
mock_client.initialize = AsyncMock()
|
|
129
|
+
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
130
|
+
mock_client.shutdown = AsyncMock()
|
|
131
|
+
MockClient.return_value = mock_client
|
|
132
|
+
|
|
133
|
+
# Run analysis
|
|
134
|
+
await analyze_environment(
|
|
135
|
+
["docker", "run", "test"],
|
|
136
|
+
output_format=output_format,
|
|
137
|
+
verbose=False,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Check correct display function was called
|
|
141
|
+
if output_format == "json":
|
|
142
|
+
mock_console.print_json.assert_called()
|
|
143
|
+
elif output_format == "markdown":
|
|
144
|
+
mock_markdown.assert_called_once_with(mock_analysis)
|
|
145
|
+
else: # interactive
|
|
146
|
+
mock_interactive.assert_called_once_with(mock_analysis)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestAnalyzeWithConfig:
|
|
150
|
+
"""Test config-based analysis functions."""
|
|
151
|
+
|
|
152
|
+
@pytest.mark.asyncio
|
|
153
|
+
async def test_analyze_with_config_success(self) -> None:
|
|
154
|
+
"""Test successful config-based analysis."""
|
|
155
|
+
mock_config = {"server": {"command": "test", "args": ["--arg"]}}
|
|
156
|
+
mock_analysis = {
|
|
157
|
+
"metadata": {"servers": ["server"], "initialized": True},
|
|
158
|
+
"tools": [],
|
|
159
|
+
"hub_tools": {},
|
|
160
|
+
"resources": [],
|
|
161
|
+
"telemetry": {},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
with (
|
|
165
|
+
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
166
|
+
patch("hud.cli.analyze.console"),
|
|
167
|
+
patch("hud.cli.analyze.display_interactive") as mock_interactive,
|
|
168
|
+
):
|
|
169
|
+
# Setup mock client
|
|
170
|
+
mock_client = MagicMock()
|
|
171
|
+
mock_client.initialize = AsyncMock()
|
|
172
|
+
mock_client.analyze_environment = AsyncMock(return_value=mock_analysis)
|
|
173
|
+
mock_client.shutdown = AsyncMock()
|
|
174
|
+
MockClient.return_value = mock_client
|
|
175
|
+
|
|
176
|
+
await _analyze_with_config(
|
|
177
|
+
mock_config,
|
|
178
|
+
output_format="interactive",
|
|
179
|
+
verbose=False,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Check client was created with correct config
|
|
183
|
+
MockClient.assert_called_once_with(mcp_config=mock_config, verbose=False)
|
|
184
|
+
mock_interactive.assert_called_once_with(mock_analysis)
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_analyze_with_config_exception(self) -> None:
|
|
188
|
+
"""Test config analysis handles exceptions gracefully."""
|
|
189
|
+
mock_config = {"server": {"command": "test"}}
|
|
190
|
+
|
|
191
|
+
with (
|
|
192
|
+
patch("hud.cli.analyze.MCPClient") as MockClient,
|
|
193
|
+
patch("hud.cli.analyze.console"),
|
|
194
|
+
):
|
|
195
|
+
# Setup mock client that fails
|
|
196
|
+
mock_client = MagicMock()
|
|
197
|
+
mock_client.initialize = AsyncMock(side_effect=Exception("Test error"))
|
|
198
|
+
mock_client.shutdown = AsyncMock()
|
|
199
|
+
MockClient.return_value = mock_client
|
|
200
|
+
|
|
201
|
+
# Should not raise
|
|
202
|
+
await _analyze_with_config(
|
|
203
|
+
mock_config,
|
|
204
|
+
output_format="json",
|
|
205
|
+
verbose=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
mock_client.shutdown.assert_called_once()
|
|
209
|
+
|
|
210
|
+
@pytest.mark.asyncio
|
|
211
|
+
async def test_analyze_environment_from_config(self) -> None:
|
|
212
|
+
"""Test analyze_environment_from_config."""
|
|
213
|
+
config_data = {"server": {"command": "test"}}
|
|
214
|
+
mock_path = Path("test.json")
|
|
215
|
+
|
|
216
|
+
with (
|
|
217
|
+
patch("builtins.open", mock_open(read_data=json.dumps(config_data))),
|
|
218
|
+
patch("hud.cli.analyze._analyze_with_config") as mock_analyze,
|
|
219
|
+
):
|
|
220
|
+
await analyze_environment_from_config(mock_path, "json", False)
|
|
221
|
+
|
|
222
|
+
mock_analyze.assert_called_once_with(config_data, "json", False)
|
|
223
|
+
|
|
224
|
+
@pytest.mark.asyncio
|
|
225
|
+
async def test_analyze_environment_from_mcp_config(self) -> None:
|
|
226
|
+
"""Test analyze_environment_from_mcp_config."""
|
|
227
|
+
config_data = {"server": {"command": "test"}}
|
|
228
|
+
|
|
229
|
+
with patch("hud.cli.analyze._analyze_with_config") as mock_analyze:
|
|
230
|
+
await analyze_environment_from_mcp_config(config_data, "markdown", True)
|
|
231
|
+
|
|
232
|
+
mock_analyze.assert_called_once_with(config_data, "markdown", True)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestDisplayFunctions:
|
|
236
|
+
"""Test display formatting functions."""
|
|
237
|
+
|
|
238
|
+
def test_display_interactive_basic(self) -> None:
|
|
239
|
+
"""Test basic interactive display."""
|
|
240
|
+
analysis = {
|
|
241
|
+
"metadata": {"servers": ["test"], "initialized": True},
|
|
242
|
+
"tools": [{"name": "tool1", "description": "Test tool"}],
|
|
243
|
+
"hub_tools": {"hub1": ["func1", "func2"]},
|
|
244
|
+
"resources": [{"uri": "file:///test", "name": "Test", "description": "Resource"}],
|
|
245
|
+
"telemetry": {"status": "running", "live_url": "http://test"},
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
with patch("hud.cli.analyze.console") as mock_console:
|
|
249
|
+
display_interactive(analysis)
|
|
250
|
+
|
|
251
|
+
# Check console was called multiple times
|
|
252
|
+
assert mock_console.print.call_count > 0
|
|
253
|
+
# The design.section_title uses its own console, not the patched one
|
|
254
|
+
# Just verify the function ran without errors
|
|
255
|
+
|
|
256
|
+
def test_display_markdown_basic(self) -> None:
|
|
257
|
+
"""Test basic markdown display."""
|
|
258
|
+
analysis = {
|
|
259
|
+
"metadata": {"servers": ["test1", "test2"], "initialized": True},
|
|
260
|
+
"tools": [
|
|
261
|
+
{"name": "tool1", "description": "Tool 1"},
|
|
262
|
+
{"name": "setup", "description": "Hub tool"},
|
|
263
|
+
],
|
|
264
|
+
"hub_tools": {"setup": ["init", "config"]},
|
|
265
|
+
"resources": [{"uri": "telemetry://live", "name": "Telemetry"}],
|
|
266
|
+
"telemetry": {"status": "active"},
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
with patch("hud.cli.analyze.console") as mock_console:
|
|
270
|
+
display_markdown(analysis)
|
|
271
|
+
|
|
272
|
+
# Get the markdown output
|
|
273
|
+
mock_console.print.assert_called_once()
|
|
274
|
+
markdown = mock_console.print.call_args[0][0]
|
|
275
|
+
|
|
276
|
+
# Check markdown structure
|
|
277
|
+
assert "# MCP Environment Analysis" in markdown
|
|
278
|
+
assert "## Environment Overview" in markdown
|
|
279
|
+
assert "## Available Tools" in markdown
|
|
280
|
+
assert "### Regular Tools" in markdown
|
|
281
|
+
assert "### Hub Tools" in markdown
|
|
282
|
+
assert "- **tool1**: Tool 1" in markdown
|
|
283
|
+
assert "- **setup**" in markdown
|
|
284
|
+
assert " - init" in markdown
|