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,162 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Codex MCP Backup Integration Tests
|
|
3
|
-
|
|
4
|
-
Tests for Codex TOML backup integration including backup creation,
|
|
5
|
-
restoration, and the no_backup parameter.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import unittest
|
|
9
|
-
import tempfile
|
|
10
|
-
import tomllib
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
from wobble.decorators import regression_test
|
|
14
|
-
|
|
15
|
-
from hatch.mcp_host_config.strategies import CodexHostStrategy
|
|
16
|
-
from hatch.mcp_host_config.models import MCPServerConfig, HostConfiguration
|
|
17
|
-
from hatch.mcp_host_config.backup import MCPHostConfigBackupManager, BackupInfo
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestCodexBackupIntegration(unittest.TestCase):
|
|
21
|
-
"""Test suite for Codex backup integration."""
|
|
22
|
-
|
|
23
|
-
def setUp(self):
|
|
24
|
-
"""Set up test environment."""
|
|
25
|
-
self.strategy = CodexHostStrategy()
|
|
26
|
-
|
|
27
|
-
@regression_test
|
|
28
|
-
def test_write_configuration_creates_backup_by_default(self):
|
|
29
|
-
"""Test that write_configuration creates backup by default when file exists."""
|
|
30
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
31
|
-
config_path = Path(tmpdir) / "config.toml"
|
|
32
|
-
backup_dir = Path(tmpdir) / "backups"
|
|
33
|
-
|
|
34
|
-
# Create initial config
|
|
35
|
-
initial_toml = """[mcp_servers.old-server]
|
|
36
|
-
command = "old-command"
|
|
37
|
-
"""
|
|
38
|
-
config_path.write_text(initial_toml)
|
|
39
|
-
|
|
40
|
-
# Create new configuration
|
|
41
|
-
new_config = HostConfiguration(servers={
|
|
42
|
-
'new-server': MCPServerConfig(
|
|
43
|
-
command='new-command',
|
|
44
|
-
args=['--test']
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
# Patch paths
|
|
49
|
-
from unittest.mock import patch
|
|
50
|
-
with patch.object(self.strategy, 'get_config_path', return_value=config_path):
|
|
51
|
-
with patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager') as MockBackupManager:
|
|
52
|
-
# Create a real backup manager with custom backup dir
|
|
53
|
-
real_backup_manager = MCPHostConfigBackupManager(backup_root=backup_dir)
|
|
54
|
-
MockBackupManager.return_value = real_backup_manager
|
|
55
|
-
|
|
56
|
-
# Write configuration (should create backup)
|
|
57
|
-
success = self.strategy.write_configuration(new_config, no_backup=False)
|
|
58
|
-
self.assertTrue(success)
|
|
59
|
-
|
|
60
|
-
# Verify backup was created
|
|
61
|
-
backup_files = list(backup_dir.glob('codex/*.toml.*'))
|
|
62
|
-
self.assertGreater(len(backup_files), 0, "Backup file should be created")
|
|
63
|
-
|
|
64
|
-
@regression_test
|
|
65
|
-
def test_write_configuration_skips_backup_when_requested(self):
|
|
66
|
-
"""Test that write_configuration skips backup when no_backup=True."""
|
|
67
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
68
|
-
config_path = Path(tmpdir) / "config.toml"
|
|
69
|
-
backup_dir = Path(tmpdir) / "backups"
|
|
70
|
-
|
|
71
|
-
# Create initial config
|
|
72
|
-
initial_toml = """[mcp_servers.old-server]
|
|
73
|
-
command = "old-command"
|
|
74
|
-
"""
|
|
75
|
-
config_path.write_text(initial_toml)
|
|
76
|
-
|
|
77
|
-
# Create new configuration
|
|
78
|
-
new_config = HostConfiguration(servers={
|
|
79
|
-
'new-server': MCPServerConfig(
|
|
80
|
-
command='new-command'
|
|
81
|
-
)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
# Patch paths
|
|
85
|
-
from unittest.mock import patch
|
|
86
|
-
with patch.object(self.strategy, 'get_config_path', return_value=config_path):
|
|
87
|
-
with patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager') as MockBackupManager:
|
|
88
|
-
real_backup_manager = MCPHostConfigBackupManager(backup_root=backup_dir)
|
|
89
|
-
MockBackupManager.return_value = real_backup_manager
|
|
90
|
-
|
|
91
|
-
# Write configuration with no_backup=True
|
|
92
|
-
success = self.strategy.write_configuration(new_config, no_backup=True)
|
|
93
|
-
self.assertTrue(success)
|
|
94
|
-
|
|
95
|
-
# Verify no backup was created
|
|
96
|
-
if backup_dir.exists():
|
|
97
|
-
backup_files = list(backup_dir.glob('codex/*.toml.*'))
|
|
98
|
-
self.assertEqual(len(backup_files), 0, "No backup should be created when no_backup=True")
|
|
99
|
-
|
|
100
|
-
@regression_test
|
|
101
|
-
def test_write_configuration_no_backup_for_new_file(self):
|
|
102
|
-
"""Test that no backup is created when writing to a new file."""
|
|
103
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
104
|
-
config_path = Path(tmpdir) / "config.toml"
|
|
105
|
-
backup_dir = Path(tmpdir) / "backups"
|
|
106
|
-
|
|
107
|
-
# Don't create initial file - this is a new file
|
|
108
|
-
|
|
109
|
-
# Create new configuration
|
|
110
|
-
new_config = HostConfiguration(servers={
|
|
111
|
-
'new-server': MCPServerConfig(
|
|
112
|
-
command='new-command'
|
|
113
|
-
)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
# Patch paths
|
|
117
|
-
from unittest.mock import patch
|
|
118
|
-
with patch.object(self.strategy, 'get_config_path', return_value=config_path):
|
|
119
|
-
with patch('hatch.mcp_host_config.strategies.MCPHostConfigBackupManager') as MockBackupManager:
|
|
120
|
-
real_backup_manager = MCPHostConfigBackupManager(backup_root=backup_dir)
|
|
121
|
-
MockBackupManager.return_value = real_backup_manager
|
|
122
|
-
|
|
123
|
-
# Write configuration to new file
|
|
124
|
-
success = self.strategy.write_configuration(new_config, no_backup=False)
|
|
125
|
-
self.assertTrue(success)
|
|
126
|
-
|
|
127
|
-
# Verify file was created
|
|
128
|
-
self.assertTrue(config_path.exists())
|
|
129
|
-
|
|
130
|
-
# Verify no backup was created (nothing to backup)
|
|
131
|
-
if backup_dir.exists():
|
|
132
|
-
backup_files = list(backup_dir.glob('codex/*.toml.*'))
|
|
133
|
-
self.assertEqual(len(backup_files), 0, "No backup for new file")
|
|
134
|
-
|
|
135
|
-
@regression_test
|
|
136
|
-
def test_codex_hostname_supported_in_backup_system(self):
|
|
137
|
-
"""Test that 'codex' hostname is supported by the backup system."""
|
|
138
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
139
|
-
config_path = Path(tmpdir) / "config.toml"
|
|
140
|
-
backup_dir = Path(tmpdir) / "backups"
|
|
141
|
-
|
|
142
|
-
# Create a config file
|
|
143
|
-
config_path.write_text("[mcp_servers.test]\ncommand = 'test'\n")
|
|
144
|
-
|
|
145
|
-
# Create backup manager
|
|
146
|
-
backup_manager = MCPHostConfigBackupManager(backup_root=backup_dir)
|
|
147
|
-
|
|
148
|
-
# Create backup with 'codex' hostname - should not raise validation error
|
|
149
|
-
result = backup_manager.create_backup(config_path, 'codex')
|
|
150
|
-
|
|
151
|
-
# Verify backup succeeded
|
|
152
|
-
self.assertTrue(result.success, "Backup with 'codex' hostname should succeed")
|
|
153
|
-
self.assertIsNotNone(result.backup_path)
|
|
154
|
-
|
|
155
|
-
# Verify backup filename follows pattern
|
|
156
|
-
backup_filename = result.backup_path.name
|
|
157
|
-
self.assertTrue(backup_filename.startswith('config.toml.codex.'))
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if __name__ == '__main__':
|
|
161
|
-
unittest.main()
|
|
162
|
-
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Codex MCP Host Strategy Tests
|
|
3
|
-
|
|
4
|
-
Tests for CodexHostStrategy implementation including path resolution,
|
|
5
|
-
configuration read/write, TOML handling, and host detection.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import unittest
|
|
9
|
-
import tempfile
|
|
10
|
-
import tomllib
|
|
11
|
-
from unittest.mock import patch, mock_open, MagicMock
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
from wobble.decorators import regression_test
|
|
15
|
-
|
|
16
|
-
from hatch.mcp_host_config.strategies import CodexHostStrategy
|
|
17
|
-
from hatch.mcp_host_config.models import MCPServerConfig, HostConfiguration
|
|
18
|
-
|
|
19
|
-
# Import test data loader from local tests module
|
|
20
|
-
import sys
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
23
|
-
from test_data_utils import MCPHostConfigTestDataLoader
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TestCodexHostStrategy(unittest.TestCase):
|
|
27
|
-
"""Test suite for CodexHostStrategy implementation."""
|
|
28
|
-
|
|
29
|
-
def setUp(self):
|
|
30
|
-
"""Set up test environment."""
|
|
31
|
-
self.strategy = CodexHostStrategy()
|
|
32
|
-
self.test_data_loader = MCPHostConfigTestDataLoader()
|
|
33
|
-
|
|
34
|
-
@regression_test
|
|
35
|
-
def test_codex_config_path_resolution(self):
|
|
36
|
-
"""Test Codex configuration path resolution."""
|
|
37
|
-
config_path = self.strategy.get_config_path()
|
|
38
|
-
|
|
39
|
-
# Verify path structure (use normalized path for cross-platform compatibility)
|
|
40
|
-
self.assertIsNotNone(config_path)
|
|
41
|
-
normalized_path = str(config_path).replace('\\', '/')
|
|
42
|
-
self.assertTrue(normalized_path.endswith('.codex/config.toml'))
|
|
43
|
-
self.assertEqual(config_path.name, 'config.toml')
|
|
44
|
-
self.assertEqual(config_path.suffix, '.toml') # Verify TOML extension
|
|
45
|
-
|
|
46
|
-
@regression_test
|
|
47
|
-
def test_codex_config_key(self):
|
|
48
|
-
"""Test Codex configuration key."""
|
|
49
|
-
config_key = self.strategy.get_config_key()
|
|
50
|
-
# Codex uses underscore, not camelCase
|
|
51
|
-
self.assertEqual(config_key, "mcp_servers")
|
|
52
|
-
self.assertNotEqual(config_key, "mcpServers") # Verify different from other hosts
|
|
53
|
-
|
|
54
|
-
@regression_test
|
|
55
|
-
def test_codex_server_config_validation_stdio(self):
|
|
56
|
-
"""Test Codex STDIO server configuration validation."""
|
|
57
|
-
# Test local server validation
|
|
58
|
-
local_config = MCPServerConfig(
|
|
59
|
-
command="npx",
|
|
60
|
-
args=["-y", "package"]
|
|
61
|
-
)
|
|
62
|
-
self.assertTrue(self.strategy.validate_server_config(local_config))
|
|
63
|
-
|
|
64
|
-
@regression_test
|
|
65
|
-
def test_codex_server_config_validation_http(self):
|
|
66
|
-
"""Test Codex HTTP server configuration validation."""
|
|
67
|
-
# Test remote server validation
|
|
68
|
-
remote_config = MCPServerConfig(
|
|
69
|
-
url="https://api.example.com/mcp"
|
|
70
|
-
)
|
|
71
|
-
self.assertTrue(self.strategy.validate_server_config(remote_config))
|
|
72
|
-
|
|
73
|
-
@patch('pathlib.Path.exists')
|
|
74
|
-
@regression_test
|
|
75
|
-
def test_codex_host_availability_detection(self, mock_exists):
|
|
76
|
-
"""Test Codex host availability detection."""
|
|
77
|
-
# Test when Codex directory exists
|
|
78
|
-
mock_exists.return_value = True
|
|
79
|
-
self.assertTrue(self.strategy.is_host_available())
|
|
80
|
-
|
|
81
|
-
# Test when Codex directory doesn't exist
|
|
82
|
-
mock_exists.return_value = False
|
|
83
|
-
self.assertFalse(self.strategy.is_host_available())
|
|
84
|
-
|
|
85
|
-
@regression_test
|
|
86
|
-
def test_codex_read_configuration_success(self):
|
|
87
|
-
"""Test successful Codex TOML configuration reading."""
|
|
88
|
-
# Load test data
|
|
89
|
-
test_toml_path = Path(__file__).parent.parent / "test_data" / "codex" / "valid_config.toml"
|
|
90
|
-
|
|
91
|
-
with patch.object(self.strategy, 'get_config_path', return_value=test_toml_path):
|
|
92
|
-
config = self.strategy.read_configuration()
|
|
93
|
-
|
|
94
|
-
# Verify configuration was read
|
|
95
|
-
self.assertIsInstance(config, HostConfiguration)
|
|
96
|
-
self.assertIn('context7', config.servers)
|
|
97
|
-
|
|
98
|
-
# Verify server details
|
|
99
|
-
server = config.servers['context7']
|
|
100
|
-
self.assertEqual(server.command, 'npx')
|
|
101
|
-
self.assertEqual(server.args, ['-y', '@upstash/context7-mcp'])
|
|
102
|
-
|
|
103
|
-
# Verify nested env section was parsed correctly
|
|
104
|
-
self.assertIsNotNone(server.env)
|
|
105
|
-
self.assertEqual(server.env.get('MY_VAR'), 'value')
|
|
106
|
-
|
|
107
|
-
@regression_test
|
|
108
|
-
def test_codex_read_configuration_file_not_exists(self):
|
|
109
|
-
"""Test Codex configuration reading when file doesn't exist."""
|
|
110
|
-
non_existent_path = Path("/non/existent/path/config.toml")
|
|
111
|
-
|
|
112
|
-
with patch.object(self.strategy, 'get_config_path', return_value=non_existent_path):
|
|
113
|
-
config = self.strategy.read_configuration()
|
|
114
|
-
|
|
115
|
-
# Should return empty configuration without error
|
|
116
|
-
self.assertIsInstance(config, HostConfiguration)
|
|
117
|
-
self.assertEqual(len(config.servers), 0)
|
|
118
|
-
|
|
119
|
-
@regression_test
|
|
120
|
-
def test_codex_write_configuration_preserves_features(self):
|
|
121
|
-
"""Test that write_configuration preserves [features] section."""
|
|
122
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
123
|
-
config_path = Path(tmpdir) / "config.toml"
|
|
124
|
-
|
|
125
|
-
# Create initial config with features section
|
|
126
|
-
initial_toml = """[features]
|
|
127
|
-
rmcp_client = true
|
|
128
|
-
|
|
129
|
-
[mcp_servers.existing]
|
|
130
|
-
command = "old-command"
|
|
131
|
-
"""
|
|
132
|
-
config_path.write_text(initial_toml)
|
|
133
|
-
|
|
134
|
-
# Create new configuration to write
|
|
135
|
-
new_config = HostConfiguration(servers={
|
|
136
|
-
'new-server': MCPServerConfig(
|
|
137
|
-
command='new-command',
|
|
138
|
-
args=['--test']
|
|
139
|
-
)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
# Write configuration
|
|
143
|
-
with patch.object(self.strategy, 'get_config_path', return_value=config_path):
|
|
144
|
-
success = self.strategy.write_configuration(new_config, no_backup=True)
|
|
145
|
-
self.assertTrue(success)
|
|
146
|
-
|
|
147
|
-
# Read back and verify features section preserved
|
|
148
|
-
with open(config_path, 'rb') as f:
|
|
149
|
-
result_data = tomllib.load(f)
|
|
150
|
-
|
|
151
|
-
# Verify features section preserved
|
|
152
|
-
self.assertIn('features', result_data)
|
|
153
|
-
self.assertTrue(result_data['features'].get('rmcp_client'))
|
|
154
|
-
|
|
155
|
-
# Verify new server added
|
|
156
|
-
self.assertIn('mcp_servers', result_data)
|
|
157
|
-
self.assertIn('new-server', result_data['mcp_servers'])
|
|
158
|
-
self.assertEqual(result_data['mcp_servers']['new-server']['command'], 'new-command')
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if __name__ == '__main__':
|
|
162
|
-
unittest.main()
|
|
163
|
-
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Codex MCP Model Validation Tests
|
|
3
|
-
|
|
4
|
-
Tests for MCPServerConfigCodex model validation including Codex-specific fields,
|
|
5
|
-
Omni conversion, and registry integration.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import unittest
|
|
9
|
-
from wobble.decorators import regression_test
|
|
10
|
-
|
|
11
|
-
from hatch.mcp_host_config.models import (
|
|
12
|
-
MCPServerConfigCodex, MCPServerConfigOmni, MCPHostType, HOST_MODEL_REGISTRY
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestCodexModelValidation(unittest.TestCase):
|
|
17
|
-
"""Test suite for Codex model validation."""
|
|
18
|
-
|
|
19
|
-
@regression_test
|
|
20
|
-
def test_codex_specific_fields_accepted(self):
|
|
21
|
-
"""Test that Codex-specific fields are accepted in MCPServerConfigCodex."""
|
|
22
|
-
# Create model with Codex-specific fields
|
|
23
|
-
config = MCPServerConfigCodex(
|
|
24
|
-
command="npx",
|
|
25
|
-
args=["-y", "package"],
|
|
26
|
-
env={"API_KEY": "test"},
|
|
27
|
-
# Codex-specific fields
|
|
28
|
-
env_vars=["PATH", "HOME"],
|
|
29
|
-
cwd="/workspace",
|
|
30
|
-
startup_timeout_sec=10,
|
|
31
|
-
tool_timeout_sec=60,
|
|
32
|
-
enabled=True,
|
|
33
|
-
enabled_tools=["read", "write"],
|
|
34
|
-
disabled_tools=["delete"],
|
|
35
|
-
bearer_token_env_var="AUTH_TOKEN",
|
|
36
|
-
http_headers={"X-Custom": "value"},
|
|
37
|
-
env_http_headers={"X-Auth": "AUTH_VAR"}
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# Verify all fields are accessible
|
|
41
|
-
self.assertEqual(config.command, "npx")
|
|
42
|
-
self.assertEqual(config.env_vars, ["PATH", "HOME"])
|
|
43
|
-
self.assertEqual(config.cwd, "/workspace")
|
|
44
|
-
self.assertEqual(config.startup_timeout_sec, 10)
|
|
45
|
-
self.assertEqual(config.tool_timeout_sec, 60)
|
|
46
|
-
self.assertTrue(config.enabled)
|
|
47
|
-
self.assertEqual(config.enabled_tools, ["read", "write"])
|
|
48
|
-
self.assertEqual(config.disabled_tools, ["delete"])
|
|
49
|
-
self.assertEqual(config.bearer_token_env_var, "AUTH_TOKEN")
|
|
50
|
-
self.assertEqual(config.http_headers, {"X-Custom": "value"})
|
|
51
|
-
self.assertEqual(config.env_http_headers, {"X-Auth": "AUTH_VAR"})
|
|
52
|
-
|
|
53
|
-
@regression_test
|
|
54
|
-
def test_codex_from_omni_conversion(self):
|
|
55
|
-
"""Test MCPServerConfigCodex.from_omni() conversion."""
|
|
56
|
-
# Create Omni model with Codex-specific fields
|
|
57
|
-
omni = MCPServerConfigOmni(
|
|
58
|
-
command="npx",
|
|
59
|
-
args=["-y", "package"],
|
|
60
|
-
env={"API_KEY": "test"},
|
|
61
|
-
# Codex-specific fields
|
|
62
|
-
env_vars=["PATH"],
|
|
63
|
-
startup_timeout_sec=15,
|
|
64
|
-
tool_timeout_sec=90,
|
|
65
|
-
enabled=True,
|
|
66
|
-
enabled_tools=["read"],
|
|
67
|
-
disabled_tools=["write"],
|
|
68
|
-
bearer_token_env_var="TOKEN",
|
|
69
|
-
headers={"X-Test": "value"}, # Universal field (maps to http_headers in Codex)
|
|
70
|
-
env_http_headers={"X-Env": "VAR"},
|
|
71
|
-
# Non-Codex fields (should be excluded)
|
|
72
|
-
envFile="/path/to/env", # VS Code specific
|
|
73
|
-
disabled=True # Kiro specific
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Convert to Codex model
|
|
77
|
-
codex = MCPServerConfigCodex.from_omni(omni)
|
|
78
|
-
|
|
79
|
-
# Verify Codex fields transferred correctly
|
|
80
|
-
self.assertEqual(codex.command, "npx")
|
|
81
|
-
self.assertEqual(codex.env_vars, ["PATH"])
|
|
82
|
-
self.assertEqual(codex.startup_timeout_sec, 15)
|
|
83
|
-
self.assertEqual(codex.tool_timeout_sec, 90)
|
|
84
|
-
self.assertTrue(codex.enabled)
|
|
85
|
-
self.assertEqual(codex.enabled_tools, ["read"])
|
|
86
|
-
self.assertEqual(codex.disabled_tools, ["write"])
|
|
87
|
-
self.assertEqual(codex.bearer_token_env_var, "TOKEN")
|
|
88
|
-
self.assertEqual(codex.http_headers, {"X-Test": "value"})
|
|
89
|
-
self.assertEqual(codex.env_http_headers, {"X-Env": "VAR"})
|
|
90
|
-
|
|
91
|
-
# Verify non-Codex fields excluded (should not have these attributes)
|
|
92
|
-
with self.assertRaises(AttributeError):
|
|
93
|
-
_ = codex.envFile
|
|
94
|
-
with self.assertRaises(AttributeError):
|
|
95
|
-
_ = codex.disabled
|
|
96
|
-
|
|
97
|
-
@regression_test
|
|
98
|
-
def test_host_model_registry_contains_codex(self):
|
|
99
|
-
"""Test that HOST_MODEL_REGISTRY contains Codex model."""
|
|
100
|
-
# Verify CODEX is in registry
|
|
101
|
-
self.assertIn(MCPHostType.CODEX, HOST_MODEL_REGISTRY)
|
|
102
|
-
|
|
103
|
-
# Verify it maps to correct model class
|
|
104
|
-
self.assertEqual(
|
|
105
|
-
HOST_MODEL_REGISTRY[MCPHostType.CODEX],
|
|
106
|
-
MCPServerConfigCodex
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Verify we can instantiate from registry
|
|
110
|
-
model_class = HOST_MODEL_REGISTRY[MCPHostType.CODEX]
|
|
111
|
-
instance = model_class(command="test")
|
|
112
|
-
self.assertIsInstance(instance, MCPServerConfigCodex)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if __name__ == '__main__':
|
|
116
|
-
unittest.main()
|
|
117
|
-
|
|
@@ -1,241 +0,0 @@
|
|
|
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()
|