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.
Files changed (81) hide show
  1. hatch/__init__.py +1 -1
  2. hatch/cli/__init__.py +71 -0
  3. hatch/cli/__main__.py +1035 -0
  4. hatch/cli/cli_env.py +865 -0
  5. hatch/cli/cli_mcp.py +1965 -0
  6. hatch/cli/cli_package.py +566 -0
  7. hatch/cli/cli_system.py +136 -0
  8. hatch/cli/cli_utils.py +1289 -0
  9. hatch/cli_hatch.py +160 -2838
  10. hatch/mcp_host_config/__init__.py +10 -10
  11. hatch/mcp_host_config/adapters/__init__.py +34 -0
  12. hatch/mcp_host_config/adapters/base.py +170 -0
  13. hatch/mcp_host_config/adapters/claude.py +105 -0
  14. hatch/mcp_host_config/adapters/codex.py +104 -0
  15. hatch/mcp_host_config/adapters/cursor.py +83 -0
  16. hatch/mcp_host_config/adapters/gemini.py +75 -0
  17. hatch/mcp_host_config/adapters/kiro.py +78 -0
  18. hatch/mcp_host_config/adapters/lmstudio.py +79 -0
  19. hatch/mcp_host_config/adapters/registry.py +149 -0
  20. hatch/mcp_host_config/adapters/vscode.py +83 -0
  21. hatch/mcp_host_config/backup.py +5 -3
  22. hatch/mcp_host_config/fields.py +126 -0
  23. hatch/mcp_host_config/models.py +161 -456
  24. hatch/mcp_host_config/reporting.py +57 -16
  25. hatch/mcp_host_config/strategies.py +155 -87
  26. hatch/template_generator.py +1 -1
  27. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
  28. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
  29. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
  30. hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
  31. tests/cli_test_utils.py +280 -0
  32. tests/integration/cli/__init__.py +14 -0
  33. tests/integration/cli/test_cli_reporter_integration.py +2439 -0
  34. tests/integration/mcp/__init__.py +0 -0
  35. tests/integration/mcp/test_adapter_serialization.py +173 -0
  36. tests/regression/cli/__init__.py +16 -0
  37. tests/regression/cli/test_color_logic.py +268 -0
  38. tests/regression/cli/test_consequence_type.py +298 -0
  39. tests/regression/cli/test_error_formatting.py +328 -0
  40. tests/regression/cli/test_result_reporter.py +586 -0
  41. tests/regression/cli/test_table_formatter.py +211 -0
  42. tests/regression/mcp/__init__.py +0 -0
  43. tests/regression/mcp/test_field_filtering.py +162 -0
  44. tests/test_cli_version.py +7 -5
  45. tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
  46. tests/unit/__init__.py +0 -0
  47. tests/unit/mcp/__init__.py +0 -0
  48. tests/unit/mcp/test_adapter_protocol.py +138 -0
  49. tests/unit/mcp/test_adapter_registry.py +158 -0
  50. tests/unit/mcp/test_config_model.py +146 -0
  51. hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
  52. tests/integration/test_mcp_kiro_integration.py +0 -153
  53. tests/regression/test_mcp_codex_backup_integration.py +0 -162
  54. tests/regression/test_mcp_codex_host_strategy.py +0 -163
  55. tests/regression/test_mcp_codex_model_validation.py +0 -117
  56. tests/regression/test_mcp_kiro_backup_integration.py +0 -241
  57. tests/regression/test_mcp_kiro_cli_integration.py +0 -141
  58. tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
  59. tests/regression/test_mcp_kiro_host_strategy.py +0 -214
  60. tests/regression/test_mcp_kiro_model_validation.py +0 -116
  61. tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
  62. tests/test_mcp_atomic_operations.py +0 -276
  63. tests/test_mcp_backup_integration.py +0 -308
  64. tests/test_mcp_cli_all_host_specific_args.py +0 -496
  65. tests/test_mcp_cli_backup_management.py +0 -295
  66. tests/test_mcp_cli_direct_management.py +0 -456
  67. tests/test_mcp_cli_discovery_listing.py +0 -582
  68. tests/test_mcp_cli_host_config_integration.py +0 -823
  69. tests/test_mcp_cli_package_management.py +0 -360
  70. tests/test_mcp_cli_partial_updates.py +0 -859
  71. tests/test_mcp_environment_integration.py +0 -520
  72. tests/test_mcp_host_config_backup.py +0 -257
  73. tests/test_mcp_host_configuration_manager.py +0 -331
  74. tests/test_mcp_host_registry_decorator.py +0 -348
  75. tests/test_mcp_pydantic_architecture_v4.py +0 -603
  76. tests/test_mcp_server_config_models.py +0 -242
  77. tests/test_mcp_server_config_type_field.py +0 -221
  78. tests/test_mcp_sync_functionality.py +0 -316
  79. tests/test_mcp_user_feedback_reporting.py +0 -359
  80. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
  81. {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()