Skip to content

Commit 09776d2

Browse files
author
LittleCoinCoin
committed
test(kiro): add comprehensive backup integration tests
Add complete test coverage for Kiro backup functionality including backup creation, skipping, failure handling, and strategy integration. New test file: test_mcp_kiro_backup_integration.py - 5 comprehensive backup integration tests - Tests backup creation by default when file exists - Tests backup skipping with no_backup=True parameter - Tests no backup for new files (expected behavior) - Tests backup failure prevention and error handling - Tests Kiro hostname support in backup system Updated test file: test_mcp_kiro_host_strategy.py - Fixed existing strategy tests to mock backup operations - Added proper mocking for MCPHostConfigBackupManager - Added proper mocking for AtomicFileOperations - Ensures tests use controlled backup behavior All 70 Kiro-related tests now pass with 100% success rate.
1 parent f8287f1 commit 09776d2

File tree

2 files changed

+272
-13
lines changed

2 files changed

+272
-13
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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()

tests/regression/test_mcp_kiro_host_strategy.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ def test_kiro_read_configuration_file_not_exists(self, mock_exists):
120120
self.assertIsInstance(config, HostConfiguration)
121121
self.assertEqual(len(config.servers), 0)
122122

123+
@patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager')
124+
@patch('hatch.mcp_host_config.strategies.AtomicFileOperations')
123125
@patch('builtins.open', new_callable=mock_open)
124126
@patch('pathlib.Path.exists')
125127
@patch('pathlib.Path.mkdir')
126-
@patch('json.dump')
127128
@patch('json.load')
128129
@regression_test
129-
def test_kiro_write_configuration_success(self, mock_json_load, mock_json_dump,
130-
mock_mkdir, mock_exists, mock_file):
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):
131132
"""Test successful Kiro configuration writing."""
132133
# Mock existing file with other settings
133134
mock_exists.return_value = True
@@ -136,6 +137,13 @@ def test_kiro_write_configuration_success(self, mock_json_load, mock_json_dump,
136137
"mcpServers": {}
137138
}
138139

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+
139147
# Create test configuration
140148
server_config = MCPServerConfig(
141149
command="auggie",
@@ -148,26 +156,35 @@ def test_kiro_write_configuration_success(self, mock_json_load, mock_json_dump,
148156
# Verify success
149157
self.assertTrue(result)
150158

151-
# Verify JSON dump was called
152-
mock_json_dump.assert_called_once()
159+
# Verify atomic write was called
160+
mock_atomic_ops.atomic_write_with_backup.assert_called_once()
153161

154-
# Verify configuration structure preservation
155-
written_data = mock_json_dump.call_args[0][0]
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'
156165
self.assertIn("otherSettings", written_data) # Preserved
157166
self.assertIn("mcpServers", written_data) # Updated
158167
self.assertIn("test-server", written_data["mcpServers"])
159168

169+
@patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager')
170+
@patch('hatch.mcp_host_config.strategies.AtomicFileOperations')
160171
@patch('builtins.open', new_callable=mock_open)
161172
@patch('pathlib.Path.exists')
162173
@patch('pathlib.Path.mkdir')
163-
@patch('json.dump')
164174
@regression_test
165-
def test_kiro_write_configuration_new_file(self, mock_json_dump, mock_mkdir,
166-
mock_exists, mock_file):
175+
def test_kiro_write_configuration_new_file(self, mock_mkdir, mock_exists,
176+
mock_file, mock_atomic_ops_class, mock_backup_manager_class):
167177
"""Test Kiro configuration writing when file doesn't exist."""
168178
# Mock file doesn't exist
169179
mock_exists.return_value = False
170180

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+
171188
# Create test configuration
172189
server_config = MCPServerConfig(
173190
command="auggie",
@@ -183,11 +200,12 @@ def test_kiro_write_configuration_new_file(self, mock_json_dump, mock_mkdir,
183200
# Verify directory creation was attempted
184201
mock_mkdir.assert_called_once()
185202

186-
# Verify JSON dump was called
187-
mock_json_dump.assert_called_once()
203+
# Verify atomic write was called
204+
mock_atomic_ops.atomic_write_with_backup.assert_called_once()
188205

189206
# Verify configuration structure
190-
written_data = mock_json_dump.call_args[0][0]
207+
call_args = mock_atomic_ops.atomic_write_with_backup.call_args
208+
written_data = call_args[1]['data'] # keyword argument 'data'
191209
self.assertIn("mcpServers", written_data)
192210
self.assertIn("new-server", written_data["mcpServers"])
193211

0 commit comments

Comments
 (0)