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 commit comments