hatch-xclam 0.7.1.dev3__py3-none-any.whl → 0.8.0.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.
- hatch/__init__.py +1 -1
- hatch/cli/__init__.py +71 -0
- hatch/cli/__main__.py +1035 -0
- hatch/cli/cli_env.py +865 -0
- hatch/cli/cli_mcp.py +1965 -0
- hatch/cli/cli_package.py +566 -0
- hatch/cli/cli_system.py +136 -0
- hatch/cli/cli_utils.py +1289 -0
- hatch/cli_hatch.py +160 -2838
- hatch/mcp_host_config/__init__.py +10 -10
- hatch/mcp_host_config/adapters/__init__.py +34 -0
- hatch/mcp_host_config/adapters/base.py +170 -0
- hatch/mcp_host_config/adapters/claude.py +105 -0
- hatch/mcp_host_config/adapters/codex.py +104 -0
- hatch/mcp_host_config/adapters/cursor.py +83 -0
- hatch/mcp_host_config/adapters/gemini.py +75 -0
- hatch/mcp_host_config/adapters/kiro.py +78 -0
- hatch/mcp_host_config/adapters/lmstudio.py +79 -0
- hatch/mcp_host_config/adapters/registry.py +149 -0
- hatch/mcp_host_config/adapters/vscode.py +83 -0
- hatch/mcp_host_config/backup.py +5 -3
- hatch/mcp_host_config/fields.py +126 -0
- hatch/mcp_host_config/models.py +161 -456
- hatch/mcp_host_config/reporting.py +57 -16
- hatch/mcp_host_config/strategies.py +155 -87
- hatch/template_generator.py +1 -1
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
- hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
- tests/cli_test_utils.py +280 -0
- tests/integration/cli/__init__.py +14 -0
- tests/integration/cli/test_cli_reporter_integration.py +2439 -0
- tests/integration/mcp/__init__.py +0 -0
- tests/integration/mcp/test_adapter_serialization.py +173 -0
- tests/regression/cli/__init__.py +16 -0
- tests/regression/cli/test_color_logic.py +268 -0
- tests/regression/cli/test_consequence_type.py +298 -0
- tests/regression/cli/test_error_formatting.py +328 -0
- tests/regression/cli/test_result_reporter.py +586 -0
- tests/regression/cli/test_table_formatter.py +211 -0
- tests/regression/mcp/__init__.py +0 -0
- tests/regression/mcp/test_field_filtering.py +162 -0
- tests/test_cli_version.py +7 -5
- tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
- tests/unit/__init__.py +0 -0
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_adapter_protocol.py +138 -0
- tests/unit/mcp/test_adapter_registry.py +158 -0
- tests/unit/mcp/test_config_model.py +146 -0
- hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
- tests/integration/test_mcp_kiro_integration.py +0 -153
- tests/regression/test_mcp_codex_backup_integration.py +0 -162
- tests/regression/test_mcp_codex_host_strategy.py +0 -163
- tests/regression/test_mcp_codex_model_validation.py +0 -117
- tests/regression/test_mcp_kiro_backup_integration.py +0 -241
- tests/regression/test_mcp_kiro_cli_integration.py +0 -141
- tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
- tests/regression/test_mcp_kiro_host_strategy.py +0 -214
- tests/regression/test_mcp_kiro_model_validation.py +0 -116
- tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
- tests/test_mcp_atomic_operations.py +0 -276
- tests/test_mcp_backup_integration.py +0 -308
- tests/test_mcp_cli_all_host_specific_args.py +0 -496
- tests/test_mcp_cli_backup_management.py +0 -295
- tests/test_mcp_cli_direct_management.py +0 -456
- tests/test_mcp_cli_discovery_listing.py +0 -582
- tests/test_mcp_cli_host_config_integration.py +0 -823
- tests/test_mcp_cli_package_management.py +0 -360
- tests/test_mcp_cli_partial_updates.py +0 -859
- tests/test_mcp_environment_integration.py +0 -520
- tests/test_mcp_host_config_backup.py +0 -257
- tests/test_mcp_host_configuration_manager.py +0 -331
- tests/test_mcp_host_registry_decorator.py +0 -348
- tests/test_mcp_pydantic_architecture_v4.py +0 -603
- tests/test_mcp_server_config_models.py +0 -242
- tests/test_mcp_server_config_type_field.py +0 -221
- tests/test_mcp_sync_functionality.py +0 -316
- tests/test_mcp_user_feedback_reporting.py +0 -359
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
"""Tests for MCP atomic file operations.
|
|
2
|
-
|
|
3
|
-
This module contains tests for atomic file operations and backup-aware
|
|
4
|
-
operations with host-agnostic design.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import unittest
|
|
8
|
-
import tempfile
|
|
9
|
-
import shutil
|
|
10
|
-
import json
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from unittest.mock import patch, mock_open
|
|
13
|
-
|
|
14
|
-
from wobble.decorators import regression_test
|
|
15
|
-
from test_data_utils import MCPBackupTestDataLoader
|
|
16
|
-
|
|
17
|
-
from hatch.mcp_host_config.backup import (
|
|
18
|
-
AtomicFileOperations,
|
|
19
|
-
MCPHostConfigBackupManager,
|
|
20
|
-
BackupAwareOperation,
|
|
21
|
-
BackupError
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class TestAtomicFileOperations(unittest.TestCase):
|
|
26
|
-
"""Test atomic file operations with host-agnostic design."""
|
|
27
|
-
|
|
28
|
-
def setUp(self):
|
|
29
|
-
"""Set up test environment."""
|
|
30
|
-
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_atomic_"))
|
|
31
|
-
self.test_file = self.temp_dir / "test_config.json"
|
|
32
|
-
self.backup_manager = MCPHostConfigBackupManager(backup_root=self.temp_dir / "backups")
|
|
33
|
-
self.atomic_ops = AtomicFileOperations()
|
|
34
|
-
self.test_data = MCPBackupTestDataLoader()
|
|
35
|
-
|
|
36
|
-
def tearDown(self):
|
|
37
|
-
"""Clean up test environment."""
|
|
38
|
-
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
39
|
-
|
|
40
|
-
@regression_test
|
|
41
|
-
def test_atomic_write_success_host_agnostic(self):
|
|
42
|
-
"""Test successful atomic write with any JSON configuration format."""
|
|
43
|
-
test_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
44
|
-
|
|
45
|
-
result = self.atomic_ops.atomic_write_with_backup(
|
|
46
|
-
self.test_file, test_data, self.backup_manager, "claude-desktop"
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
self.assertTrue(result)
|
|
50
|
-
self.assertTrue(self.test_file.exists())
|
|
51
|
-
|
|
52
|
-
# Verify content (host-agnostic)
|
|
53
|
-
with open(self.test_file) as f:
|
|
54
|
-
written_data = json.load(f)
|
|
55
|
-
self.assertEqual(written_data, test_data)
|
|
56
|
-
|
|
57
|
-
@regression_test
|
|
58
|
-
def test_atomic_write_with_existing_file(self):
|
|
59
|
-
"""Test atomic write with existing file creates backup."""
|
|
60
|
-
# Create initial file
|
|
61
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
62
|
-
with open(self.test_file, 'w') as f:
|
|
63
|
-
json.dump(initial_data, f)
|
|
64
|
-
|
|
65
|
-
# Update with atomic write
|
|
66
|
-
new_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
67
|
-
result = self.atomic_ops.atomic_write_with_backup(
|
|
68
|
-
self.test_file, new_data, self.backup_manager, "vscode"
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
self.assertTrue(result)
|
|
72
|
-
|
|
73
|
-
# Verify backup was created
|
|
74
|
-
backups = self.backup_manager.list_backups("vscode")
|
|
75
|
-
self.assertEqual(len(backups), 1)
|
|
76
|
-
|
|
77
|
-
# Verify backup contains original data
|
|
78
|
-
with open(backups[0].file_path) as f:
|
|
79
|
-
backup_data = json.load(f)
|
|
80
|
-
self.assertEqual(backup_data, initial_data)
|
|
81
|
-
|
|
82
|
-
# Verify file contains new data
|
|
83
|
-
with open(self.test_file) as f:
|
|
84
|
-
current_data = json.load(f)
|
|
85
|
-
self.assertEqual(current_data, new_data)
|
|
86
|
-
|
|
87
|
-
@regression_test
|
|
88
|
-
def test_atomic_write_skip_backup(self):
|
|
89
|
-
"""Test atomic write with backup skipped."""
|
|
90
|
-
# Create initial file
|
|
91
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
92
|
-
with open(self.test_file, 'w') as f:
|
|
93
|
-
json.dump(initial_data, f)
|
|
94
|
-
|
|
95
|
-
# Update with atomic write, skipping backup
|
|
96
|
-
new_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
97
|
-
result = self.atomic_ops.atomic_write_with_backup(
|
|
98
|
-
self.test_file, new_data, self.backup_manager, "cursor", skip_backup=True
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
self.assertTrue(result)
|
|
102
|
-
|
|
103
|
-
# Verify no backup was created
|
|
104
|
-
backups = self.backup_manager.list_backups("cursor")
|
|
105
|
-
self.assertEqual(len(backups), 0)
|
|
106
|
-
|
|
107
|
-
# Verify file contains new data
|
|
108
|
-
with open(self.test_file) as f:
|
|
109
|
-
current_data = json.load(f)
|
|
110
|
-
self.assertEqual(current_data, new_data)
|
|
111
|
-
|
|
112
|
-
@regression_test
|
|
113
|
-
def test_atomic_write_failure_rollback(self):
|
|
114
|
-
"""Test atomic write failure triggers rollback."""
|
|
115
|
-
# Create initial file
|
|
116
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
117
|
-
with open(self.test_file, 'w') as f:
|
|
118
|
-
json.dump(initial_data, f)
|
|
119
|
-
|
|
120
|
-
# Mock file write failure after backup creation
|
|
121
|
-
with patch('builtins.open', side_effect=[
|
|
122
|
-
# First call succeeds (backup creation)
|
|
123
|
-
open(self.test_file, 'r'),
|
|
124
|
-
# Second call fails (atomic write)
|
|
125
|
-
PermissionError("Access denied")
|
|
126
|
-
]):
|
|
127
|
-
with self.assertRaises(BackupError):
|
|
128
|
-
self.atomic_ops.atomic_write_with_backup(
|
|
129
|
-
self.test_file, {"new": "data"}, self.backup_manager, "lmstudio"
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Verify original file is unchanged
|
|
133
|
-
with open(self.test_file) as f:
|
|
134
|
-
current_data = json.load(f)
|
|
135
|
-
self.assertEqual(current_data, initial_data)
|
|
136
|
-
|
|
137
|
-
@regression_test
|
|
138
|
-
def test_atomic_copy_success(self):
|
|
139
|
-
"""Test successful atomic copy operation."""
|
|
140
|
-
source_file = self.temp_dir / "source.json"
|
|
141
|
-
target_file = self.temp_dir / "target.json"
|
|
142
|
-
|
|
143
|
-
test_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
144
|
-
with open(source_file, 'w') as f:
|
|
145
|
-
json.dump(test_data, f)
|
|
146
|
-
|
|
147
|
-
result = self.atomic_ops.atomic_copy(source_file, target_file)
|
|
148
|
-
|
|
149
|
-
self.assertTrue(result)
|
|
150
|
-
self.assertTrue(target_file.exists())
|
|
151
|
-
|
|
152
|
-
# Verify content integrity
|
|
153
|
-
with open(target_file) as f:
|
|
154
|
-
copied_data = json.load(f)
|
|
155
|
-
self.assertEqual(copied_data, test_data)
|
|
156
|
-
|
|
157
|
-
@regression_test
|
|
158
|
-
def test_atomic_copy_failure_cleanup(self):
|
|
159
|
-
"""Test atomic copy failure cleans up temporary files."""
|
|
160
|
-
source_file = self.temp_dir / "source.json"
|
|
161
|
-
target_file = self.temp_dir / "target.json"
|
|
162
|
-
|
|
163
|
-
test_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
164
|
-
with open(source_file, 'w') as f:
|
|
165
|
-
json.dump(test_data, f)
|
|
166
|
-
|
|
167
|
-
# Mock copy failure
|
|
168
|
-
with patch('shutil.copy2', side_effect=PermissionError("Access denied")):
|
|
169
|
-
result = self.atomic_ops.atomic_copy(source_file, target_file)
|
|
170
|
-
|
|
171
|
-
self.assertFalse(result)
|
|
172
|
-
self.assertFalse(target_file.exists())
|
|
173
|
-
|
|
174
|
-
# Verify no temporary files left behind
|
|
175
|
-
temp_files = list(self.temp_dir.glob("*.tmp"))
|
|
176
|
-
self.assertEqual(len(temp_files), 0)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
class TestBackupAwareOperation(unittest.TestCase):
|
|
180
|
-
"""Test backup-aware operation API."""
|
|
181
|
-
|
|
182
|
-
def setUp(self):
|
|
183
|
-
"""Set up test environment."""
|
|
184
|
-
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_backup_aware_"))
|
|
185
|
-
self.test_file = self.temp_dir / "test_config.json"
|
|
186
|
-
self.backup_manager = MCPHostConfigBackupManager(backup_root=self.temp_dir / "backups")
|
|
187
|
-
self.test_data = MCPBackupTestDataLoader()
|
|
188
|
-
|
|
189
|
-
def tearDown(self):
|
|
190
|
-
"""Clean up test environment."""
|
|
191
|
-
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
192
|
-
|
|
193
|
-
@regression_test
|
|
194
|
-
def test_prepare_backup_success(self):
|
|
195
|
-
"""Test explicit backup preparation."""
|
|
196
|
-
# Create initial configuration
|
|
197
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
198
|
-
with open(self.test_file, 'w') as f:
|
|
199
|
-
json.dump(initial_data, f)
|
|
200
|
-
|
|
201
|
-
# Test backup-aware operation
|
|
202
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
203
|
-
|
|
204
|
-
# Test explicit backup preparation
|
|
205
|
-
backup_result = operation.prepare_backup(self.test_file, "gemini", no_backup=False)
|
|
206
|
-
self.assertIsNotNone(backup_result)
|
|
207
|
-
self.assertTrue(backup_result.success)
|
|
208
|
-
|
|
209
|
-
# Verify backup was created
|
|
210
|
-
backups = self.backup_manager.list_backups("gemini")
|
|
211
|
-
self.assertEqual(len(backups), 1)
|
|
212
|
-
|
|
213
|
-
@regression_test
|
|
214
|
-
def test_prepare_backup_no_backup_mode(self):
|
|
215
|
-
"""Test no-backup mode."""
|
|
216
|
-
# Create initial configuration
|
|
217
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
218
|
-
with open(self.test_file, 'w') as f:
|
|
219
|
-
json.dump(initial_data, f)
|
|
220
|
-
|
|
221
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
222
|
-
|
|
223
|
-
# Test no-backup mode
|
|
224
|
-
no_backup_result = operation.prepare_backup(self.test_file, "claude-code", no_backup=True)
|
|
225
|
-
self.assertIsNone(no_backup_result)
|
|
226
|
-
|
|
227
|
-
# Verify no backup was created
|
|
228
|
-
backups = self.backup_manager.list_backups("claude-code")
|
|
229
|
-
self.assertEqual(len(backups), 0)
|
|
230
|
-
|
|
231
|
-
@regression_test
|
|
232
|
-
def test_prepare_backup_failure_raises_exception(self):
|
|
233
|
-
"""Test backup preparation failure raises BackupError."""
|
|
234
|
-
# Test with nonexistent file
|
|
235
|
-
nonexistent_file = self.temp_dir / "nonexistent.json"
|
|
236
|
-
|
|
237
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
238
|
-
|
|
239
|
-
with self.assertRaises(BackupError):
|
|
240
|
-
operation.prepare_backup(nonexistent_file, "vscode", no_backup=False)
|
|
241
|
-
|
|
242
|
-
@regression_test
|
|
243
|
-
def test_rollback_on_failure_success(self):
|
|
244
|
-
"""Test successful rollback functionality."""
|
|
245
|
-
# Create initial configuration
|
|
246
|
-
initial_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
247
|
-
with open(self.test_file, 'w') as f:
|
|
248
|
-
json.dump(initial_data, f)
|
|
249
|
-
|
|
250
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
251
|
-
|
|
252
|
-
# Create backup
|
|
253
|
-
backup_result = operation.prepare_backup(self.test_file, "cursor", no_backup=False)
|
|
254
|
-
self.assertTrue(backup_result.success)
|
|
255
|
-
|
|
256
|
-
# Modify file (simulate failed operation)
|
|
257
|
-
modified_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
258
|
-
with open(self.test_file, 'w') as f:
|
|
259
|
-
json.dump(modified_data, f)
|
|
260
|
-
|
|
261
|
-
# Test rollback functionality
|
|
262
|
-
rollback_success = operation.rollback_on_failure(backup_result, self.test_file, "cursor")
|
|
263
|
-
self.assertTrue(rollback_success)
|
|
264
|
-
|
|
265
|
-
@regression_test
|
|
266
|
-
def test_rollback_on_failure_no_backup(self):
|
|
267
|
-
"""Test rollback with no backup result."""
|
|
268
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
269
|
-
|
|
270
|
-
# Test rollback with None backup result
|
|
271
|
-
rollback_success = operation.rollback_on_failure(None, self.test_file, "lmstudio")
|
|
272
|
-
self.assertFalse(rollback_success)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if __name__ == '__main__':
|
|
276
|
-
unittest.main()
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
"""Tests for MCP backup system integration.
|
|
2
|
-
|
|
3
|
-
This module contains integration tests for the backup system with existing
|
|
4
|
-
Hatch infrastructure and end-to-end workflows.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import unittest
|
|
8
|
-
import tempfile
|
|
9
|
-
import shutil
|
|
10
|
-
import json
|
|
11
|
-
import time
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from unittest.mock import Mock, patch
|
|
14
|
-
|
|
15
|
-
from wobble.decorators import integration_test, slow_test, regression_test
|
|
16
|
-
from test_data_utils import MCPBackupTestDataLoader
|
|
17
|
-
|
|
18
|
-
from hatch.mcp_host_config.backup import (
|
|
19
|
-
MCPHostConfigBackupManager,
|
|
20
|
-
BackupAwareOperation,
|
|
21
|
-
BackupInfo,
|
|
22
|
-
BackupResult
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TestMCPBackupIntegration(unittest.TestCase):
|
|
27
|
-
"""Test backup system integration with existing Hatch infrastructure."""
|
|
28
|
-
|
|
29
|
-
def setUp(self):
|
|
30
|
-
"""Set up integration test environment."""
|
|
31
|
-
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_integration_"))
|
|
32
|
-
self.backup_manager = MCPHostConfigBackupManager(backup_root=self.temp_dir / "backups")
|
|
33
|
-
self.test_data = MCPBackupTestDataLoader()
|
|
34
|
-
|
|
35
|
-
# Create test configuration files
|
|
36
|
-
self.config_dir = self.temp_dir / "configs"
|
|
37
|
-
self.config_dir.mkdir(parents=True)
|
|
38
|
-
|
|
39
|
-
self.test_configs = {}
|
|
40
|
-
for hostname in ['claude-desktop', 'claude-code', 'vscode', 'cursor']:
|
|
41
|
-
config_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
42
|
-
config_file = self.config_dir / f"{hostname}_config.json"
|
|
43
|
-
with open(config_file, 'w') as f:
|
|
44
|
-
json.dump(config_data, f, indent=2)
|
|
45
|
-
self.test_configs[hostname] = config_file
|
|
46
|
-
|
|
47
|
-
def tearDown(self):
|
|
48
|
-
"""Clean up integration test environment."""
|
|
49
|
-
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
50
|
-
|
|
51
|
-
@integration_test(scope="component")
|
|
52
|
-
def test_complete_backup_restore_cycle(self):
|
|
53
|
-
"""Test complete backup creation and restoration cycle."""
|
|
54
|
-
hostname = 'claude-desktop'
|
|
55
|
-
config_file = self.test_configs[hostname]
|
|
56
|
-
|
|
57
|
-
# Create backup
|
|
58
|
-
backup_result = self.backup_manager.create_backup(config_file, hostname)
|
|
59
|
-
self.assertTrue(backup_result.success)
|
|
60
|
-
|
|
61
|
-
# Modify original file
|
|
62
|
-
modified_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
63
|
-
with open(config_file, 'w') as f:
|
|
64
|
-
json.dump(modified_data, f)
|
|
65
|
-
|
|
66
|
-
# Verify file was modified
|
|
67
|
-
with open(config_file) as f:
|
|
68
|
-
current_data = json.load(f)
|
|
69
|
-
self.assertEqual(current_data, modified_data)
|
|
70
|
-
|
|
71
|
-
# Restore from backup (placeholder - actual restore would need host config paths)
|
|
72
|
-
restore_success = self.backup_manager.restore_backup(hostname)
|
|
73
|
-
self.assertTrue(restore_success) # Currently returns True as placeholder
|
|
74
|
-
|
|
75
|
-
@integration_test(scope="component")
|
|
76
|
-
def test_multi_host_backup_management(self):
|
|
77
|
-
"""Test backup management across multiple hosts."""
|
|
78
|
-
# Create backups for multiple hosts
|
|
79
|
-
results = {}
|
|
80
|
-
for hostname, config_file in self.test_configs.items():
|
|
81
|
-
results[hostname] = self.backup_manager.create_backup(config_file, hostname)
|
|
82
|
-
self.assertTrue(results[hostname].success)
|
|
83
|
-
|
|
84
|
-
# Verify separate backup directories
|
|
85
|
-
for hostname in self.test_configs.keys():
|
|
86
|
-
backups = self.backup_manager.list_backups(hostname)
|
|
87
|
-
self.assertEqual(len(backups), 1)
|
|
88
|
-
|
|
89
|
-
# Verify backup isolation
|
|
90
|
-
backup_dir = backups[0].file_path.parent
|
|
91
|
-
self.assertEqual(backup_dir.name, hostname)
|
|
92
|
-
|
|
93
|
-
# Verify no cross-contamination
|
|
94
|
-
for other_hostname in self.test_configs.keys():
|
|
95
|
-
if other_hostname != hostname:
|
|
96
|
-
other_backups = self.backup_manager.list_backups(other_hostname)
|
|
97
|
-
self.assertNotEqual(
|
|
98
|
-
backups[0].file_path.parent,
|
|
99
|
-
other_backups[0].file_path.parent
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
@integration_test(scope="end_to_end")
|
|
103
|
-
def test_backup_with_configuration_update_workflow(self):
|
|
104
|
-
"""Test backup integration with configuration update operations."""
|
|
105
|
-
hostname = 'vscode'
|
|
106
|
-
config_file = self.test_configs[hostname]
|
|
107
|
-
|
|
108
|
-
# Simulate configuration update with backup
|
|
109
|
-
original_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
110
|
-
updated_data = self.test_data.load_host_agnostic_config("complex_server")
|
|
111
|
-
|
|
112
|
-
# Ensure original data is in file
|
|
113
|
-
with open(config_file, 'w') as f:
|
|
114
|
-
json.dump(original_data, f)
|
|
115
|
-
|
|
116
|
-
# Simulate update operation with backup
|
|
117
|
-
backup_result = self.backup_manager.create_backup(config_file, hostname)
|
|
118
|
-
self.assertTrue(backup_result.success)
|
|
119
|
-
|
|
120
|
-
# Update configuration
|
|
121
|
-
with open(config_file, 'w') as f:
|
|
122
|
-
json.dump(updated_data, f)
|
|
123
|
-
|
|
124
|
-
# Verify backup contains original data
|
|
125
|
-
backups = self.backup_manager.list_backups(hostname)
|
|
126
|
-
self.assertEqual(len(backups), 1)
|
|
127
|
-
|
|
128
|
-
with open(backups[0].file_path) as f:
|
|
129
|
-
backup_data = json.load(f)
|
|
130
|
-
self.assertEqual(backup_data, original_data)
|
|
131
|
-
|
|
132
|
-
# Verify current file has updated data
|
|
133
|
-
with open(config_file) as f:
|
|
134
|
-
current_data = json.load(f)
|
|
135
|
-
self.assertEqual(current_data, updated_data)
|
|
136
|
-
|
|
137
|
-
@integration_test(scope="service")
|
|
138
|
-
def test_backup_system_with_existing_test_utilities(self):
|
|
139
|
-
"""Test backup system integration with existing test utilities."""
|
|
140
|
-
# Use existing TestDataLoader patterns
|
|
141
|
-
test_config = self.test_data.load_host_agnostic_config("complex_server")
|
|
142
|
-
|
|
143
|
-
# Test backup creation with complex configuration
|
|
144
|
-
config_path = self.temp_dir / "complex_config.json"
|
|
145
|
-
with open(config_path, 'w') as f:
|
|
146
|
-
json.dump(test_config, f)
|
|
147
|
-
|
|
148
|
-
result = self.backup_manager.create_backup(config_path, "lmstudio")
|
|
149
|
-
self.assertTrue(result.success)
|
|
150
|
-
|
|
151
|
-
# Verify integration with existing test data patterns
|
|
152
|
-
self.assertIsInstance(test_config, dict)
|
|
153
|
-
self.assertIn("servers", test_config)
|
|
154
|
-
|
|
155
|
-
# Verify backup content matches test data
|
|
156
|
-
with open(result.backup_path) as f:
|
|
157
|
-
backup_content = json.load(f)
|
|
158
|
-
self.assertEqual(backup_content, test_config)
|
|
159
|
-
|
|
160
|
-
@integration_test(scope="component")
|
|
161
|
-
def test_backup_aware_operation_workflow(self):
|
|
162
|
-
"""Test backup-aware operation following environment manager patterns."""
|
|
163
|
-
hostname = 'cursor'
|
|
164
|
-
config_file = self.test_configs[hostname]
|
|
165
|
-
|
|
166
|
-
# Test backup-aware operation following existing patterns
|
|
167
|
-
operation = BackupAwareOperation(self.backup_manager)
|
|
168
|
-
|
|
169
|
-
# Simulate environment manager update workflow
|
|
170
|
-
backup_result = operation.prepare_backup(config_file, hostname, no_backup=False)
|
|
171
|
-
self.assertTrue(backup_result.success)
|
|
172
|
-
|
|
173
|
-
# Verify backup was created following existing patterns
|
|
174
|
-
backups = self.backup_manager.list_backups(hostname)
|
|
175
|
-
self.assertEqual(len(backups), 1)
|
|
176
|
-
self.assertEqual(backups[0].hostname, hostname)
|
|
177
|
-
|
|
178
|
-
# Test rollback capability
|
|
179
|
-
rollback_success = operation.rollback_on_failure(backup_result, config_file, hostname)
|
|
180
|
-
self.assertTrue(rollback_success)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class TestMCPBackupPerformance(unittest.TestCase):
|
|
184
|
-
"""Test backup system performance characteristics."""
|
|
185
|
-
|
|
186
|
-
def setUp(self):
|
|
187
|
-
"""Set up performance test environment."""
|
|
188
|
-
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_performance_"))
|
|
189
|
-
self.backup_manager = MCPHostConfigBackupManager(backup_root=self.temp_dir / "backups")
|
|
190
|
-
self.test_data = MCPBackupTestDataLoader()
|
|
191
|
-
|
|
192
|
-
def tearDown(self):
|
|
193
|
-
"""Clean up performance test environment."""
|
|
194
|
-
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
195
|
-
|
|
196
|
-
@slow_test
|
|
197
|
-
@regression_test
|
|
198
|
-
def test_backup_performance_large_config(self):
|
|
199
|
-
"""Test backup performance with larger configuration files."""
|
|
200
|
-
# Create large host-agnostic configuration
|
|
201
|
-
large_config = {"servers": {}}
|
|
202
|
-
for i in range(1000):
|
|
203
|
-
large_config["servers"][f"server_{i}"] = {
|
|
204
|
-
"command": f"python_{i}",
|
|
205
|
-
"args": [f"arg_{j}" for j in range(10)]
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
config_file = self.temp_dir / "large_config.json"
|
|
209
|
-
with open(config_file, 'w') as f:
|
|
210
|
-
json.dump(large_config, f)
|
|
211
|
-
|
|
212
|
-
start_time = time.time()
|
|
213
|
-
result = self.backup_manager.create_backup(config_file, "gemini")
|
|
214
|
-
duration = time.time() - start_time
|
|
215
|
-
|
|
216
|
-
self.assertTrue(result.success)
|
|
217
|
-
self.assertLess(duration, 1.0) # Should complete within 1 second
|
|
218
|
-
|
|
219
|
-
@regression_test
|
|
220
|
-
def test_pydantic_validation_performance(self):
|
|
221
|
-
"""Test Pydantic model validation performance."""
|
|
222
|
-
hostname = "claude-desktop"
|
|
223
|
-
config_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
224
|
-
config_file = self.temp_dir / "test_config.json"
|
|
225
|
-
|
|
226
|
-
with open(config_file, 'w') as f:
|
|
227
|
-
json.dump(config_data, f)
|
|
228
|
-
|
|
229
|
-
start_time = time.time()
|
|
230
|
-
|
|
231
|
-
# Create backup (includes Pydantic validation)
|
|
232
|
-
result = self.backup_manager.create_backup(config_file, hostname)
|
|
233
|
-
|
|
234
|
-
# List backups (includes Pydantic model creation)
|
|
235
|
-
backups = self.backup_manager.list_backups(hostname)
|
|
236
|
-
|
|
237
|
-
duration = time.time() - start_time
|
|
238
|
-
|
|
239
|
-
self.assertTrue(result.success)
|
|
240
|
-
self.assertEqual(len(backups), 1)
|
|
241
|
-
self.assertLess(duration, 0.1) # Pydantic operations should be fast
|
|
242
|
-
|
|
243
|
-
@regression_test
|
|
244
|
-
def test_concurrent_backup_operations(self):
|
|
245
|
-
"""Test concurrent backup operations for different hosts."""
|
|
246
|
-
import threading
|
|
247
|
-
|
|
248
|
-
results = {}
|
|
249
|
-
config_files = {}
|
|
250
|
-
|
|
251
|
-
# Create test configurations for different hosts
|
|
252
|
-
for hostname in ['claude-desktop', 'vscode', 'cursor', 'lmstudio']:
|
|
253
|
-
config_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
254
|
-
config_file = self.temp_dir / f"{hostname}_config.json"
|
|
255
|
-
with open(config_file, 'w') as f:
|
|
256
|
-
json.dump(config_data, f)
|
|
257
|
-
config_files[hostname] = config_file
|
|
258
|
-
|
|
259
|
-
def create_backup_thread(hostname, config_file):
|
|
260
|
-
results[hostname] = self.backup_manager.create_backup(config_file, hostname)
|
|
261
|
-
|
|
262
|
-
# Start concurrent backup operations
|
|
263
|
-
threads = []
|
|
264
|
-
for hostname, config_file in config_files.items():
|
|
265
|
-
thread = threading.Thread(target=create_backup_thread, args=(hostname, config_file))
|
|
266
|
-
threads.append(thread)
|
|
267
|
-
thread.start()
|
|
268
|
-
|
|
269
|
-
# Wait for all threads to complete
|
|
270
|
-
for thread in threads:
|
|
271
|
-
thread.join(timeout=5.0)
|
|
272
|
-
|
|
273
|
-
# Verify all operations succeeded
|
|
274
|
-
for hostname in config_files.keys():
|
|
275
|
-
self.assertIn(hostname, results)
|
|
276
|
-
self.assertTrue(results[hostname].success)
|
|
277
|
-
|
|
278
|
-
@regression_test
|
|
279
|
-
def test_backup_list_performance_many_backups(self):
|
|
280
|
-
"""Test backup listing performance with many backup files."""
|
|
281
|
-
hostname = "claude-code"
|
|
282
|
-
config_data = self.test_data.load_host_agnostic_config("simple_server")
|
|
283
|
-
config_file = self.temp_dir / "test_config.json"
|
|
284
|
-
|
|
285
|
-
with open(config_file, 'w') as f:
|
|
286
|
-
json.dump(config_data, f)
|
|
287
|
-
|
|
288
|
-
# Create many backups
|
|
289
|
-
for i in range(50):
|
|
290
|
-
result = self.backup_manager.create_backup(config_file, hostname)
|
|
291
|
-
self.assertTrue(result.success)
|
|
292
|
-
|
|
293
|
-
# Test listing performance
|
|
294
|
-
start_time = time.time()
|
|
295
|
-
backups = self.backup_manager.list_backups(hostname)
|
|
296
|
-
duration = time.time() - start_time
|
|
297
|
-
|
|
298
|
-
self.assertEqual(len(backups), 50)
|
|
299
|
-
self.assertLess(duration, 0.1) # Should be fast even with many backups
|
|
300
|
-
|
|
301
|
-
# Verify all backups are valid Pydantic models
|
|
302
|
-
for backup in backups:
|
|
303
|
-
self.assertIsInstance(backup, BackupInfo)
|
|
304
|
-
self.assertEqual(backup.hostname, hostname)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if __name__ == '__main__':
|
|
308
|
-
unittest.main()
|