hatch-xclam 0.7.0.dev12__py3-none-any.whl → 0.7.1__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/cli_hatch.py +120 -18
- hatch/mcp_host_config/__init__.py +4 -2
- hatch/mcp_host_config/backup.py +62 -31
- hatch/mcp_host_config/models.py +125 -1
- hatch/mcp_host_config/strategies.py +268 -1
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/METADATA +41 -32
- hatch_xclam-0.7.1.dist-info/RECORD +105 -0
- hatch_xclam-0.7.1.dist-info/top_level.txt +2 -0
- tests/integration/__init__.py +5 -0
- tests/integration/test_mcp_kiro_integration.py +153 -0
- tests/regression/__init__.py +5 -0
- tests/regression/test_mcp_codex_backup_integration.py +162 -0
- tests/regression/test_mcp_codex_host_strategy.py +163 -0
- tests/regression/test_mcp_codex_model_validation.py +117 -0
- tests/regression/test_mcp_kiro_backup_integration.py +241 -0
- tests/regression/test_mcp_kiro_cli_integration.py +141 -0
- tests/regression/test_mcp_kiro_decorator_registration.py +71 -0
- tests/regression/test_mcp_kiro_host_strategy.py +214 -0
- tests/regression/test_mcp_kiro_model_validation.py +116 -0
- tests/regression/test_mcp_kiro_omni_conversion.py +104 -0
- tests/test_data_utils.py +108 -0
- tests/test_mcp_cli_all_host_specific_args.py +194 -1
- tests/test_mcp_cli_direct_management.py +8 -5
- hatch_xclam-0.7.0.dev12.dist-info/RECORD +0 -152
- hatch_xclam-0.7.0.dev12.dist-info/top_level.txt +0 -3
- node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py +0 -45
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -365
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1272
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -152
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -270
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -704
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -709
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -173
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -169
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -113
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -805
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1172
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1319
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -128
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -104
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -462
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -56
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2745
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3976
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2965
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -67
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1391
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -26
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3112
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -99
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -767
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1260
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -373
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation_test.py +0 -54
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -303
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3196
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/__init__.py +0 -15
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_elffile.py +0 -108
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_manylinux.py +0 -252
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_musllinux.py +0 -83
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_parser.py +0 -359
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_structures.py +0 -61
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_tokenizer.py +0 -192
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/markers.py +0 -252
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/metadata.py +0 -825
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/py.typed +0 -0
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/requirements.py +0 -90
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/specifiers.py +0 -1030
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/tags.py +0 -553
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/utils.py +0 -172
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/version.py +0 -563
- node_modules/npm/node_modules/node-gyp/gyp/test_gyp.py +0 -261
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/WHEEL +0 -0
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/entry_points.txt +0 -0
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Tests for Kiro MCP backup integration.
|
|
2
|
+
|
|
3
|
+
This module tests the integration between KiroHostStrategy and the backup system,
|
|
4
|
+
ensuring that Kiro configurations are properly backed up during write operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from unittest.mock import patch, MagicMock
|
|
12
|
+
|
|
13
|
+
from wobble.decorators import regression_test
|
|
14
|
+
|
|
15
|
+
from hatch.mcp_host_config.strategies import KiroHostStrategy
|
|
16
|
+
from hatch.mcp_host_config.models import HostConfiguration, MCPServerConfig
|
|
17
|
+
from hatch.mcp_host_config.backup import MCPHostConfigBackupManager, BackupResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestKiroBackupIntegration(unittest.TestCase):
|
|
21
|
+
"""Test Kiro backup integration with host strategy."""
|
|
22
|
+
|
|
23
|
+
def setUp(self):
|
|
24
|
+
"""Set up test environment."""
|
|
25
|
+
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_kiro_backup_"))
|
|
26
|
+
self.config_dir = self.temp_dir / ".kiro" / "settings"
|
|
27
|
+
self.config_dir.mkdir(parents=True)
|
|
28
|
+
self.config_file = self.config_dir / "mcp.json"
|
|
29
|
+
|
|
30
|
+
self.backup_dir = self.temp_dir / "backups"
|
|
31
|
+
self.backup_manager = MCPHostConfigBackupManager(backup_root=self.backup_dir)
|
|
32
|
+
|
|
33
|
+
self.strategy = KiroHostStrategy()
|
|
34
|
+
|
|
35
|
+
def tearDown(self):
|
|
36
|
+
"""Clean up test environment."""
|
|
37
|
+
import shutil
|
|
38
|
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
39
|
+
|
|
40
|
+
@regression_test
|
|
41
|
+
def test_write_configuration_creates_backup_by_default(self):
|
|
42
|
+
"""Test that write_configuration creates backup by default when file exists."""
|
|
43
|
+
# Create initial configuration
|
|
44
|
+
initial_config = {
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"existing-server": {
|
|
47
|
+
"command": "uvx",
|
|
48
|
+
"args": ["existing-package"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"otherSettings": {
|
|
52
|
+
"theme": "dark"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
with open(self.config_file, 'w') as f:
|
|
57
|
+
json.dump(initial_config, f, indent=2)
|
|
58
|
+
|
|
59
|
+
# Create new configuration to write
|
|
60
|
+
server_config = MCPServerConfig(
|
|
61
|
+
command="uvx",
|
|
62
|
+
args=["new-package"]
|
|
63
|
+
)
|
|
64
|
+
host_config = HostConfiguration(servers={"new-server": server_config})
|
|
65
|
+
|
|
66
|
+
# Mock the strategy's get_config_path to return our test file
|
|
67
|
+
# Mock the backup manager creation to use our test backup manager
|
|
68
|
+
with patch.object(self.strategy, 'get_config_path', return_value=self.config_file), \
|
|
69
|
+
patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager', return_value=self.backup_manager):
|
|
70
|
+
# Write configuration (should create backup)
|
|
71
|
+
result = self.strategy.write_configuration(host_config, no_backup=False)
|
|
72
|
+
|
|
73
|
+
# Verify write succeeded
|
|
74
|
+
self.assertTrue(result)
|
|
75
|
+
|
|
76
|
+
# Verify backup was created
|
|
77
|
+
backups = self.backup_manager.list_backups("kiro")
|
|
78
|
+
self.assertEqual(len(backups), 1)
|
|
79
|
+
|
|
80
|
+
# Verify backup contains original content
|
|
81
|
+
backup_content = json.loads(backups[0].file_path.read_text())
|
|
82
|
+
self.assertEqual(backup_content, initial_config)
|
|
83
|
+
|
|
84
|
+
# Verify new configuration was written
|
|
85
|
+
new_content = json.loads(self.config_file.read_text())
|
|
86
|
+
self.assertIn("new-server", new_content["mcpServers"])
|
|
87
|
+
self.assertEqual(new_content["otherSettings"], {"theme": "dark"}) # Preserved
|
|
88
|
+
|
|
89
|
+
@regression_test
|
|
90
|
+
def test_write_configuration_skips_backup_when_requested(self):
|
|
91
|
+
"""Test that write_configuration skips backup when no_backup=True."""
|
|
92
|
+
# Create initial configuration
|
|
93
|
+
initial_config = {
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"existing-server": {
|
|
96
|
+
"command": "uvx",
|
|
97
|
+
"args": ["existing-package"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
with open(self.config_file, 'w') as f:
|
|
103
|
+
json.dump(initial_config, f, indent=2)
|
|
104
|
+
|
|
105
|
+
# Create new configuration to write
|
|
106
|
+
server_config = MCPServerConfig(
|
|
107
|
+
command="uvx",
|
|
108
|
+
args=["new-package"]
|
|
109
|
+
)
|
|
110
|
+
host_config = HostConfiguration(servers={"new-server": server_config})
|
|
111
|
+
|
|
112
|
+
# Mock the strategy's get_config_path to return our test file
|
|
113
|
+
# Mock the backup manager creation to use our test backup manager
|
|
114
|
+
with patch.object(self.strategy, 'get_config_path', return_value=self.config_file), \
|
|
115
|
+
patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager', return_value=self.backup_manager):
|
|
116
|
+
# Write configuration with no_backup=True
|
|
117
|
+
result = self.strategy.write_configuration(host_config, no_backup=True)
|
|
118
|
+
|
|
119
|
+
# Verify write succeeded
|
|
120
|
+
self.assertTrue(result)
|
|
121
|
+
|
|
122
|
+
# Verify no backup was created
|
|
123
|
+
backups = self.backup_manager.list_backups("kiro")
|
|
124
|
+
self.assertEqual(len(backups), 0)
|
|
125
|
+
|
|
126
|
+
# Verify new configuration was written
|
|
127
|
+
new_content = json.loads(self.config_file.read_text())
|
|
128
|
+
self.assertIn("new-server", new_content["mcpServers"])
|
|
129
|
+
|
|
130
|
+
@regression_test
|
|
131
|
+
def test_write_configuration_no_backup_for_new_file(self):
|
|
132
|
+
"""Test that no backup is created when writing to a new file."""
|
|
133
|
+
# Ensure config file doesn't exist
|
|
134
|
+
self.assertFalse(self.config_file.exists())
|
|
135
|
+
|
|
136
|
+
# Create configuration to write
|
|
137
|
+
server_config = MCPServerConfig(
|
|
138
|
+
command="uvx",
|
|
139
|
+
args=["new-package"]
|
|
140
|
+
)
|
|
141
|
+
host_config = HostConfiguration(servers={"new-server": server_config})
|
|
142
|
+
|
|
143
|
+
# Mock the strategy's get_config_path to return our test file
|
|
144
|
+
# Mock the backup manager creation to use our test backup manager
|
|
145
|
+
with patch.object(self.strategy, 'get_config_path', return_value=self.config_file), \
|
|
146
|
+
patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager', return_value=self.backup_manager):
|
|
147
|
+
# Write configuration
|
|
148
|
+
result = self.strategy.write_configuration(host_config, no_backup=False)
|
|
149
|
+
|
|
150
|
+
# Verify write succeeded
|
|
151
|
+
self.assertTrue(result)
|
|
152
|
+
|
|
153
|
+
# Verify no backup was created (file didn't exist)
|
|
154
|
+
backups = self.backup_manager.list_backups("kiro")
|
|
155
|
+
self.assertEqual(len(backups), 0)
|
|
156
|
+
|
|
157
|
+
# Verify configuration was written
|
|
158
|
+
self.assertTrue(self.config_file.exists())
|
|
159
|
+
new_content = json.loads(self.config_file.read_text())
|
|
160
|
+
self.assertIn("new-server", new_content["mcpServers"])
|
|
161
|
+
|
|
162
|
+
@regression_test
|
|
163
|
+
def test_backup_failure_prevents_write(self):
|
|
164
|
+
"""Test that backup failure prevents configuration write."""
|
|
165
|
+
# Create initial configuration
|
|
166
|
+
initial_config = {
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"existing-server": {
|
|
169
|
+
"command": "uvx",
|
|
170
|
+
"args": ["existing-package"]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
with open(self.config_file, 'w') as f:
|
|
176
|
+
json.dump(initial_config, f, indent=2)
|
|
177
|
+
|
|
178
|
+
# Create new configuration to write
|
|
179
|
+
server_config = MCPServerConfig(
|
|
180
|
+
command="uvx",
|
|
181
|
+
args=["new-package"]
|
|
182
|
+
)
|
|
183
|
+
host_config = HostConfiguration(servers={"new-server": server_config})
|
|
184
|
+
|
|
185
|
+
# Mock backup manager to fail
|
|
186
|
+
with patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager') as mock_backup_class:
|
|
187
|
+
mock_backup_manager = MagicMock()
|
|
188
|
+
mock_backup_manager.create_backup.return_value = BackupResult(
|
|
189
|
+
success=False,
|
|
190
|
+
error_message="Backup failed"
|
|
191
|
+
)
|
|
192
|
+
mock_backup_class.return_value = mock_backup_manager
|
|
193
|
+
|
|
194
|
+
# Mock the strategy's get_config_path to return our test file
|
|
195
|
+
with patch.object(self.strategy, 'get_config_path', return_value=self.config_file):
|
|
196
|
+
# Write configuration (should fail due to backup failure)
|
|
197
|
+
result = self.strategy.write_configuration(host_config, no_backup=False)
|
|
198
|
+
|
|
199
|
+
# Verify write failed
|
|
200
|
+
self.assertFalse(result)
|
|
201
|
+
|
|
202
|
+
# Verify original configuration is unchanged
|
|
203
|
+
current_content = json.loads(self.config_file.read_text())
|
|
204
|
+
self.assertEqual(current_content, initial_config)
|
|
205
|
+
|
|
206
|
+
@regression_test
|
|
207
|
+
def test_kiro_hostname_supported_in_backup_system(self):
|
|
208
|
+
"""Test that 'kiro' hostname is supported by the backup system."""
|
|
209
|
+
# Create test configuration file
|
|
210
|
+
test_config = {
|
|
211
|
+
"mcpServers": {
|
|
212
|
+
"test-server": {
|
|
213
|
+
"command": "uvx",
|
|
214
|
+
"args": ["test-package"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
with open(self.config_file, 'w') as f:
|
|
220
|
+
json.dump(test_config, f, indent=2)
|
|
221
|
+
|
|
222
|
+
# Test backup creation with 'kiro' hostname
|
|
223
|
+
result = self.backup_manager.create_backup(self.config_file, "kiro")
|
|
224
|
+
|
|
225
|
+
# Verify backup succeeded
|
|
226
|
+
self.assertTrue(result.success)
|
|
227
|
+
self.assertIsNotNone(result.backup_path)
|
|
228
|
+
self.assertTrue(result.backup_path.exists())
|
|
229
|
+
|
|
230
|
+
# Verify backup filename format
|
|
231
|
+
expected_pattern = r"mcp\.json\.kiro\.\d{8}_\d{6}_\d{6}"
|
|
232
|
+
import re
|
|
233
|
+
self.assertRegex(result.backup_path.name, expected_pattern)
|
|
234
|
+
|
|
235
|
+
# Verify backup content
|
|
236
|
+
backup_content = json.loads(result.backup_path.read_text())
|
|
237
|
+
self.assertEqual(backup_content, test_config)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == '__main__':
|
|
241
|
+
unittest.main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP CLI Integration Tests
|
|
3
|
+
|
|
4
|
+
Tests for CLI argument parsing and integration with Kiro-specific arguments.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
from unittest.mock import patch, MagicMock
|
|
9
|
+
|
|
10
|
+
from wobble.decorators import regression_test
|
|
11
|
+
|
|
12
|
+
from hatch.cli_hatch import handle_mcp_configure
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestKiroCLIIntegration(unittest.TestCase):
|
|
16
|
+
"""Test suite for Kiro CLI argument integration."""
|
|
17
|
+
|
|
18
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
19
|
+
@regression_test
|
|
20
|
+
def test_kiro_cli_with_disabled_flag(self, mock_manager_class):
|
|
21
|
+
"""Test CLI with --disabled flag for Kiro."""
|
|
22
|
+
mock_manager = MagicMock()
|
|
23
|
+
mock_manager_class.return_value = mock_manager
|
|
24
|
+
|
|
25
|
+
mock_result = MagicMock()
|
|
26
|
+
mock_result.success = True
|
|
27
|
+
mock_result.backup_path = None
|
|
28
|
+
mock_manager.configure_server.return_value = mock_result
|
|
29
|
+
|
|
30
|
+
result = handle_mcp_configure(
|
|
31
|
+
host='kiro',
|
|
32
|
+
server_name='test-server',
|
|
33
|
+
command='auggie',
|
|
34
|
+
args=['--mcp'],
|
|
35
|
+
disabled=True, # Kiro-specific argument
|
|
36
|
+
auto_approve=True
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.assertEqual(result, 0)
|
|
40
|
+
|
|
41
|
+
# Verify configure_server was called with Kiro model
|
|
42
|
+
mock_manager.configure_server.assert_called_once()
|
|
43
|
+
call_args = mock_manager.configure_server.call_args
|
|
44
|
+
server_config = call_args.kwargs['server_config']
|
|
45
|
+
|
|
46
|
+
# Verify Kiro-specific field was set
|
|
47
|
+
self.assertTrue(server_config.disabled)
|
|
48
|
+
|
|
49
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
50
|
+
@regression_test
|
|
51
|
+
def test_kiro_cli_with_auto_approve_tools(self, mock_manager_class):
|
|
52
|
+
"""Test CLI with --auto-approve-tools for Kiro."""
|
|
53
|
+
mock_manager = MagicMock()
|
|
54
|
+
mock_manager_class.return_value = mock_manager
|
|
55
|
+
|
|
56
|
+
mock_result = MagicMock()
|
|
57
|
+
mock_result.success = True
|
|
58
|
+
mock_manager.configure_server.return_value = mock_result
|
|
59
|
+
|
|
60
|
+
result = handle_mcp_configure(
|
|
61
|
+
host='kiro',
|
|
62
|
+
server_name='test-server',
|
|
63
|
+
command='auggie',
|
|
64
|
+
args=['--mcp'], # Required parameter
|
|
65
|
+
auto_approve_tools=['codebase-retrieval', 'fetch'],
|
|
66
|
+
auto_approve=True
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.assertEqual(result, 0)
|
|
70
|
+
|
|
71
|
+
# Verify autoApprove field was set correctly
|
|
72
|
+
call_args = mock_manager.configure_server.call_args
|
|
73
|
+
server_config = call_args.kwargs['server_config']
|
|
74
|
+
self.assertEqual(len(server_config.autoApprove), 2)
|
|
75
|
+
self.assertIn('codebase-retrieval', server_config.autoApprove)
|
|
76
|
+
|
|
77
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
78
|
+
@regression_test
|
|
79
|
+
def test_kiro_cli_with_disable_tools(self, mock_manager_class):
|
|
80
|
+
"""Test CLI with --disable-tools for Kiro."""
|
|
81
|
+
mock_manager = MagicMock()
|
|
82
|
+
mock_manager_class.return_value = mock_manager
|
|
83
|
+
|
|
84
|
+
mock_result = MagicMock()
|
|
85
|
+
mock_result.success = True
|
|
86
|
+
mock_manager.configure_server.return_value = mock_result
|
|
87
|
+
|
|
88
|
+
result = handle_mcp_configure(
|
|
89
|
+
host='kiro',
|
|
90
|
+
server_name='test-server',
|
|
91
|
+
command='python',
|
|
92
|
+
args=['server.py'], # Required parameter
|
|
93
|
+
disable_tools=['dangerous-tool', 'risky-tool'],
|
|
94
|
+
auto_approve=True
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.assertEqual(result, 0)
|
|
98
|
+
|
|
99
|
+
# Verify disabledTools field was set correctly
|
|
100
|
+
call_args = mock_manager.configure_server.call_args
|
|
101
|
+
server_config = call_args.kwargs['server_config']
|
|
102
|
+
self.assertEqual(len(server_config.disabledTools), 2)
|
|
103
|
+
self.assertIn('dangerous-tool', server_config.disabledTools)
|
|
104
|
+
|
|
105
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
106
|
+
@regression_test
|
|
107
|
+
def test_kiro_cli_combined_arguments(self, mock_manager_class):
|
|
108
|
+
"""Test CLI with multiple Kiro-specific arguments combined."""
|
|
109
|
+
mock_manager = MagicMock()
|
|
110
|
+
mock_manager_class.return_value = mock_manager
|
|
111
|
+
|
|
112
|
+
mock_result = MagicMock()
|
|
113
|
+
mock_result.success = True
|
|
114
|
+
mock_manager.configure_server.return_value = mock_result
|
|
115
|
+
|
|
116
|
+
result = handle_mcp_configure(
|
|
117
|
+
host='kiro',
|
|
118
|
+
server_name='comprehensive-server',
|
|
119
|
+
command='auggie',
|
|
120
|
+
args=['--mcp', '-m', 'default'],
|
|
121
|
+
disabled=False,
|
|
122
|
+
auto_approve_tools=['codebase-retrieval'],
|
|
123
|
+
disable_tools=['dangerous-tool'],
|
|
124
|
+
auto_approve=True
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.assertEqual(result, 0)
|
|
128
|
+
|
|
129
|
+
# Verify all Kiro fields were set correctly
|
|
130
|
+
call_args = mock_manager.configure_server.call_args
|
|
131
|
+
server_config = call_args.kwargs['server_config']
|
|
132
|
+
|
|
133
|
+
self.assertFalse(server_config.disabled)
|
|
134
|
+
self.assertEqual(len(server_config.autoApprove), 1)
|
|
135
|
+
self.assertEqual(len(server_config.disabledTools), 1)
|
|
136
|
+
self.assertIn('codebase-retrieval', server_config.autoApprove)
|
|
137
|
+
self.assertIn('dangerous-tool', server_config.disabledTools)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == '__main__':
|
|
141
|
+
unittest.main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Decorator Registration Tests
|
|
3
|
+
|
|
4
|
+
Tests for automatic strategy registration via @register_host_strategy decorator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
from wobble.decorators import regression_test
|
|
10
|
+
|
|
11
|
+
from hatch.mcp_host_config.host_management import MCPHostRegistry
|
|
12
|
+
from hatch.mcp_host_config.models import MCPHostType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestKiroDecoratorRegistration(unittest.TestCase):
|
|
16
|
+
"""Test suite for Kiro decorator registration."""
|
|
17
|
+
|
|
18
|
+
@regression_test
|
|
19
|
+
def test_kiro_strategy_registration(self):
|
|
20
|
+
"""Test that KiroHostStrategy is properly registered."""
|
|
21
|
+
# Import strategies to trigger registration
|
|
22
|
+
import hatch.mcp_host_config.strategies
|
|
23
|
+
|
|
24
|
+
# Verify Kiro is registered
|
|
25
|
+
self.assertIn(MCPHostType.KIRO, MCPHostRegistry._strategies)
|
|
26
|
+
|
|
27
|
+
# Verify correct strategy class
|
|
28
|
+
strategy_class = MCPHostRegistry._strategies[MCPHostType.KIRO]
|
|
29
|
+
self.assertEqual(strategy_class.__name__, "KiroHostStrategy")
|
|
30
|
+
|
|
31
|
+
@regression_test
|
|
32
|
+
def test_kiro_strategy_instantiation(self):
|
|
33
|
+
"""Test that Kiro strategy can be instantiated."""
|
|
34
|
+
# Import strategies to trigger registration
|
|
35
|
+
import hatch.mcp_host_config.strategies
|
|
36
|
+
|
|
37
|
+
strategy = MCPHostRegistry.get_strategy(MCPHostType.KIRO)
|
|
38
|
+
|
|
39
|
+
# Verify strategy instance
|
|
40
|
+
self.assertIsNotNone(strategy)
|
|
41
|
+
self.assertEqual(strategy.__class__.__name__, "KiroHostStrategy")
|
|
42
|
+
|
|
43
|
+
@regression_test
|
|
44
|
+
def test_kiro_in_host_detection(self):
|
|
45
|
+
"""Test that Kiro appears in host detection."""
|
|
46
|
+
# Import strategies to trigger registration
|
|
47
|
+
import hatch.mcp_host_config.strategies
|
|
48
|
+
|
|
49
|
+
# Get all registered host types
|
|
50
|
+
registered_hosts = list(MCPHostRegistry._strategies.keys())
|
|
51
|
+
|
|
52
|
+
# Verify Kiro is included
|
|
53
|
+
self.assertIn(MCPHostType.KIRO, registered_hosts)
|
|
54
|
+
|
|
55
|
+
@regression_test
|
|
56
|
+
def test_kiro_registry_consistency(self):
|
|
57
|
+
"""Test that Kiro registration is consistent across calls."""
|
|
58
|
+
# Import strategies to trigger registration
|
|
59
|
+
import hatch.mcp_host_config.strategies
|
|
60
|
+
|
|
61
|
+
# Get strategy multiple times
|
|
62
|
+
strategy1 = MCPHostRegistry.get_strategy(MCPHostType.KIRO)
|
|
63
|
+
strategy2 = MCPHostRegistry.get_strategy(MCPHostType.KIRO)
|
|
64
|
+
|
|
65
|
+
# Verify same class (not necessarily same instance)
|
|
66
|
+
self.assertEqual(strategy1.__class__, strategy2.__class__)
|
|
67
|
+
self.assertEqual(strategy1.__class__.__name__, "KiroHostStrategy")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
unittest.main()
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Host Strategy Tests
|
|
3
|
+
|
|
4
|
+
Tests for KiroHostStrategy implementation including path resolution,
|
|
5
|
+
configuration read/write, and host detection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import json
|
|
10
|
+
from unittest.mock import patch, mock_open, MagicMock
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from wobble.decorators import regression_test
|
|
14
|
+
|
|
15
|
+
from hatch.mcp_host_config.strategies import KiroHostStrategy
|
|
16
|
+
from hatch.mcp_host_config.models import MCPServerConfig, HostConfiguration
|
|
17
|
+
|
|
18
|
+
# Import test data loader from local tests module
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
22
|
+
from test_data_utils import MCPHostConfigTestDataLoader
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestKiroHostStrategy(unittest.TestCase):
|
|
26
|
+
"""Test suite for KiroHostStrategy implementation."""
|
|
27
|
+
|
|
28
|
+
def setUp(self):
|
|
29
|
+
"""Set up test environment."""
|
|
30
|
+
self.strategy = KiroHostStrategy()
|
|
31
|
+
self.test_data_loader = MCPHostConfigTestDataLoader()
|
|
32
|
+
|
|
33
|
+
@regression_test
|
|
34
|
+
def test_kiro_config_path_resolution(self):
|
|
35
|
+
"""Test Kiro configuration path resolution."""
|
|
36
|
+
config_path = self.strategy.get_config_path()
|
|
37
|
+
|
|
38
|
+
# Verify path structure (use normalized path for cross-platform compatibility)
|
|
39
|
+
self.assertIsNotNone(config_path)
|
|
40
|
+
normalized_path = str(config_path).replace('\\', '/')
|
|
41
|
+
self.assertTrue(normalized_path.endswith('.kiro/settings/mcp.json'))
|
|
42
|
+
self.assertEqual(config_path.name, 'mcp.json')
|
|
43
|
+
|
|
44
|
+
@regression_test
|
|
45
|
+
def test_kiro_config_key(self):
|
|
46
|
+
"""Test Kiro configuration key."""
|
|
47
|
+
config_key = self.strategy.get_config_key()
|
|
48
|
+
self.assertEqual(config_key, "mcpServers")
|
|
49
|
+
|
|
50
|
+
@regression_test
|
|
51
|
+
def test_kiro_server_config_validation(self):
|
|
52
|
+
"""Test Kiro server configuration validation."""
|
|
53
|
+
# Test local server validation
|
|
54
|
+
local_config = MCPServerConfig(
|
|
55
|
+
command="auggie",
|
|
56
|
+
args=["--mcp"]
|
|
57
|
+
)
|
|
58
|
+
self.assertTrue(self.strategy.validate_server_config(local_config))
|
|
59
|
+
|
|
60
|
+
# Test remote server validation
|
|
61
|
+
remote_config = MCPServerConfig(
|
|
62
|
+
url="https://api.example.com/mcp"
|
|
63
|
+
)
|
|
64
|
+
self.assertTrue(self.strategy.validate_server_config(remote_config))
|
|
65
|
+
|
|
66
|
+
# Test invalid configuration (should raise ValidationError during creation)
|
|
67
|
+
with self.assertRaises(Exception): # Pydantic ValidationError
|
|
68
|
+
invalid_config = MCPServerConfig()
|
|
69
|
+
self.strategy.validate_server_config(invalid_config)
|
|
70
|
+
|
|
71
|
+
@patch('pathlib.Path.exists')
|
|
72
|
+
@regression_test
|
|
73
|
+
def test_kiro_host_availability_detection(self, mock_exists):
|
|
74
|
+
"""Test Kiro host availability detection."""
|
|
75
|
+
# Test when Kiro directory exists
|
|
76
|
+
mock_exists.return_value = True
|
|
77
|
+
self.assertTrue(self.strategy.is_host_available())
|
|
78
|
+
|
|
79
|
+
# Test when Kiro directory doesn't exist
|
|
80
|
+
mock_exists.return_value = False
|
|
81
|
+
self.assertFalse(self.strategy.is_host_available())
|
|
82
|
+
|
|
83
|
+
@patch('builtins.open', new_callable=mock_open)
|
|
84
|
+
@patch('pathlib.Path.exists')
|
|
85
|
+
@patch('json.load')
|
|
86
|
+
@regression_test
|
|
87
|
+
def test_kiro_read_configuration_success(self, mock_json_load, mock_exists, mock_file):
|
|
88
|
+
"""Test successful Kiro configuration reading."""
|
|
89
|
+
# Mock file exists and JSON content
|
|
90
|
+
mock_exists.return_value = True
|
|
91
|
+
mock_json_load.return_value = {
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"augment": {
|
|
94
|
+
"command": "auggie",
|
|
95
|
+
"args": ["--mcp", "-m", "default"],
|
|
96
|
+
"autoApprove": ["codebase-retrieval"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
config = self.strategy.read_configuration()
|
|
102
|
+
|
|
103
|
+
# Verify configuration structure
|
|
104
|
+
self.assertIsInstance(config, HostConfiguration)
|
|
105
|
+
self.assertIn("augment", config.servers)
|
|
106
|
+
|
|
107
|
+
server = config.servers["augment"]
|
|
108
|
+
self.assertEqual(server.command, "auggie")
|
|
109
|
+
self.assertEqual(len(server.args), 3)
|
|
110
|
+
|
|
111
|
+
@patch('pathlib.Path.exists')
|
|
112
|
+
@regression_test
|
|
113
|
+
def test_kiro_read_configuration_file_not_exists(self, mock_exists):
|
|
114
|
+
"""Test Kiro configuration reading when file doesn't exist."""
|
|
115
|
+
mock_exists.return_value = False
|
|
116
|
+
|
|
117
|
+
config = self.strategy.read_configuration()
|
|
118
|
+
|
|
119
|
+
# Should return empty configuration
|
|
120
|
+
self.assertIsInstance(config, HostConfiguration)
|
|
121
|
+
self.assertEqual(len(config.servers), 0)
|
|
122
|
+
|
|
123
|
+
@patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager')
|
|
124
|
+
@patch('hatch.mcp_host_config.strategies.AtomicFileOperations')
|
|
125
|
+
@patch('builtins.open', new_callable=mock_open)
|
|
126
|
+
@patch('pathlib.Path.exists')
|
|
127
|
+
@patch('pathlib.Path.mkdir')
|
|
128
|
+
@patch('json.load')
|
|
129
|
+
@regression_test
|
|
130
|
+
def test_kiro_write_configuration_success(self, mock_json_load, mock_mkdir,
|
|
131
|
+
mock_exists, mock_file, mock_atomic_ops_class, mock_backup_manager_class):
|
|
132
|
+
"""Test successful Kiro configuration writing."""
|
|
133
|
+
# Mock existing file with other settings
|
|
134
|
+
mock_exists.return_value = True
|
|
135
|
+
mock_json_load.return_value = {
|
|
136
|
+
"otherSettings": {"theme": "dark"},
|
|
137
|
+
"mcpServers": {}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Mock backup and atomic operations
|
|
141
|
+
mock_backup_manager = MagicMock()
|
|
142
|
+
mock_backup_manager_class.return_value = mock_backup_manager
|
|
143
|
+
|
|
144
|
+
mock_atomic_ops = MagicMock()
|
|
145
|
+
mock_atomic_ops_class.return_value = mock_atomic_ops
|
|
146
|
+
|
|
147
|
+
# Create test configuration
|
|
148
|
+
server_config = MCPServerConfig(
|
|
149
|
+
command="auggie",
|
|
150
|
+
args=["--mcp"]
|
|
151
|
+
)
|
|
152
|
+
config = HostConfiguration(servers={"test-server": server_config})
|
|
153
|
+
|
|
154
|
+
result = self.strategy.write_configuration(config)
|
|
155
|
+
|
|
156
|
+
# Verify success
|
|
157
|
+
self.assertTrue(result)
|
|
158
|
+
|
|
159
|
+
# Verify atomic write was called
|
|
160
|
+
mock_atomic_ops.atomic_write_with_backup.assert_called_once()
|
|
161
|
+
|
|
162
|
+
# Verify configuration structure in the call
|
|
163
|
+
call_args = mock_atomic_ops.atomic_write_with_backup.call_args
|
|
164
|
+
written_data = call_args[1]['data'] # keyword argument 'data'
|
|
165
|
+
self.assertIn("otherSettings", written_data) # Preserved
|
|
166
|
+
self.assertIn("mcpServers", written_data) # Updated
|
|
167
|
+
self.assertIn("test-server", written_data["mcpServers"])
|
|
168
|
+
|
|
169
|
+
@patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager')
|
|
170
|
+
@patch('hatch.mcp_host_config.strategies.AtomicFileOperations')
|
|
171
|
+
@patch('builtins.open', new_callable=mock_open)
|
|
172
|
+
@patch('pathlib.Path.exists')
|
|
173
|
+
@patch('pathlib.Path.mkdir')
|
|
174
|
+
@regression_test
|
|
175
|
+
def test_kiro_write_configuration_new_file(self, mock_mkdir, mock_exists,
|
|
176
|
+
mock_file, mock_atomic_ops_class, mock_backup_manager_class):
|
|
177
|
+
"""Test Kiro configuration writing when file doesn't exist."""
|
|
178
|
+
# Mock file doesn't exist
|
|
179
|
+
mock_exists.return_value = False
|
|
180
|
+
|
|
181
|
+
# Mock backup and atomic operations
|
|
182
|
+
mock_backup_manager = MagicMock()
|
|
183
|
+
mock_backup_manager_class.return_value = mock_backup_manager
|
|
184
|
+
|
|
185
|
+
mock_atomic_ops = MagicMock()
|
|
186
|
+
mock_atomic_ops_class.return_value = mock_atomic_ops
|
|
187
|
+
|
|
188
|
+
# Create test configuration
|
|
189
|
+
server_config = MCPServerConfig(
|
|
190
|
+
command="auggie",
|
|
191
|
+
args=["--mcp"]
|
|
192
|
+
)
|
|
193
|
+
config = HostConfiguration(servers={"new-server": server_config})
|
|
194
|
+
|
|
195
|
+
result = self.strategy.write_configuration(config)
|
|
196
|
+
|
|
197
|
+
# Verify success
|
|
198
|
+
self.assertTrue(result)
|
|
199
|
+
|
|
200
|
+
# Verify directory creation was attempted
|
|
201
|
+
mock_mkdir.assert_called_once()
|
|
202
|
+
|
|
203
|
+
# Verify atomic write was called
|
|
204
|
+
mock_atomic_ops.atomic_write_with_backup.assert_called_once()
|
|
205
|
+
|
|
206
|
+
# Verify configuration structure
|
|
207
|
+
call_args = mock_atomic_ops.atomic_write_with_backup.call_args
|
|
208
|
+
written_data = call_args[1]['data'] # keyword argument 'data'
|
|
209
|
+
self.assertIn("mcpServers", written_data)
|
|
210
|
+
self.assertIn("new-server", written_data["mcpServers"])
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == '__main__':
|
|
214
|
+
unittest.main()
|