hatch-xclam 0.7.0.dev13__py3-none-any.whl → 0.7.1.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.
@@ -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()