hatch-xclam 0.7.0.dev13__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.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/METADATA +6 -3
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/RECORD +26 -14
- 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.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/WHEEL +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/entry_points.txt +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/top_level.txt +0 -0
|
@@ -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()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Model Validation Tests
|
|
3
|
+
|
|
4
|
+
Tests for MCPServerConfigKiro Pydantic model behavior, field validation,
|
|
5
|
+
and Kiro-specific field combinations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
|
|
11
|
+
from wobble.decorators import regression_test
|
|
12
|
+
|
|
13
|
+
from hatch.mcp_host_config.models import (
|
|
14
|
+
MCPServerConfigKiro,
|
|
15
|
+
MCPServerConfigOmni,
|
|
16
|
+
MCPHostType
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestMCPServerConfigKiro(unittest.TestCase):
|
|
21
|
+
"""Test suite for MCPServerConfigKiro model validation."""
|
|
22
|
+
|
|
23
|
+
@regression_test
|
|
24
|
+
def test_kiro_model_with_disabled_field(self):
|
|
25
|
+
"""Test Kiro model with disabled field."""
|
|
26
|
+
config = MCPServerConfigKiro(
|
|
27
|
+
name="kiro-server",
|
|
28
|
+
command="auggie",
|
|
29
|
+
args=["--mcp", "-m", "default"],
|
|
30
|
+
disabled=True
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.assertEqual(config.command, "auggie")
|
|
34
|
+
self.assertTrue(config.disabled)
|
|
35
|
+
self.assertEqual(config.type, "stdio") # Inferred
|
|
36
|
+
|
|
37
|
+
@regression_test
|
|
38
|
+
def test_kiro_model_with_auto_approve_tools(self):
|
|
39
|
+
"""Test Kiro model with autoApprove field."""
|
|
40
|
+
config = MCPServerConfigKiro(
|
|
41
|
+
name="kiro-server",
|
|
42
|
+
command="auggie",
|
|
43
|
+
autoApprove=["codebase-retrieval", "fetch"]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.assertEqual(config.command, "auggie")
|
|
47
|
+
self.assertEqual(len(config.autoApprove), 2)
|
|
48
|
+
self.assertIn("codebase-retrieval", config.autoApprove)
|
|
49
|
+
self.assertIn("fetch", config.autoApprove)
|
|
50
|
+
|
|
51
|
+
@regression_test
|
|
52
|
+
def test_kiro_model_with_disabled_tools(self):
|
|
53
|
+
"""Test Kiro model with disabledTools field."""
|
|
54
|
+
config = MCPServerConfigKiro(
|
|
55
|
+
name="kiro-server",
|
|
56
|
+
command="python",
|
|
57
|
+
disabledTools=["dangerous-tool", "risky-tool"]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.assertEqual(config.command, "python")
|
|
61
|
+
self.assertEqual(len(config.disabledTools), 2)
|
|
62
|
+
self.assertIn("dangerous-tool", config.disabledTools)
|
|
63
|
+
|
|
64
|
+
@regression_test
|
|
65
|
+
def test_kiro_model_all_fields_combined(self):
|
|
66
|
+
"""Test Kiro model with all Kiro-specific fields."""
|
|
67
|
+
config = MCPServerConfigKiro(
|
|
68
|
+
name="kiro-server",
|
|
69
|
+
command="auggie",
|
|
70
|
+
args=["--mcp"],
|
|
71
|
+
env={"DEBUG": "true"},
|
|
72
|
+
disabled=False,
|
|
73
|
+
autoApprove=["codebase-retrieval"],
|
|
74
|
+
disabledTools=["dangerous-tool"]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Verify all fields
|
|
78
|
+
self.assertEqual(config.command, "auggie")
|
|
79
|
+
self.assertFalse(config.disabled)
|
|
80
|
+
self.assertEqual(len(config.autoApprove), 1)
|
|
81
|
+
self.assertEqual(len(config.disabledTools), 1)
|
|
82
|
+
self.assertEqual(config.env["DEBUG"], "true")
|
|
83
|
+
|
|
84
|
+
@regression_test
|
|
85
|
+
def test_kiro_model_minimal_configuration(self):
|
|
86
|
+
"""Test Kiro model with minimal configuration."""
|
|
87
|
+
config = MCPServerConfigKiro(
|
|
88
|
+
name="kiro-server",
|
|
89
|
+
command="auggie"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.assertEqual(config.command, "auggie")
|
|
93
|
+
self.assertEqual(config.type, "stdio") # Inferred
|
|
94
|
+
self.assertIsNone(config.disabled)
|
|
95
|
+
self.assertIsNone(config.autoApprove)
|
|
96
|
+
self.assertIsNone(config.disabledTools)
|
|
97
|
+
|
|
98
|
+
@regression_test
|
|
99
|
+
def test_kiro_model_remote_server_with_kiro_fields(self):
|
|
100
|
+
"""Test Kiro model with remote server and Kiro-specific fields."""
|
|
101
|
+
config = MCPServerConfigKiro(
|
|
102
|
+
name="kiro-remote",
|
|
103
|
+
url="https://api.example.com/mcp",
|
|
104
|
+
headers={"Authorization": "Bearer token"},
|
|
105
|
+
disabled=True,
|
|
106
|
+
autoApprove=["safe-tool"]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(config.url, "https://api.example.com/mcp")
|
|
110
|
+
self.assertTrue(config.disabled)
|
|
111
|
+
self.assertEqual(len(config.autoApprove), 1)
|
|
112
|
+
self.assertEqual(config.type, "sse") # Inferred for remote
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
unittest.main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Omni Conversion Tests
|
|
3
|
+
|
|
4
|
+
Tests for conversion from MCPServerConfigOmni to MCPServerConfigKiro
|
|
5
|
+
using the from_omni() method.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
from wobble.decorators import regression_test
|
|
11
|
+
|
|
12
|
+
from hatch.mcp_host_config.models import (
|
|
13
|
+
MCPServerConfigKiro,
|
|
14
|
+
MCPServerConfigOmni
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestKiroFromOmniConversion(unittest.TestCase):
|
|
19
|
+
"""Test suite for Kiro from_omni() conversion method."""
|
|
20
|
+
|
|
21
|
+
@regression_test
|
|
22
|
+
def test_kiro_from_omni_with_supported_fields(self):
|
|
23
|
+
"""Test Kiro from_omni with supported fields."""
|
|
24
|
+
omni = MCPServerConfigOmni(
|
|
25
|
+
name="kiro-server",
|
|
26
|
+
command="auggie",
|
|
27
|
+
args=["--mcp", "-m", "default"],
|
|
28
|
+
disabled=True,
|
|
29
|
+
autoApprove=["codebase-retrieval", "fetch"],
|
|
30
|
+
disabledTools=["dangerous-tool"]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Convert to Kiro model
|
|
34
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
35
|
+
|
|
36
|
+
# Verify all supported fields transferred
|
|
37
|
+
self.assertEqual(kiro.name, "kiro-server")
|
|
38
|
+
self.assertEqual(kiro.command, "auggie")
|
|
39
|
+
self.assertEqual(len(kiro.args), 3)
|
|
40
|
+
self.assertTrue(kiro.disabled)
|
|
41
|
+
self.assertEqual(len(kiro.autoApprove), 2)
|
|
42
|
+
self.assertEqual(len(kiro.disabledTools), 1)
|
|
43
|
+
|
|
44
|
+
@regression_test
|
|
45
|
+
def test_kiro_from_omni_with_unsupported_fields(self):
|
|
46
|
+
"""Test Kiro from_omni excludes unsupported fields."""
|
|
47
|
+
omni = MCPServerConfigOmni(
|
|
48
|
+
name="kiro-server",
|
|
49
|
+
command="python",
|
|
50
|
+
disabled=True, # Kiro field
|
|
51
|
+
envFile=".env", # VS Code field (unsupported by Kiro)
|
|
52
|
+
timeout=30000 # Gemini field (unsupported by Kiro)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Convert to Kiro model
|
|
56
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
57
|
+
|
|
58
|
+
# Verify Kiro fields transferred
|
|
59
|
+
self.assertEqual(kiro.command, "python")
|
|
60
|
+
self.assertTrue(kiro.disabled)
|
|
61
|
+
|
|
62
|
+
# Verify unsupported fields NOT transferred
|
|
63
|
+
self.assertFalse(hasattr(kiro, 'envFile') and kiro.envFile is not None)
|
|
64
|
+
self.assertFalse(hasattr(kiro, 'timeout') and kiro.timeout is not None)
|
|
65
|
+
|
|
66
|
+
@regression_test
|
|
67
|
+
def test_kiro_from_omni_exclude_unset_behavior(self):
|
|
68
|
+
"""Test that from_omni respects exclude_unset=True."""
|
|
69
|
+
omni = MCPServerConfigOmni(
|
|
70
|
+
name="kiro-server",
|
|
71
|
+
command="auggie"
|
|
72
|
+
# disabled, autoApprove, disabledTools not set
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
76
|
+
|
|
77
|
+
# Verify unset fields remain None
|
|
78
|
+
self.assertIsNone(kiro.disabled)
|
|
79
|
+
self.assertIsNone(kiro.autoApprove)
|
|
80
|
+
self.assertIsNone(kiro.disabledTools)
|
|
81
|
+
|
|
82
|
+
@regression_test
|
|
83
|
+
def test_kiro_from_omni_remote_server_conversion(self):
|
|
84
|
+
"""Test Kiro from_omni with remote server configuration."""
|
|
85
|
+
omni = MCPServerConfigOmni(
|
|
86
|
+
name="kiro-remote",
|
|
87
|
+
url="https://api.example.com/mcp",
|
|
88
|
+
headers={"Authorization": "Bearer token"},
|
|
89
|
+
disabled=False,
|
|
90
|
+
autoApprove=["safe-tool"]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
94
|
+
|
|
95
|
+
# Verify remote server fields
|
|
96
|
+
self.assertEqual(kiro.url, "https://api.example.com/mcp")
|
|
97
|
+
self.assertEqual(kiro.headers["Authorization"], "Bearer token")
|
|
98
|
+
self.assertFalse(kiro.disabled)
|
|
99
|
+
self.assertEqual(len(kiro.autoApprove), 1)
|
|
100
|
+
self.assertEqual(kiro.type, "sse") # Inferred for remote
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == '__main__':
|
|
104
|
+
unittest.main()
|