|
18 | 18 | sys.path.insert(0, str(Path(__file__).parent.parent)) |
19 | 19 |
|
20 | 20 | from hatch.cli_hatch import ( |
21 | | - main, handle_mcp_configure, handle_mcp_remove, parse_env_vars, parse_headers |
| 21 | + main, handle_mcp_configure, handle_mcp_remove, handle_mcp_remove_server, |
| 22 | + handle_mcp_remove_host, parse_env_vars, parse_headers |
22 | 23 | ) |
23 | 24 | from hatch.mcp_host_config.models import MCPHostType, MCPServerConfig |
24 | 25 | from wobble import regression_test, integration_test |
@@ -209,15 +210,15 @@ class TestMCPRemoveCommand(unittest.TestCase): |
209 | 210 |
|
210 | 211 | @regression_test |
211 | 212 | def test_remove_argument_parsing(self): |
212 | | - """Test argument parsing for 'hatch mcp remove' command.""" |
213 | | - test_args = ['hatch', 'mcp', 'remove', 'vscode', 'old-server', '--no-backup', '--auto-approve'] |
214 | | - |
| 213 | + """Test argument parsing for 'hatch mcp remove server' command.""" |
| 214 | + test_args = ['hatch', 'mcp', 'remove', 'server', 'old-server', '--host', 'vscode', '--no-backup', '--auto-approve'] |
| 215 | + |
215 | 216 | with patch('sys.argv', test_args): |
216 | 217 | with patch('hatch.cli_hatch.HatchEnvironmentManager'): |
217 | | - with patch('hatch.cli_hatch.handle_mcp_remove', return_value=0) as mock_handler: |
| 218 | + with patch('hatch.cli_hatch.handle_mcp_remove_server', return_value=0) as mock_handler: |
218 | 219 | try: |
219 | 220 | main() |
220 | | - mock_handler.assert_called_once_with('vscode', 'old-server', True, False, True) |
| 221 | + mock_handler.assert_called_once_with('old-server', 'vscode', None, True, False, True) |
221 | 222 | except SystemExit as e: |
222 | 223 | self.assertEqual(e.code, 0) |
223 | 224 |
|
@@ -303,5 +304,131 @@ def test_remove_failed(self): |
303 | 304 | self.assertTrue(any("Server not found in configuration" in call for call in print_calls)) |
304 | 305 |
|
305 | 306 |
|
| 307 | +class TestMCPRemoveServerCommand(unittest.TestCase): |
| 308 | + """Test suite for MCP remove server command (new object-action pattern).""" |
| 309 | + |
| 310 | + @regression_test |
| 311 | + def test_remove_server_argument_parsing(self): |
| 312 | + """Test argument parsing for 'hatch mcp remove server' command.""" |
| 313 | + test_args = ['hatch', 'mcp', 'remove', 'server', 'test-server', '--host', 'claude-desktop', '--no-backup'] |
| 314 | + |
| 315 | + with patch('sys.argv', test_args): |
| 316 | + with patch('hatch.cli_hatch.HatchEnvironmentManager'): |
| 317 | + with patch('hatch.cli_hatch.handle_mcp_remove_server', return_value=0) as mock_handler: |
| 318 | + try: |
| 319 | + main() |
| 320 | + mock_handler.assert_called_once_with('test-server', 'claude-desktop', None, True, False, False) |
| 321 | + except SystemExit as e: |
| 322 | + self.assertEqual(e.code, 0) |
| 323 | + |
| 324 | + @integration_test(scope="component") |
| 325 | + def test_remove_server_multi_host(self): |
| 326 | + """Test remove server from multiple hosts.""" |
| 327 | + with patch('hatch.cli_hatch.MCPHostConfigurationManager') as mock_manager_class: |
| 328 | + mock_manager = MagicMock() |
| 329 | + mock_manager.remove_server.return_value = MagicMock(success=True, backup_path=None) |
| 330 | + mock_manager_class.return_value = mock_manager |
| 331 | + |
| 332 | + with patch('builtins.print') as mock_print: |
| 333 | + result = handle_mcp_remove_server('test-server', 'claude-desktop,cursor', auto_approve=True) |
| 334 | + |
| 335 | + self.assertEqual(result, 0) |
| 336 | + self.assertEqual(mock_manager.remove_server.call_count, 2) |
| 337 | + |
| 338 | + # Verify success messages |
| 339 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 340 | + self.assertTrue(any("[SUCCESS] Successfully removed 'test-server' from 'claude-desktop'" in call for call in print_calls)) |
| 341 | + self.assertTrue(any("[SUCCESS] Successfully removed 'test-server' from 'cursor'" in call for call in print_calls)) |
| 342 | + |
| 343 | + @integration_test(scope="component") |
| 344 | + def test_remove_server_no_host_specified(self): |
| 345 | + """Test remove server with no host specified.""" |
| 346 | + with patch('builtins.print') as mock_print: |
| 347 | + result = handle_mcp_remove_server('test-server') |
| 348 | + |
| 349 | + self.assertEqual(result, 1) |
| 350 | + |
| 351 | + # Verify error message |
| 352 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 353 | + self.assertTrue(any("Error: Must specify either --host or --env" in call for call in print_calls)) |
| 354 | + |
| 355 | + @integration_test(scope="component") |
| 356 | + def test_remove_server_dry_run(self): |
| 357 | + """Test remove server dry run functionality.""" |
| 358 | + with patch('builtins.print') as mock_print: |
| 359 | + result = handle_mcp_remove_server('test-server', 'claude-desktop', dry_run=True) |
| 360 | + |
| 361 | + self.assertEqual(result, 0) |
| 362 | + |
| 363 | + # Verify dry run output |
| 364 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 365 | + self.assertTrue(any("[DRY RUN] Would remove MCP server 'test-server' from hosts: claude-desktop" in call for call in print_calls)) |
| 366 | + |
| 367 | + |
| 368 | +class TestMCPRemoveHostCommand(unittest.TestCase): |
| 369 | + """Test suite for MCP remove host command.""" |
| 370 | + |
| 371 | + @regression_test |
| 372 | + def test_remove_host_argument_parsing(self): |
| 373 | + """Test argument parsing for 'hatch mcp remove host' command.""" |
| 374 | + test_args = ['hatch', 'mcp', 'remove', 'host', 'claude-desktop', '--auto-approve'] |
| 375 | + |
| 376 | + with patch('sys.argv', test_args): |
| 377 | + with patch('hatch.cli_hatch.HatchEnvironmentManager'): |
| 378 | + with patch('hatch.cli_hatch.handle_mcp_remove_host', return_value=0) as mock_handler: |
| 379 | + try: |
| 380 | + main() |
| 381 | + mock_handler.assert_called_once_with('claude-desktop', False, False, True) |
| 382 | + except SystemExit as e: |
| 383 | + self.assertEqual(e.code, 0) |
| 384 | + |
| 385 | + @integration_test(scope="component") |
| 386 | + def test_remove_host_successful(self): |
| 387 | + """Test successful host configuration removal.""" |
| 388 | + with patch('hatch.cli_hatch.MCPHostConfigurationManager') as mock_manager_class: |
| 389 | + mock_manager = MagicMock() |
| 390 | + mock_result = MagicMock() |
| 391 | + mock_result.success = True |
| 392 | + mock_result.backup_path = Path("/test/backup.json") |
| 393 | + mock_manager.remove_host_configuration.return_value = mock_result |
| 394 | + mock_manager_class.return_value = mock_manager |
| 395 | + |
| 396 | + with patch('builtins.print') as mock_print: |
| 397 | + result = handle_mcp_remove_host('claude-desktop', auto_approve=True) |
| 398 | + |
| 399 | + self.assertEqual(result, 0) |
| 400 | + mock_manager.remove_host_configuration.assert_called_once_with( |
| 401 | + hostname='claude-desktop', no_backup=False |
| 402 | + ) |
| 403 | + |
| 404 | + # Verify success message |
| 405 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 406 | + self.assertTrue(any("[SUCCESS] Successfully removed host configuration for 'claude-desktop'" in call for call in print_calls)) |
| 407 | + |
| 408 | + @integration_test(scope="component") |
| 409 | + def test_remove_host_invalid_host(self): |
| 410 | + """Test remove host with invalid host type.""" |
| 411 | + with patch('builtins.print') as mock_print: |
| 412 | + result = handle_mcp_remove_host('invalid-host') |
| 413 | + |
| 414 | + self.assertEqual(result, 1) |
| 415 | + |
| 416 | + # Verify error message |
| 417 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 418 | + self.assertTrue(any("Error: Invalid host 'invalid-host'" in call for call in print_calls)) |
| 419 | + |
| 420 | + @integration_test(scope="component") |
| 421 | + def test_remove_host_dry_run(self): |
| 422 | + """Test remove host dry run functionality.""" |
| 423 | + with patch('builtins.print') as mock_print: |
| 424 | + result = handle_mcp_remove_host('claude-desktop', dry_run=True) |
| 425 | + |
| 426 | + self.assertEqual(result, 0) |
| 427 | + |
| 428 | + # Verify dry run output |
| 429 | + print_calls = [call[0][0] for call in mock_print.call_args_list] |
| 430 | + self.assertTrue(any("[DRY RUN] Would remove entire host configuration for 'claude-desktop'" in call for call in print_calls)) |
| 431 | + |
| 432 | + |
306 | 433 | if __name__ == '__main__': |
307 | 434 | unittest.main() |
0 commit comments