hatch-xclam 0.7.1.dev3__py3-none-any.whl → 0.8.0.dev1__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.
- hatch/__init__.py +1 -1
- hatch/cli/__init__.py +71 -0
- hatch/cli/__main__.py +1035 -0
- hatch/cli/cli_env.py +865 -0
- hatch/cli/cli_mcp.py +1965 -0
- hatch/cli/cli_package.py +566 -0
- hatch/cli/cli_system.py +136 -0
- hatch/cli/cli_utils.py +1289 -0
- hatch/cli_hatch.py +160 -2838
- hatch/mcp_host_config/__init__.py +10 -10
- hatch/mcp_host_config/adapters/__init__.py +34 -0
- hatch/mcp_host_config/adapters/base.py +170 -0
- hatch/mcp_host_config/adapters/claude.py +105 -0
- hatch/mcp_host_config/adapters/codex.py +104 -0
- hatch/mcp_host_config/adapters/cursor.py +83 -0
- hatch/mcp_host_config/adapters/gemini.py +75 -0
- hatch/mcp_host_config/adapters/kiro.py +78 -0
- hatch/mcp_host_config/adapters/lmstudio.py +79 -0
- hatch/mcp_host_config/adapters/registry.py +149 -0
- hatch/mcp_host_config/adapters/vscode.py +83 -0
- hatch/mcp_host_config/backup.py +5 -3
- hatch/mcp_host_config/fields.py +126 -0
- hatch/mcp_host_config/models.py +161 -456
- hatch/mcp_host_config/reporting.py +57 -16
- hatch/mcp_host_config/strategies.py +155 -87
- hatch/template_generator.py +1 -1
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
- hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
- tests/cli_test_utils.py +280 -0
- tests/integration/cli/__init__.py +14 -0
- tests/integration/cli/test_cli_reporter_integration.py +2439 -0
- tests/integration/mcp/__init__.py +0 -0
- tests/integration/mcp/test_adapter_serialization.py +173 -0
- tests/regression/cli/__init__.py +16 -0
- tests/regression/cli/test_color_logic.py +268 -0
- tests/regression/cli/test_consequence_type.py +298 -0
- tests/regression/cli/test_error_formatting.py +328 -0
- tests/regression/cli/test_result_reporter.py +586 -0
- tests/regression/cli/test_table_formatter.py +211 -0
- tests/regression/mcp/__init__.py +0 -0
- tests/regression/mcp/test_field_filtering.py +162 -0
- tests/test_cli_version.py +7 -5
- tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
- tests/unit/__init__.py +0 -0
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_adapter_protocol.py +138 -0
- tests/unit/mcp/test_adapter_registry.py +158 -0
- tests/unit/mcp/test_config_model.py +146 -0
- hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
- tests/integration/test_mcp_kiro_integration.py +0 -153
- tests/regression/test_mcp_codex_backup_integration.py +0 -162
- tests/regression/test_mcp_codex_host_strategy.py +0 -163
- tests/regression/test_mcp_codex_model_validation.py +0 -117
- tests/regression/test_mcp_kiro_backup_integration.py +0 -241
- tests/regression/test_mcp_kiro_cli_integration.py +0 -141
- tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
- tests/regression/test_mcp_kiro_host_strategy.py +0 -214
- tests/regression/test_mcp_kiro_model_validation.py +0 -116
- tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
- tests/test_mcp_atomic_operations.py +0 -276
- tests/test_mcp_backup_integration.py +0 -308
- tests/test_mcp_cli_all_host_specific_args.py +0 -496
- tests/test_mcp_cli_backup_management.py +0 -295
- tests/test_mcp_cli_direct_management.py +0 -456
- tests/test_mcp_cli_discovery_listing.py +0 -582
- tests/test_mcp_cli_host_config_integration.py +0 -823
- tests/test_mcp_cli_package_management.py +0 -360
- tests/test_mcp_cli_partial_updates.py +0 -859
- tests/test_mcp_environment_integration.py +0 -520
- tests/test_mcp_host_config_backup.py +0 -257
- tests/test_mcp_host_configuration_manager.py +0 -331
- tests/test_mcp_host_registry_decorator.py +0 -348
- tests/test_mcp_pydantic_architecture_v4.py +0 -603
- tests/test_mcp_server_config_models.py +0 -242
- tests/test_mcp_server_config_type_field.py +0 -221
- tests/test_mcp_sync_functionality.py +0 -316
- tests/test_mcp_user_feedback_reporting.py +0 -359
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Test suite for MCP CLI discovery and listing commands (Phase 3c).
|
|
3
|
-
|
|
4
|
-
This module tests the new MCP discovery and listing functionality:
|
|
5
|
-
- hatch mcp discover hosts
|
|
6
|
-
- hatch mcp discover servers
|
|
7
|
-
- hatch mcp list hosts
|
|
8
|
-
- hatch mcp list servers
|
|
9
|
-
|
|
10
|
-
Tests cover argument parsing, backend integration, output formatting,
|
|
11
|
-
and error handling scenarios.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import unittest
|
|
15
|
-
from unittest.mock import patch, MagicMock
|
|
16
|
-
import sys
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
# Add the parent directory to the path to import hatch modules
|
|
20
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
21
|
-
|
|
22
|
-
from hatch.cli_hatch import (
|
|
23
|
-
main, handle_mcp_discover_hosts, handle_mcp_discover_servers,
|
|
24
|
-
handle_mcp_list_hosts, handle_mcp_list_servers
|
|
25
|
-
)
|
|
26
|
-
from hatch.mcp_host_config.models import MCPHostType, MCPServerConfig
|
|
27
|
-
from hatch.environment_manager import HatchEnvironmentManager
|
|
28
|
-
from wobble import regression_test, integration_test
|
|
29
|
-
import json
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class TestMCPDiscoveryCommands(unittest.TestCase):
|
|
33
|
-
"""Test suite for MCP discovery commands."""
|
|
34
|
-
|
|
35
|
-
def setUp(self):
|
|
36
|
-
"""Set up test fixtures."""
|
|
37
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
38
|
-
self.mock_env_manager.get_current_environment.return_value = "test-env"
|
|
39
|
-
self.mock_env_manager.environment_exists.return_value = True
|
|
40
|
-
|
|
41
|
-
@regression_test
|
|
42
|
-
def test_discover_hosts_argument_parsing(self):
|
|
43
|
-
"""Test argument parsing for 'hatch mcp discover hosts' command."""
|
|
44
|
-
test_args = ['hatch', 'mcp', 'discover', 'hosts']
|
|
45
|
-
|
|
46
|
-
with patch('sys.argv', test_args):
|
|
47
|
-
with patch('hatch.cli_hatch.HatchEnvironmentManager'):
|
|
48
|
-
with patch('hatch.cli_hatch.handle_mcp_discover_hosts', return_value=0) as mock_handler:
|
|
49
|
-
try:
|
|
50
|
-
main()
|
|
51
|
-
mock_handler.assert_called_once()
|
|
52
|
-
except SystemExit as e:
|
|
53
|
-
self.assertEqual(e.code, 0)
|
|
54
|
-
|
|
55
|
-
@regression_test
|
|
56
|
-
def test_discover_servers_argument_parsing(self):
|
|
57
|
-
"""Test argument parsing for 'hatch mcp discover servers' command."""
|
|
58
|
-
test_args = ['hatch', 'mcp', 'discover', 'servers', '--env', 'test-env']
|
|
59
|
-
|
|
60
|
-
with patch('sys.argv', test_args):
|
|
61
|
-
with patch('hatch.cli_hatch.HatchEnvironmentManager'):
|
|
62
|
-
with patch('hatch.cli_hatch.handle_mcp_discover_servers', return_value=0) as mock_handler:
|
|
63
|
-
try:
|
|
64
|
-
main()
|
|
65
|
-
mock_handler.assert_called_once()
|
|
66
|
-
except SystemExit as e:
|
|
67
|
-
self.assertEqual(e.code, 0)
|
|
68
|
-
|
|
69
|
-
@regression_test
|
|
70
|
-
def test_discover_servers_default_environment(self):
|
|
71
|
-
"""Test discover servers uses current environment when --env not specified."""
|
|
72
|
-
test_args = ['hatch', 'mcp', 'discover', 'servers']
|
|
73
|
-
|
|
74
|
-
with patch('sys.argv', test_args):
|
|
75
|
-
with patch('hatch.cli_hatch.HatchEnvironmentManager') as mock_env_class:
|
|
76
|
-
mock_env_manager = MagicMock()
|
|
77
|
-
mock_env_class.return_value = mock_env_manager
|
|
78
|
-
|
|
79
|
-
with patch('hatch.cli_hatch.handle_mcp_discover_servers', return_value=0) as mock_handler:
|
|
80
|
-
try:
|
|
81
|
-
main()
|
|
82
|
-
# Should be called with env_manager and None (default env)
|
|
83
|
-
mock_handler.assert_called_once()
|
|
84
|
-
args = mock_handler.call_args[0]
|
|
85
|
-
self.assertEqual(len(args), 2) # env_manager, env_name
|
|
86
|
-
self.assertIsNone(args[1]) # env_name should be None
|
|
87
|
-
except SystemExit as e:
|
|
88
|
-
self.assertEqual(e.code, 0)
|
|
89
|
-
|
|
90
|
-
@integration_test(scope="component")
|
|
91
|
-
def test_discover_hosts_backend_integration(self):
|
|
92
|
-
"""Test discover hosts integration with MCPHostRegistry."""
|
|
93
|
-
with patch('hatch.mcp_host_config.strategies'): # Import strategies
|
|
94
|
-
with patch('hatch.cli_hatch.MCPHostRegistry') as mock_registry:
|
|
95
|
-
mock_registry.detect_available_hosts.return_value = [
|
|
96
|
-
MCPHostType.CLAUDE_DESKTOP,
|
|
97
|
-
MCPHostType.CURSOR
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
# Mock strategy for each host type
|
|
101
|
-
mock_strategy = MagicMock()
|
|
102
|
-
mock_strategy.get_config_path.return_value = Path("/test/config.json")
|
|
103
|
-
mock_registry.get_strategy.return_value = mock_strategy
|
|
104
|
-
|
|
105
|
-
with patch('builtins.print') as mock_print:
|
|
106
|
-
result = handle_mcp_discover_hosts()
|
|
107
|
-
|
|
108
|
-
self.assertEqual(result, 0)
|
|
109
|
-
mock_registry.detect_available_hosts.assert_called_once()
|
|
110
|
-
|
|
111
|
-
# Verify output contains expected information
|
|
112
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
113
|
-
self.assertTrue(any("Available MCP host platforms:" in call for call in print_calls))
|
|
114
|
-
|
|
115
|
-
@integration_test(scope="component")
|
|
116
|
-
def test_discover_servers_backend_integration(self):
|
|
117
|
-
"""Test discover servers integration with environment manager."""
|
|
118
|
-
# Mock packages with MCP servers
|
|
119
|
-
mock_packages = [
|
|
120
|
-
{'name': 'weather-toolkit', 'version': '1.0.0'},
|
|
121
|
-
{'name': 'file-manager', 'version': '2.0.0'},
|
|
122
|
-
{'name': 'regular-package', 'version': '1.5.0'} # No MCP server
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
self.mock_env_manager.list_packages.return_value = mock_packages
|
|
126
|
-
|
|
127
|
-
# Mock get_package_mcp_server_config to return config for some packages
|
|
128
|
-
def mock_get_config(env_manager, env_name, package_name):
|
|
129
|
-
if package_name in ['weather-toolkit', 'file-manager']:
|
|
130
|
-
return MCPServerConfig(
|
|
131
|
-
name=f"{package_name}-server",
|
|
132
|
-
command="python",
|
|
133
|
-
args=[f"{package_name}.py"],
|
|
134
|
-
env={}
|
|
135
|
-
)
|
|
136
|
-
else:
|
|
137
|
-
raise ValueError(f"Package '{package_name}' has no MCP server")
|
|
138
|
-
|
|
139
|
-
with patch('hatch.cli_hatch.get_package_mcp_server_config', side_effect=mock_get_config):
|
|
140
|
-
with patch('builtins.print') as mock_print:
|
|
141
|
-
result = handle_mcp_discover_servers(self.mock_env_manager, "test-env")
|
|
142
|
-
|
|
143
|
-
self.assertEqual(result, 0)
|
|
144
|
-
self.mock_env_manager.list_packages.assert_called_once_with("test-env")
|
|
145
|
-
|
|
146
|
-
# Verify output contains MCP servers
|
|
147
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
148
|
-
self.assertTrue(any("MCP servers in environment 'test-env':" in call for call in print_calls))
|
|
149
|
-
self.assertTrue(any("weather-toolkit-server:" in call for call in print_calls))
|
|
150
|
-
self.assertTrue(any("file-manager-server:" in call for call in print_calls))
|
|
151
|
-
|
|
152
|
-
@regression_test
|
|
153
|
-
def test_discover_servers_no_mcp_packages(self):
|
|
154
|
-
"""Test discover servers when no packages have MCP servers."""
|
|
155
|
-
mock_packages = [
|
|
156
|
-
{'name': 'regular-package-1', 'version': '1.0.0'},
|
|
157
|
-
{'name': 'regular-package-2', 'version': '2.0.0'}
|
|
158
|
-
]
|
|
159
|
-
|
|
160
|
-
self.mock_env_manager.list_packages.return_value = mock_packages
|
|
161
|
-
|
|
162
|
-
# Mock get_package_mcp_server_config to always raise ValueError
|
|
163
|
-
def mock_get_config(env_manager, env_name, package_name):
|
|
164
|
-
raise ValueError(f"Package '{package_name}' has no MCP server")
|
|
165
|
-
|
|
166
|
-
with patch('hatch.cli_hatch.get_package_mcp_server_config', side_effect=mock_get_config):
|
|
167
|
-
with patch('builtins.print') as mock_print:
|
|
168
|
-
result = handle_mcp_discover_servers(self.mock_env_manager, "test-env")
|
|
169
|
-
|
|
170
|
-
self.assertEqual(result, 0)
|
|
171
|
-
|
|
172
|
-
# Verify appropriate message is shown
|
|
173
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
174
|
-
self.assertTrue(any("No MCP servers found in environment 'test-env'" in call for call in print_calls))
|
|
175
|
-
|
|
176
|
-
@regression_test
|
|
177
|
-
def test_discover_servers_nonexistent_environment(self):
|
|
178
|
-
"""Test discover servers with nonexistent environment."""
|
|
179
|
-
self.mock_env_manager.environment_exists.return_value = False
|
|
180
|
-
|
|
181
|
-
with patch('builtins.print') as mock_print:
|
|
182
|
-
result = handle_mcp_discover_servers(self.mock_env_manager, "nonexistent-env")
|
|
183
|
-
|
|
184
|
-
self.assertEqual(result, 1)
|
|
185
|
-
|
|
186
|
-
# Verify error message
|
|
187
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
188
|
-
self.assertTrue(any("Error: Environment 'nonexistent-env' does not exist" in call for call in print_calls))
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
class TestMCPListCommands(unittest.TestCase):
|
|
192
|
-
"""Test suite for MCP list commands."""
|
|
193
|
-
|
|
194
|
-
def setUp(self):
|
|
195
|
-
"""Set up test fixtures."""
|
|
196
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
197
|
-
self.mock_env_manager.get_current_environment.return_value = "test-env"
|
|
198
|
-
self.mock_env_manager.environment_exists.return_value = True
|
|
199
|
-
|
|
200
|
-
@regression_test
|
|
201
|
-
def test_list_hosts_argument_parsing(self):
|
|
202
|
-
"""Test argument parsing for 'hatch mcp list hosts' command."""
|
|
203
|
-
test_args = ['hatch', 'mcp', 'list', 'hosts']
|
|
204
|
-
|
|
205
|
-
with patch('sys.argv', test_args):
|
|
206
|
-
with patch('hatch.cli_hatch.HatchEnvironmentManager'):
|
|
207
|
-
with patch('hatch.cli_hatch.handle_mcp_list_hosts', return_value=0) as mock_handler:
|
|
208
|
-
try:
|
|
209
|
-
main()
|
|
210
|
-
mock_handler.assert_called_once()
|
|
211
|
-
except SystemExit as e:
|
|
212
|
-
self.assertEqual(e.code, 0)
|
|
213
|
-
|
|
214
|
-
@regression_test
|
|
215
|
-
def test_list_servers_argument_parsing(self):
|
|
216
|
-
"""Test argument parsing for 'hatch mcp list servers' command."""
|
|
217
|
-
test_args = ['hatch', 'mcp', 'list', 'servers', '--env', 'production']
|
|
218
|
-
|
|
219
|
-
with patch('sys.argv', test_args):
|
|
220
|
-
with patch('hatch.cli_hatch.HatchEnvironmentManager'):
|
|
221
|
-
with patch('hatch.cli_hatch.handle_mcp_list_servers', return_value=0) as mock_handler:
|
|
222
|
-
try:
|
|
223
|
-
main()
|
|
224
|
-
mock_handler.assert_called_once()
|
|
225
|
-
except SystemExit as e:
|
|
226
|
-
self.assertEqual(e.code, 0)
|
|
227
|
-
|
|
228
|
-
@integration_test(scope="component")
|
|
229
|
-
def test_list_hosts_formatted_output(self):
|
|
230
|
-
"""Test list hosts produces properly formatted output for environment-scoped listing."""
|
|
231
|
-
# Setup mock environment manager with test data
|
|
232
|
-
mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
233
|
-
mock_env_manager.get_current_environment.return_value = "test-env"
|
|
234
|
-
mock_env_manager.environment_exists.return_value = True
|
|
235
|
-
mock_env_manager.get_environment_data.return_value = {
|
|
236
|
-
"packages": [
|
|
237
|
-
{
|
|
238
|
-
"name": "weather-toolkit",
|
|
239
|
-
"configured_hosts": {
|
|
240
|
-
"claude-desktop": {
|
|
241
|
-
"config_path": "~/.claude/config.json",
|
|
242
|
-
"configured_at": "2025-09-25T10:00:00"
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
]
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
with patch('builtins.print') as mock_print:
|
|
250
|
-
result = handle_mcp_list_hosts(mock_env_manager, None, False)
|
|
251
|
-
|
|
252
|
-
self.assertEqual(result, 0)
|
|
253
|
-
|
|
254
|
-
# Verify environment-scoped output format
|
|
255
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
256
|
-
output = ' '.join(print_calls)
|
|
257
|
-
self.assertIn("Configured hosts for environment 'test-env':", output)
|
|
258
|
-
self.assertIn("claude-desktop (1 packages)", output)
|
|
259
|
-
|
|
260
|
-
@integration_test(scope="component")
|
|
261
|
-
def test_list_servers_formatted_output(self):
|
|
262
|
-
"""Test list servers produces properly formatted table output."""
|
|
263
|
-
# Mock packages with MCP servers
|
|
264
|
-
mock_packages = [
|
|
265
|
-
{'name': 'weather-toolkit', 'version': '1.0.0'},
|
|
266
|
-
{'name': 'file-manager', 'version': '2.1.0'}
|
|
267
|
-
]
|
|
268
|
-
|
|
269
|
-
self.mock_env_manager.list_packages.return_value = mock_packages
|
|
270
|
-
|
|
271
|
-
# Mock get_package_mcp_server_config
|
|
272
|
-
def mock_get_config(env_manager, env_name, package_name):
|
|
273
|
-
return MCPServerConfig(
|
|
274
|
-
name=f"{package_name}-server",
|
|
275
|
-
command="python",
|
|
276
|
-
args=[f"{package_name}.py", "--port", "8080"],
|
|
277
|
-
env={}
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
with patch('hatch.cli_hatch.get_package_mcp_server_config', side_effect=mock_get_config):
|
|
281
|
-
with patch('builtins.print') as mock_print:
|
|
282
|
-
result = handle_mcp_list_servers(self.mock_env_manager, "test-env")
|
|
283
|
-
|
|
284
|
-
self.assertEqual(result, 0)
|
|
285
|
-
|
|
286
|
-
# Verify formatted table output
|
|
287
|
-
print_calls = []
|
|
288
|
-
for call in mock_print.call_args_list:
|
|
289
|
-
if call[0]: # Check if args exist
|
|
290
|
-
print_calls.append(call[0][0])
|
|
291
|
-
|
|
292
|
-
self.assertTrue(any("MCP servers in environment 'test-env':" in call for call in print_calls))
|
|
293
|
-
self.assertTrue(any("Server Name" in call for call in print_calls))
|
|
294
|
-
self.assertTrue(any("weather-toolkit-server" in call for call in print_calls))
|
|
295
|
-
self.assertTrue(any("file-manager-server" in call for call in print_calls))
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
class TestMCPListHostsEnvironmentScoped(unittest.TestCase):
|
|
299
|
-
"""Test suite for environment-scoped list hosts functionality."""
|
|
300
|
-
|
|
301
|
-
def setUp(self):
|
|
302
|
-
"""Set up test fixtures."""
|
|
303
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
304
|
-
self.mock_env_manager.get_current_environment.return_value = "test-env"
|
|
305
|
-
self.mock_env_manager.environment_exists.return_value = True
|
|
306
|
-
# Configure the mock to have the get_environment_data method
|
|
307
|
-
self.mock_env_manager.get_environment_data = MagicMock()
|
|
308
|
-
|
|
309
|
-
# Load test fixture data
|
|
310
|
-
fixture_path = Path(__file__).parent / "test_data" / "fixtures" / "environment_host_configs.json"
|
|
311
|
-
with open(fixture_path, 'r') as f:
|
|
312
|
-
self.test_data = json.load(f)
|
|
313
|
-
|
|
314
|
-
@regression_test
|
|
315
|
-
def test_list_hosts_environment_scoped_basic(self):
|
|
316
|
-
"""Test list hosts shows only hosts configured in specified environment.
|
|
317
|
-
|
|
318
|
-
Validates:
|
|
319
|
-
- Reads from environment data (not system detection)
|
|
320
|
-
- Shows only hosts with configured packages in target environment
|
|
321
|
-
- Displays host count information correctly
|
|
322
|
-
- Uses environment manager for data source
|
|
323
|
-
"""
|
|
324
|
-
# Setup: Mock environment with 2 packages using different hosts
|
|
325
|
-
self.mock_env_manager.get_environment_data.return_value = self.test_data["multi_host_environment"]
|
|
326
|
-
|
|
327
|
-
with patch('builtins.print') as mock_print:
|
|
328
|
-
# Action: Call handle_mcp_list_hosts with env_manager and env_name
|
|
329
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "test-env", False)
|
|
330
|
-
|
|
331
|
-
# Assert: Success exit code
|
|
332
|
-
self.assertEqual(result, 0)
|
|
333
|
-
|
|
334
|
-
# Assert: Environment manager methods called correctly
|
|
335
|
-
self.mock_env_manager.environment_exists.assert_called_with("test-env")
|
|
336
|
-
self.mock_env_manager.get_environment_data.assert_called_with("test-env")
|
|
337
|
-
|
|
338
|
-
# Assert: Output contains both hosts with correct package counts
|
|
339
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
340
|
-
output = ' '.join(print_calls)
|
|
341
|
-
|
|
342
|
-
self.assertIn("Configured hosts for environment 'test-env':", output)
|
|
343
|
-
self.assertIn("claude-desktop (2 packages)", output)
|
|
344
|
-
self.assertIn("cursor (1 packages)", output)
|
|
345
|
-
|
|
346
|
-
@regression_test
|
|
347
|
-
def test_list_hosts_empty_environment(self):
|
|
348
|
-
"""Test list hosts with environment containing no packages.
|
|
349
|
-
|
|
350
|
-
Validates:
|
|
351
|
-
- Handles empty environment gracefully
|
|
352
|
-
- Displays appropriate message for no configured hosts
|
|
353
|
-
- Returns success exit code (0)
|
|
354
|
-
- Does not attempt system detection
|
|
355
|
-
"""
|
|
356
|
-
# Setup: Mock environment with no packages
|
|
357
|
-
self.mock_env_manager.get_environment_data.return_value = self.test_data["empty_environment"]
|
|
358
|
-
|
|
359
|
-
with patch('builtins.print') as mock_print:
|
|
360
|
-
# Action: Call handle_mcp_list_hosts
|
|
361
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "empty-env", False)
|
|
362
|
-
|
|
363
|
-
# Assert: Success exit code
|
|
364
|
-
self.assertEqual(result, 0)
|
|
365
|
-
|
|
366
|
-
# Assert: Appropriate message displayed
|
|
367
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
368
|
-
output = ' '.join(print_calls)
|
|
369
|
-
self.assertIn("No configured hosts for environment 'empty-env'", output)
|
|
370
|
-
|
|
371
|
-
@regression_test
|
|
372
|
-
def test_list_hosts_packages_no_host_tracking(self):
|
|
373
|
-
"""Test list hosts with packages that have no configured_hosts data.
|
|
374
|
-
|
|
375
|
-
Validates:
|
|
376
|
-
- Handles packages without configured_hosts gracefully
|
|
377
|
-
- Displays appropriate message for no host configurations
|
|
378
|
-
- Maintains backward compatibility with older environment data
|
|
379
|
-
"""
|
|
380
|
-
# Setup: Mock environment with packages lacking configured_hosts
|
|
381
|
-
self.mock_env_manager.get_environment_data.return_value = self.test_data["packages_no_host_tracking"]
|
|
382
|
-
|
|
383
|
-
with patch('builtins.print') as mock_print:
|
|
384
|
-
# Action: Call handle_mcp_list_hosts
|
|
385
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "legacy-env", False)
|
|
386
|
-
|
|
387
|
-
# Assert: Success exit code
|
|
388
|
-
self.assertEqual(result, 0)
|
|
389
|
-
|
|
390
|
-
# Assert: Handles missing configured_hosts keys without error
|
|
391
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
392
|
-
output = ' '.join(print_calls)
|
|
393
|
-
self.assertIn("No configured hosts for environment 'legacy-env'", output)
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
class TestMCPListHostsCLIIntegration(unittest.TestCase):
|
|
397
|
-
"""Test suite for CLI argument processing."""
|
|
398
|
-
|
|
399
|
-
def setUp(self):
|
|
400
|
-
"""Set up test fixtures."""
|
|
401
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
402
|
-
self.mock_env_manager.get_current_environment.return_value = "current-env"
|
|
403
|
-
self.mock_env_manager.environment_exists.return_value = True
|
|
404
|
-
# Configure the mock to have the get_environment_data method
|
|
405
|
-
self.mock_env_manager.get_environment_data = MagicMock(return_value={"packages": []})
|
|
406
|
-
|
|
407
|
-
@regression_test
|
|
408
|
-
def test_list_hosts_env_argument_parsing(self):
|
|
409
|
-
"""Test --env argument processing for list hosts command.
|
|
410
|
-
|
|
411
|
-
Validates:
|
|
412
|
-
- Accepts --env argument correctly
|
|
413
|
-
- Passes environment name to handler function
|
|
414
|
-
- Uses current environment when --env not specified
|
|
415
|
-
- Validates environment exists before processing
|
|
416
|
-
"""
|
|
417
|
-
# Test case 1: hatch mcp list hosts --env project-alpha
|
|
418
|
-
with patch('builtins.print'):
|
|
419
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "project-alpha", False)
|
|
420
|
-
self.assertEqual(result, 0)
|
|
421
|
-
self.mock_env_manager.environment_exists.assert_called_with("project-alpha")
|
|
422
|
-
self.mock_env_manager.get_environment_data.assert_called_with("project-alpha")
|
|
423
|
-
|
|
424
|
-
# Reset mocks
|
|
425
|
-
self.mock_env_manager.reset_mock()
|
|
426
|
-
|
|
427
|
-
# Test case 2: hatch mcp list hosts (uses current environment)
|
|
428
|
-
with patch('builtins.print'):
|
|
429
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, None, False)
|
|
430
|
-
self.assertEqual(result, 0)
|
|
431
|
-
self.mock_env_manager.get_current_environment.assert_called_once()
|
|
432
|
-
self.mock_env_manager.environment_exists.assert_called_with("current-env")
|
|
433
|
-
|
|
434
|
-
@regression_test
|
|
435
|
-
def test_list_hosts_detailed_flag_parsing(self):
|
|
436
|
-
"""Test --detailed flag processing for list hosts command.
|
|
437
|
-
|
|
438
|
-
Validates:
|
|
439
|
-
- Accepts --detailed flag correctly
|
|
440
|
-
- Passes detailed flag to handler function
|
|
441
|
-
- Default behavior when flag not specified
|
|
442
|
-
"""
|
|
443
|
-
# Load test data with detailed information
|
|
444
|
-
fixture_path = Path(__file__).parent / "test_data" / "fixtures" / "environment_host_configs.json"
|
|
445
|
-
with open(fixture_path, 'r') as f:
|
|
446
|
-
test_data = json.load(f)
|
|
447
|
-
|
|
448
|
-
self.mock_env_manager.get_environment_data.return_value = test_data["single_host_environment"]
|
|
449
|
-
|
|
450
|
-
with patch('builtins.print') as mock_print:
|
|
451
|
-
# Test: hatch mcp list hosts --detailed
|
|
452
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "test-env", True)
|
|
453
|
-
|
|
454
|
-
# Assert: detailed=True passed to handler
|
|
455
|
-
self.assertEqual(result, 0)
|
|
456
|
-
|
|
457
|
-
# Assert: Detailed output includes config paths and timestamps
|
|
458
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
459
|
-
output = ' '.join(print_calls)
|
|
460
|
-
self.assertIn("Config path:", output)
|
|
461
|
-
self.assertIn("Configured at:", output)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
class TestMCPListHostsEnvironmentManagerIntegration(unittest.TestCase):
|
|
465
|
-
"""Test suite for environment manager integration."""
|
|
466
|
-
|
|
467
|
-
def setUp(self):
|
|
468
|
-
"""Set up test fixtures."""
|
|
469
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
470
|
-
# Configure the mock to have the get_environment_data method
|
|
471
|
-
self.mock_env_manager.get_environment_data = MagicMock()
|
|
472
|
-
|
|
473
|
-
@integration_test(scope="component")
|
|
474
|
-
def test_list_hosts_reads_environment_data(self):
|
|
475
|
-
"""Test list hosts reads actual environment data via environment manager.
|
|
476
|
-
|
|
477
|
-
Validates:
|
|
478
|
-
- Calls environment manager methods correctly
|
|
479
|
-
- Processes configured_hosts data from packages
|
|
480
|
-
- Aggregates hosts across multiple packages
|
|
481
|
-
- Handles environment resolution (current vs specified)
|
|
482
|
-
"""
|
|
483
|
-
# Setup: Real environment manager with test data
|
|
484
|
-
fixture_path = Path(__file__).parent / "test_data" / "fixtures" / "environment_host_configs.json"
|
|
485
|
-
with open(fixture_path, 'r') as f:
|
|
486
|
-
test_data = json.load(f)
|
|
487
|
-
|
|
488
|
-
self.mock_env_manager.get_current_environment.return_value = "test-env"
|
|
489
|
-
self.mock_env_manager.environment_exists.return_value = True
|
|
490
|
-
self.mock_env_manager.get_environment_data.return_value = test_data["multi_host_environment"]
|
|
491
|
-
|
|
492
|
-
with patch('builtins.print'):
|
|
493
|
-
# Action: Call list hosts functionality
|
|
494
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, None, False)
|
|
495
|
-
|
|
496
|
-
# Assert: Correct environment manager method calls
|
|
497
|
-
self.mock_env_manager.get_current_environment.assert_called_once()
|
|
498
|
-
self.mock_env_manager.environment_exists.assert_called_with("test-env")
|
|
499
|
-
self.mock_env_manager.get_environment_data.assert_called_with("test-env")
|
|
500
|
-
|
|
501
|
-
# Assert: Success result
|
|
502
|
-
self.assertEqual(result, 0)
|
|
503
|
-
|
|
504
|
-
@integration_test(scope="component")
|
|
505
|
-
def test_list_hosts_environment_validation(self):
|
|
506
|
-
"""Test list hosts validates environment existence.
|
|
507
|
-
|
|
508
|
-
Validates:
|
|
509
|
-
- Checks environment exists before processing
|
|
510
|
-
- Returns appropriate error for non-existent environment
|
|
511
|
-
- Provides helpful error message with available environments
|
|
512
|
-
"""
|
|
513
|
-
# Setup: Environment manager with known environments
|
|
514
|
-
self.mock_env_manager.environment_exists.return_value = False
|
|
515
|
-
self.mock_env_manager.list_environments.return_value = ["env1", "env2", "env3"]
|
|
516
|
-
|
|
517
|
-
with patch('builtins.print') as mock_print:
|
|
518
|
-
# Action: Call list hosts with non-existent environment
|
|
519
|
-
result = handle_mcp_list_hosts(self.mock_env_manager, "non-existent", False)
|
|
520
|
-
|
|
521
|
-
# Assert: Error message includes available environments
|
|
522
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
523
|
-
output = ' '.join(print_calls)
|
|
524
|
-
self.assertIn("Environment 'non-existent' does not exist", output)
|
|
525
|
-
self.assertIn("Available environments: env1, env2, env3", output)
|
|
526
|
-
|
|
527
|
-
# Assert: Non-zero exit code
|
|
528
|
-
self.assertEqual(result, 1)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
class TestMCPDiscoverHostsUnchanged(unittest.TestCase):
|
|
532
|
-
"""Test suite for discover hosts unchanged behavior."""
|
|
533
|
-
|
|
534
|
-
def setUp(self):
|
|
535
|
-
"""Set up test fixtures."""
|
|
536
|
-
self.mock_env_manager = MagicMock(spec=HatchEnvironmentManager)
|
|
537
|
-
|
|
538
|
-
@regression_test
|
|
539
|
-
def test_discover_hosts_system_detection_unchanged(self):
|
|
540
|
-
"""Test discover hosts continues to use system detection.
|
|
541
|
-
|
|
542
|
-
Validates:
|
|
543
|
-
- Uses host strategy detection (not environment data)
|
|
544
|
-
- Shows availability status for detected hosts
|
|
545
|
-
- Behavior unchanged from previous implementation
|
|
546
|
-
- No environment dependency
|
|
547
|
-
"""
|
|
548
|
-
# Setup: Mock host strategies with available hosts
|
|
549
|
-
with patch('hatch.mcp_host_config.strategies'): # Import strategies
|
|
550
|
-
with patch('hatch.cli_hatch.MCPHostRegistry') as mock_registry:
|
|
551
|
-
mock_registry.detect_available_hosts.return_value = [
|
|
552
|
-
MCPHostType.CLAUDE_DESKTOP,
|
|
553
|
-
MCPHostType.CURSOR
|
|
554
|
-
]
|
|
555
|
-
|
|
556
|
-
# Mock strategy for each host type
|
|
557
|
-
mock_strategy = MagicMock()
|
|
558
|
-
mock_strategy.get_config_path.return_value = Path("~/.claude/config.json")
|
|
559
|
-
mock_registry.get_strategy.return_value = mock_strategy
|
|
560
|
-
|
|
561
|
-
with patch('builtins.print') as mock_print:
|
|
562
|
-
# Action: Call handle_mcp_discover_hosts
|
|
563
|
-
result = handle_mcp_discover_hosts()
|
|
564
|
-
|
|
565
|
-
# Assert: Host strategy detection called
|
|
566
|
-
mock_registry.detect_available_hosts.assert_called_once()
|
|
567
|
-
|
|
568
|
-
# Assert: No environment manager calls (discover hosts is environment-independent)
|
|
569
|
-
# Note: discover hosts doesn't use environment manager at all
|
|
570
|
-
|
|
571
|
-
# Assert: Availability-focused output format
|
|
572
|
-
print_calls = [call[0][0] for call in mock_print.call_args_list]
|
|
573
|
-
output = ' '.join(print_calls)
|
|
574
|
-
self.assertIn("Available MCP host platforms:", output)
|
|
575
|
-
self.assertIn("Available", output)
|
|
576
|
-
|
|
577
|
-
# Assert: Success result
|
|
578
|
-
self.assertEqual(result, 0)
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if __name__ == '__main__':
|
|
582
|
-
unittest.main()
|