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
@@ -0,0 +1,138 @@
1
+ """Unit tests for MCP Host Adapter protocol compliance.
2
+
3
+ Test IDs: AP-01 to AP-06 (per 02-test_architecture_rebuild_v0.md)
4
+ Scope: Verify all adapters satisfy BaseAdapter protocol contract.
5
+ """
6
+
7
+ import unittest
8
+ from typing import Dict, Any
9
+
10
+ from hatch.mcp_host_config.models import MCPServerConfig, MCPHostType
11
+ from hatch.mcp_host_config.adapters import (
12
+ get_adapter,
13
+ BaseAdapter,
14
+ ClaudeAdapter,
15
+ CodexAdapter,
16
+ CursorAdapter,
17
+ GeminiAdapter,
18
+ KiroAdapter,
19
+ LMStudioAdapter,
20
+ VSCodeAdapter,
21
+ )
22
+
23
+
24
+ # All adapter classes to test
25
+ ALL_ADAPTERS = [
26
+ ClaudeAdapter,
27
+ CodexAdapter,
28
+ CursorAdapter,
29
+ GeminiAdapter,
30
+ KiroAdapter,
31
+ LMStudioAdapter,
32
+ VSCodeAdapter,
33
+ ]
34
+
35
+ # Map host types to their expected adapter classes
36
+ HOST_ADAPTER_MAP = {
37
+ MCPHostType.CLAUDE_DESKTOP: ClaudeAdapter,
38
+ MCPHostType.CLAUDE_CODE: ClaudeAdapter,
39
+ MCPHostType.CODEX: CodexAdapter,
40
+ MCPHostType.CURSOR: CursorAdapter,
41
+ MCPHostType.GEMINI: GeminiAdapter,
42
+ MCPHostType.KIRO: KiroAdapter,
43
+ MCPHostType.LMSTUDIO: LMStudioAdapter,
44
+ MCPHostType.VSCODE: VSCodeAdapter,
45
+ }
46
+
47
+
48
+ class TestAdapterProtocol(unittest.TestCase):
49
+ """Tests for adapter protocol compliance (AP-01 to AP-06)."""
50
+
51
+ def test_AP01_all_adapters_have_get_supported_fields(self):
52
+ """AP-01: All adapters have `get_supported_fields()` returning frozenset."""
53
+ for adapter_cls in ALL_ADAPTERS:
54
+ adapter = adapter_cls()
55
+ with self.subTest(adapter=adapter_cls.__name__):
56
+ self.assertTrue(
57
+ hasattr(adapter, 'get_supported_fields'),
58
+ f"{adapter_cls.__name__} missing 'get_supported_fields'"
59
+ )
60
+ self.assertTrue(
61
+ callable(adapter.get_supported_fields),
62
+ f"{adapter_cls.__name__}.get_supported_fields is not callable"
63
+ )
64
+ supported = adapter.get_supported_fields()
65
+ self.assertIsInstance(
66
+ supported, frozenset,
67
+ f"{adapter_cls.__name__}.get_supported_fields() did not return frozenset"
68
+ )
69
+
70
+ def test_AP02_all_adapters_have_validate(self):
71
+ """AP-02: All adapters have callable `validate()` method."""
72
+ for adapter_cls in ALL_ADAPTERS:
73
+ adapter = adapter_cls()
74
+ with self.subTest(adapter=adapter_cls.__name__):
75
+ self.assertTrue(
76
+ hasattr(adapter, 'validate'),
77
+ f"{adapter_cls.__name__} missing 'validate'"
78
+ )
79
+ self.assertTrue(
80
+ callable(adapter.validate),
81
+ f"{adapter_cls.__name__}.validate is not callable"
82
+ )
83
+
84
+ def test_AP03_all_adapters_have_serialize(self):
85
+ """AP-03: All adapters have callable `serialize()` method."""
86
+ for adapter_cls in ALL_ADAPTERS:
87
+ adapter = adapter_cls()
88
+ with self.subTest(adapter=adapter_cls.__name__):
89
+ self.assertTrue(
90
+ hasattr(adapter, 'serialize'),
91
+ f"{adapter_cls.__name__} missing 'serialize'"
92
+ )
93
+ self.assertTrue(
94
+ callable(adapter.serialize),
95
+ f"{adapter_cls.__name__}.serialize is not callable"
96
+ )
97
+
98
+ def test_AP04_serialize_never_returns_name(self):
99
+ """AP-04: `serialize()` never returns `name` field for any adapter."""
100
+ config = MCPServerConfig(name="test-server", command="python")
101
+
102
+ for adapter_cls in ALL_ADAPTERS:
103
+ adapter = adapter_cls()
104
+ with self.subTest(adapter=adapter_cls.__name__):
105
+ result = adapter.serialize(config)
106
+ self.assertNotIn(
107
+ "name", result,
108
+ f"{adapter_cls.__name__}.serialize() returned 'name' field"
109
+ )
110
+
111
+ def test_AP05_serialize_never_returns_none_values(self):
112
+ """AP-05: `serialize()` returns no None values."""
113
+ config = MCPServerConfig(name="test-server", command="python")
114
+
115
+ for adapter_cls in ALL_ADAPTERS:
116
+ adapter = adapter_cls()
117
+ with self.subTest(adapter=adapter_cls.__name__):
118
+ result = adapter.serialize(config)
119
+ for key, value in result.items():
120
+ self.assertIsNotNone(
121
+ value,
122
+ f"{adapter_cls.__name__}.serialize() returned None for '{key}'"
123
+ )
124
+
125
+ def test_AP06_get_adapter_returns_correct_type(self):
126
+ """AP-06: get_adapter() returns correct adapter for each host type."""
127
+ for host_type, expected_cls in HOST_ADAPTER_MAP.items():
128
+ with self.subTest(host=host_type.value):
129
+ adapter = get_adapter(host_type)
130
+ self.assertIsInstance(
131
+ adapter, expected_cls,
132
+ f"get_adapter({host_type}) returned {type(adapter)}, expected {expected_cls}"
133
+ )
134
+
135
+
136
+ if __name__ == "__main__":
137
+ unittest.main()
138
+
@@ -0,0 +1,158 @@
1
+ """Unit tests for adapter registry.
2
+
3
+ Test IDs: AR-01 to AR-08 (per 02-test_architecture_rebuild_v0.md)
4
+ Scope: Registry initialization, adapter lookup, registration.
5
+ """
6
+
7
+ import unittest
8
+
9
+ from hatch.mcp_host_config.adapters import (
10
+ AdapterRegistry,
11
+ get_adapter,
12
+ get_default_registry,
13
+ BaseAdapter,
14
+ ClaudeAdapter,
15
+ CodexAdapter,
16
+ CursorAdapter,
17
+ GeminiAdapter,
18
+ KiroAdapter,
19
+ LMStudioAdapter,
20
+ VSCodeAdapter,
21
+ )
22
+
23
+
24
+ class TestAdapterRegistry(unittest.TestCase):
25
+ """Tests for AdapterRegistry class (AR-01 to AR-08)."""
26
+
27
+ def setUp(self):
28
+ """Create a fresh registry for each test."""
29
+ self.registry = AdapterRegistry()
30
+
31
+ def test_AR01_registry_has_all_default_hosts(self):
32
+ """AR-01: Registry initializes with all default host adapters."""
33
+ expected_hosts = {
34
+ "claude-desktop",
35
+ "claude-code",
36
+ "codex",
37
+ "cursor",
38
+ "gemini",
39
+ "kiro",
40
+ "lmstudio",
41
+ "vscode",
42
+ }
43
+
44
+ actual_hosts = set(self.registry.get_supported_hosts())
45
+
46
+ self.assertEqual(actual_hosts, expected_hosts)
47
+
48
+ def test_AR02_get_adapter_returns_correct_type(self):
49
+ """AR-02: get_adapter() returns adapter with matching host_name."""
50
+ test_cases = [
51
+ ("claude-desktop", ClaudeAdapter),
52
+ ("claude-code", ClaudeAdapter),
53
+ ("codex", CodexAdapter),
54
+ ("cursor", CursorAdapter),
55
+ ("gemini", GeminiAdapter),
56
+ ("kiro", KiroAdapter),
57
+ ("lmstudio", LMStudioAdapter),
58
+ ("vscode", VSCodeAdapter),
59
+ ]
60
+
61
+ for host_name, expected_cls in test_cases:
62
+ with self.subTest(host=host_name):
63
+ adapter = self.registry.get_adapter(host_name)
64
+ self.assertIsInstance(adapter, expected_cls)
65
+ self.assertEqual(adapter.host_name, host_name)
66
+
67
+ def test_AR03_get_adapter_raises_for_unknown_host(self):
68
+ """AR-03: get_adapter() raises KeyError for unknown host."""
69
+ with self.assertRaises(KeyError) as context:
70
+ self.registry.get_adapter("unknown-host")
71
+
72
+ self.assertIn("unknown-host", str(context.exception))
73
+ self.assertIn("Supported hosts", str(context.exception))
74
+
75
+ def test_AR04_has_adapter_returns_true_for_registered(self):
76
+ """AR-04: has_adapter() returns True for registered hosts."""
77
+ for host_name in self.registry.get_supported_hosts():
78
+ with self.subTest(host=host_name):
79
+ self.assertTrue(self.registry.has_adapter(host_name))
80
+
81
+ def test_AR05_has_adapter_returns_false_for_unknown(self):
82
+ """AR-05: has_adapter() returns False for unknown hosts."""
83
+ self.assertFalse(self.registry.has_adapter("unknown-host"))
84
+
85
+ def test_AR06_register_adds_new_adapter(self):
86
+ """AR-06: register() adds a new adapter to registry."""
87
+ # Create a custom adapter for testing
88
+ class CustomAdapter(BaseAdapter):
89
+ @property
90
+ def host_name(self):
91
+ return "custom-host"
92
+
93
+ def get_supported_fields(self):
94
+ return frozenset({"command", "args"})
95
+
96
+ def validate(self, config):
97
+ pass
98
+
99
+ def serialize(self, config):
100
+ return {"command": config.command}
101
+
102
+ custom = CustomAdapter()
103
+ self.registry.register(custom)
104
+
105
+ self.assertTrue(self.registry.has_adapter("custom-host"))
106
+ self.assertIs(self.registry.get_adapter("custom-host"), custom)
107
+
108
+ def test_AR07_register_raises_for_duplicate(self):
109
+ """AR-07: register() raises ValueError for duplicate host name."""
110
+ # Try to register another Claude adapter
111
+ duplicate = ClaudeAdapter(variant="desktop")
112
+
113
+ with self.assertRaises(ValueError) as context:
114
+ self.registry.register(duplicate)
115
+
116
+ self.assertIn("claude-desktop", str(context.exception))
117
+ self.assertIn("already registered", str(context.exception))
118
+
119
+ def test_AR08_unregister_removes_adapter(self):
120
+ """AR-08: unregister() removes adapter from registry."""
121
+ self.assertTrue(self.registry.has_adapter("claude-desktop"))
122
+
123
+ self.registry.unregister("claude-desktop")
124
+
125
+ self.assertFalse(self.registry.has_adapter("claude-desktop"))
126
+
127
+ def test_unregister_raises_for_unknown(self):
128
+ """unregister() raises KeyError for unknown host."""
129
+ with self.assertRaises(KeyError):
130
+ self.registry.unregister("unknown-host")
131
+
132
+
133
+ class TestGlobalRegistryFunctions(unittest.TestCase):
134
+ """Tests for global registry convenience functions."""
135
+
136
+ def test_get_default_registry_returns_singleton(self):
137
+ """get_default_registry() returns same instance on multiple calls."""
138
+ registry1 = get_default_registry()
139
+ registry2 = get_default_registry()
140
+
141
+ self.assertIs(registry1, registry2)
142
+
143
+ def test_get_adapter_uses_default_registry(self):
144
+ """get_adapter() function uses the default registry."""
145
+ adapter = get_adapter("claude-desktop")
146
+
147
+ self.assertIsInstance(adapter, ClaudeAdapter)
148
+ self.assertEqual(adapter.host_name, "claude-desktop")
149
+
150
+ def test_get_adapter_raises_for_unknown(self):
151
+ """get_adapter() function raises KeyError for unknown host."""
152
+ with self.assertRaises(KeyError):
153
+ get_adapter("unknown-host")
154
+
155
+
156
+ if __name__ == "__main__":
157
+ unittest.main()
158
+
@@ -0,0 +1,146 @@
1
+ """Unit tests for MCPServerConfig unified model.
2
+
3
+ Test IDs: UM-01 to UM-07 (per 02-test_architecture_rebuild_v0.md)
4
+ Scope: Unified model validation, field defaults, transport configuration.
5
+ """
6
+
7
+ import unittest
8
+ from pydantic import ValidationError
9
+
10
+ from hatch.mcp_host_config.models import MCPServerConfig
11
+
12
+
13
+ class TestMCPServerConfig(unittest.TestCase):
14
+ """Tests for MCPServerConfig unified model (UM-01 to UM-07)."""
15
+
16
+ def test_UM01_valid_stdio_config(self):
17
+ """UM-01: Valid stdio config with command field."""
18
+ config = MCPServerConfig(name="test", command="python")
19
+
20
+ self.assertEqual(config.command, "python")
21
+ self.assertTrue(config.is_local_server)
22
+ self.assertFalse(config.is_remote_server)
23
+
24
+ def test_UM02_valid_sse_config(self):
25
+ """UM-02: Valid SSE config with url field."""
26
+ config = MCPServerConfig(name="test", url="https://example.com/mcp")
27
+
28
+ self.assertEqual(config.url, "https://example.com/mcp")
29
+ self.assertFalse(config.is_local_server)
30
+ self.assertTrue(config.is_remote_server)
31
+
32
+ def test_UM03_valid_http_config_gemini(self):
33
+ """UM-03: Valid HTTP config with httpUrl field (Gemini-style)."""
34
+ config = MCPServerConfig(name="test", httpUrl="https://example.com/http")
35
+
36
+ self.assertEqual(config.httpUrl, "https://example.com/http")
37
+ # httpUrl is considered remote
38
+ self.assertTrue(config.is_remote_server)
39
+
40
+ def test_UM04_allows_command_and_url(self):
41
+ """UM-04: Unified model allows both command and url (adapters validate)."""
42
+ # The unified model is permissive - adapters enforce host-specific rules
43
+ config = MCPServerConfig(name="test", command="python", url="https://example.com")
44
+
45
+ self.assertEqual(config.command, "python")
46
+ self.assertEqual(config.url, "https://example.com")
47
+
48
+ def test_UM05_reject_no_transport(self):
49
+ """UM-05: Reject config with no transport specified."""
50
+ with self.assertRaises(ValidationError) as context:
51
+ MCPServerConfig(name="test")
52
+
53
+ self.assertIn("At least one transport must be specified", str(context.exception))
54
+
55
+ def test_UM06_accept_all_fields(self):
56
+ """UM-06: Accept config with many fields set."""
57
+ config = MCPServerConfig(
58
+ name="full-server",
59
+ command="python",
60
+ args=["-m", "server"],
61
+ env={"API_KEY": "secret"},
62
+ type="stdio",
63
+ cwd="/workspace",
64
+ timeout=30000,
65
+ )
66
+
67
+ self.assertEqual(config.name, "full-server")
68
+ self.assertEqual(config.args, ["-m", "server"])
69
+ self.assertEqual(config.env, {"API_KEY": "secret"})
70
+ self.assertEqual(config.type, "stdio")
71
+ self.assertEqual(config.cwd, "/workspace")
72
+ self.assertEqual(config.timeout, 30000)
73
+
74
+ def test_UM07_extra_fields_allowed(self):
75
+ """UM-07: Extra/unknown fields are allowed (extra='allow')."""
76
+ # Create config with extra fields via model_construct to bypass validation
77
+ config = MCPServerConfig.model_construct(
78
+ name="test",
79
+ command="python",
80
+ unknown_field="value"
81
+ )
82
+
83
+ # The model should allow extra fields
84
+ self.assertEqual(config.command, "python")
85
+
86
+ def test_url_format_validation(self):
87
+ """Test URL format validation - must start with http:// or https://."""
88
+ with self.assertRaises(ValidationError) as context:
89
+ MCPServerConfig(name="test", url="ftp://example.com")
90
+
91
+ self.assertIn("URL must start with http:// or https://", str(context.exception))
92
+
93
+ def test_command_whitespace_stripped(self):
94
+ """Test command field strips leading/trailing whitespace."""
95
+ config = MCPServerConfig(name="test", command=" python ")
96
+
97
+ self.assertEqual(config.command, "python")
98
+
99
+ def test_command_empty_rejected(self):
100
+ """Test empty command (after stripping) is rejected."""
101
+ with self.assertRaises(ValidationError):
102
+ MCPServerConfig(name="test", command=" ")
103
+
104
+ def test_serialization_roundtrip(self):
105
+ """Test JSON serialization roundtrip."""
106
+ config = MCPServerConfig(
107
+ name="roundtrip-test",
108
+ command="python",
109
+ args=["server.py"],
110
+ env={"KEY": "value"},
111
+ )
112
+
113
+ # Serialize to dict
114
+ data = config.model_dump(exclude_none=True)
115
+
116
+ # Reconstruct from dict
117
+ reconstructed = MCPServerConfig.model_validate(data)
118
+
119
+ self.assertEqual(reconstructed.name, config.name)
120
+ self.assertEqual(reconstructed.command, config.command)
121
+ self.assertEqual(reconstructed.args, config.args)
122
+ self.assertEqual(reconstructed.env, config.env)
123
+
124
+
125
+ class TestMCPServerConfigProperties(unittest.TestCase):
126
+ """Tests for MCPServerConfig computed properties."""
127
+
128
+ def test_is_local_server_with_command(self):
129
+ """Local server detection with command."""
130
+ config = MCPServerConfig(name="test", command="python")
131
+ self.assertTrue(config.is_local_server)
132
+
133
+ def test_is_remote_server_with_url(self):
134
+ """Remote server detection with url."""
135
+ config = MCPServerConfig(name="test", url="https://example.com")
136
+ self.assertTrue(config.is_remote_server)
137
+
138
+ def test_is_remote_server_with_httpUrl(self):
139
+ """Remote server detection with httpUrl."""
140
+ config = MCPServerConfig(name="test", httpUrl="https://example.com/http")
141
+ self.assertTrue(config.is_remote_server)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ unittest.main()
146
+
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- hatch = hatch.cli_hatch:main
@@ -1,153 +0,0 @@
1
- """
2
- Kiro MCP Integration Tests
3
-
4
- End-to-end integration tests combining CLI, model conversion, and strategy operations.
5
- """
6
-
7
- import unittest
8
- from unittest.mock import patch, MagicMock
9
-
10
- from wobble.decorators import integration_test
11
-
12
- from hatch.cli_hatch import handle_mcp_configure
13
- from hatch.mcp_host_config.models import (
14
- HOST_MODEL_REGISTRY,
15
- MCPHostType,
16
- MCPServerConfigKiro
17
- )
18
-
19
-
20
- class TestKiroIntegration(unittest.TestCase):
21
- """Test suite for end-to-end Kiro integration."""
22
-
23
- @integration_test(scope="component")
24
- @patch('hatch.cli_hatch.MCPHostConfigurationManager')
25
- def test_kiro_end_to_end_configuration(self, mock_manager_class):
26
- """Test complete Kiro configuration workflow."""
27
- # Setup mocks
28
- mock_manager = MagicMock()
29
- mock_manager_class.return_value = mock_manager
30
-
31
- mock_result = MagicMock()
32
- mock_result.success = True
33
- mock_manager.configure_server.return_value = mock_result
34
-
35
- # Execute CLI command with Kiro-specific arguments
36
- result = handle_mcp_configure(
37
- host='kiro',
38
- server_name='augment-server',
39
- command='auggie',
40
- args=['--mcp', '-m', 'default'],
41
- disabled=False,
42
- auto_approve_tools=['codebase-retrieval', 'fetch'],
43
- disable_tools=['dangerous-tool'],
44
- auto_approve=True
45
- )
46
-
47
- # Verify success
48
- self.assertEqual(result, 0)
49
-
50
- # Verify configuration manager was called
51
- mock_manager.configure_server.assert_called_once()
52
-
53
- # Verify server configuration
54
- call_args = mock_manager.configure_server.call_args
55
- server_config = call_args.kwargs['server_config']
56
-
57
- # Verify all Kiro-specific fields
58
- self.assertFalse(server_config.disabled)
59
- self.assertEqual(len(server_config.autoApprove), 2)
60
- self.assertEqual(len(server_config.disabledTools), 1)
61
- self.assertIn('codebase-retrieval', server_config.autoApprove)
62
- self.assertIn('dangerous-tool', server_config.disabledTools)
63
-
64
- @integration_test(scope="system")
65
- def test_kiro_host_model_registry_integration(self):
66
- """Test Kiro integration with HOST_MODEL_REGISTRY."""
67
- # Verify Kiro is in registry
68
- self.assertIn(MCPHostType.KIRO, HOST_MODEL_REGISTRY)
69
-
70
- # Verify correct model class
71
- model_class = HOST_MODEL_REGISTRY[MCPHostType.KIRO]
72
- self.assertEqual(model_class.__name__, "MCPServerConfigKiro")
73
-
74
- # Test model instantiation
75
- model_instance = model_class(
76
- name="test-server",
77
- command="auggie",
78
- disabled=True
79
- )
80
- self.assertTrue(model_instance.disabled)
81
-
82
- @integration_test(scope="component")
83
- def test_kiro_model_to_strategy_workflow(self):
84
- """Test workflow from model creation to strategy operations."""
85
- # Import to trigger registration
86
- import hatch.mcp_host_config.strategies
87
- from hatch.mcp_host_config.host_management import MCPHostRegistry
88
-
89
- # Create Kiro model
90
- kiro_model = MCPServerConfigKiro(
91
- name="workflow-test",
92
- command="auggie",
93
- args=["--mcp"],
94
- disabled=False,
95
- autoApprove=["codebase-retrieval"]
96
- )
97
-
98
- # Get Kiro strategy
99
- strategy = MCPHostRegistry.get_strategy(MCPHostType.KIRO)
100
-
101
- # Verify strategy can validate the model
102
- self.assertTrue(strategy.validate_server_config(kiro_model))
103
-
104
- # Verify model fields are accessible
105
- self.assertEqual(kiro_model.command, "auggie")
106
- self.assertFalse(kiro_model.disabled)
107
- self.assertIn("codebase-retrieval", kiro_model.autoApprove)
108
-
109
- @integration_test(scope="end_to_end")
110
- @patch('hatch.cli_hatch.MCPHostConfigurationManager')
111
- def test_kiro_complete_lifecycle(self, mock_manager_class):
112
- """Test complete Kiro server lifecycle: create, configure, validate."""
113
- # Setup mocks
114
- mock_manager = MagicMock()
115
- mock_manager_class.return_value = mock_manager
116
-
117
- mock_result = MagicMock()
118
- mock_result.success = True
119
- mock_manager.configure_server.return_value = mock_result
120
-
121
- # Step 1: Configure server via CLI
122
- result = handle_mcp_configure(
123
- host='kiro',
124
- server_name='lifecycle-test',
125
- command='auggie',
126
- args=['--mcp', '-w', '.'],
127
- disabled=False,
128
- auto_approve_tools=['codebase-retrieval'],
129
- auto_approve=True
130
- )
131
-
132
- # Verify CLI success
133
- self.assertEqual(result, 0)
134
-
135
- # Step 2: Verify configuration manager interaction
136
- mock_manager.configure_server.assert_called_once()
137
- call_args = mock_manager.configure_server.call_args
138
-
139
- # Step 3: Verify server configuration structure
140
- server_config = call_args.kwargs['server_config']
141
- self.assertEqual(server_config.name, 'lifecycle-test')
142
- self.assertEqual(server_config.command, 'auggie')
143
- self.assertIn('--mcp', server_config.args)
144
- self.assertIn('-w', server_config.args)
145
- self.assertFalse(server_config.disabled)
146
- self.assertIn('codebase-retrieval', server_config.autoApprove)
147
-
148
- # Step 4: Verify model type
149
- self.assertIsInstance(server_config, MCPServerConfigKiro)
150
-
151
-
152
- if __name__ == '__main__':
153
- unittest.main()