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.
Files changed (81) hide show
  1. hatch/__init__.py +1 -1
  2. hatch/cli/__init__.py +71 -0
  3. hatch/cli/__main__.py +1035 -0
  4. hatch/cli/cli_env.py +865 -0
  5. hatch/cli/cli_mcp.py +1965 -0
  6. hatch/cli/cli_package.py +566 -0
  7. hatch/cli/cli_system.py +136 -0
  8. hatch/cli/cli_utils.py +1289 -0
  9. hatch/cli_hatch.py +160 -2838
  10. hatch/mcp_host_config/__init__.py +10 -10
  11. hatch/mcp_host_config/adapters/__init__.py +34 -0
  12. hatch/mcp_host_config/adapters/base.py +170 -0
  13. hatch/mcp_host_config/adapters/claude.py +105 -0
  14. hatch/mcp_host_config/adapters/codex.py +104 -0
  15. hatch/mcp_host_config/adapters/cursor.py +83 -0
  16. hatch/mcp_host_config/adapters/gemini.py +75 -0
  17. hatch/mcp_host_config/adapters/kiro.py +78 -0
  18. hatch/mcp_host_config/adapters/lmstudio.py +79 -0
  19. hatch/mcp_host_config/adapters/registry.py +149 -0
  20. hatch/mcp_host_config/adapters/vscode.py +83 -0
  21. hatch/mcp_host_config/backup.py +5 -3
  22. hatch/mcp_host_config/fields.py +126 -0
  23. hatch/mcp_host_config/models.py +161 -456
  24. hatch/mcp_host_config/reporting.py +57 -16
  25. hatch/mcp_host_config/strategies.py +155 -87
  26. hatch/template_generator.py +1 -1
  27. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
  28. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
  29. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
  30. hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
  31. tests/cli_test_utils.py +280 -0
  32. tests/integration/cli/__init__.py +14 -0
  33. tests/integration/cli/test_cli_reporter_integration.py +2439 -0
  34. tests/integration/mcp/__init__.py +0 -0
  35. tests/integration/mcp/test_adapter_serialization.py +173 -0
  36. tests/regression/cli/__init__.py +16 -0
  37. tests/regression/cli/test_color_logic.py +268 -0
  38. tests/regression/cli/test_consequence_type.py +298 -0
  39. tests/regression/cli/test_error_formatting.py +328 -0
  40. tests/regression/cli/test_result_reporter.py +586 -0
  41. tests/regression/cli/test_table_formatter.py +211 -0
  42. tests/regression/mcp/__init__.py +0 -0
  43. tests/regression/mcp/test_field_filtering.py +162 -0
  44. tests/test_cli_version.py +7 -5
  45. tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
  46. tests/unit/__init__.py +0 -0
  47. tests/unit/mcp/__init__.py +0 -0
  48. tests/unit/mcp/test_adapter_protocol.py +138 -0
  49. tests/unit/mcp/test_adapter_registry.py +158 -0
  50. tests/unit/mcp/test_config_model.py +146 -0
  51. hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
  52. tests/integration/test_mcp_kiro_integration.py +0 -153
  53. tests/regression/test_mcp_codex_backup_integration.py +0 -162
  54. tests/regression/test_mcp_codex_host_strategy.py +0 -163
  55. tests/regression/test_mcp_codex_model_validation.py +0 -117
  56. tests/regression/test_mcp_kiro_backup_integration.py +0 -241
  57. tests/regression/test_mcp_kiro_cli_integration.py +0 -141
  58. tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
  59. tests/regression/test_mcp_kiro_host_strategy.py +0 -214
  60. tests/regression/test_mcp_kiro_model_validation.py +0 -116
  61. tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
  62. tests/test_mcp_atomic_operations.py +0 -276
  63. tests/test_mcp_backup_integration.py +0 -308
  64. tests/test_mcp_cli_all_host_specific_args.py +0 -496
  65. tests/test_mcp_cli_backup_management.py +0 -295
  66. tests/test_mcp_cli_direct_management.py +0 -456
  67. tests/test_mcp_cli_discovery_listing.py +0 -582
  68. tests/test_mcp_cli_host_config_integration.py +0 -823
  69. tests/test_mcp_cli_package_management.py +0 -360
  70. tests/test_mcp_cli_partial_updates.py +0 -859
  71. tests/test_mcp_environment_integration.py +0 -520
  72. tests/test_mcp_host_config_backup.py +0 -257
  73. tests/test_mcp_host_configuration_manager.py +0 -331
  74. tests/test_mcp_host_registry_decorator.py +0 -348
  75. tests/test_mcp_pydantic_architecture_v4.py +0 -603
  76. tests/test_mcp_server_config_models.py +0 -242
  77. tests/test_mcp_server_config_type_field.py +0 -221
  78. tests/test_mcp_sync_functionality.py +0 -316
  79. tests/test_mcp_user_feedback_reporting.py +0 -359
  80. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
  81. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
@@ -1,316 +0,0 @@
1
- """
2
- Test suite for MCP synchronization functionality (Phase 3f).
3
-
4
- This module contains comprehensive tests for the advanced synchronization
5
- features including cross-environment and cross-host synchronization.
6
- """
7
-
8
- import unittest
9
- from unittest.mock import MagicMock, patch, call
10
- from pathlib import Path
11
- import tempfile
12
- import json
13
- from typing import Dict, List, Optional
14
-
15
- # Import test decorators from wobble framework
16
- from wobble import integration_test, regression_test
17
-
18
- # Import the modules we'll be testing
19
- from hatch.mcp_host_config.host_management import MCPHostConfigurationManager, MCPHostType
20
- from hatch.mcp_host_config.models import (
21
- EnvironmentData, MCPServerConfig, SyncResult, ConfigurationResult
22
- )
23
- from hatch.cli_hatch import handle_mcp_sync, parse_host_list, main
24
-
25
-
26
- class TestMCPSyncConfigurations(unittest.TestCase):
27
- """Test suite for MCPHostConfigurationManager.sync_configurations() method."""
28
-
29
- def setUp(self):
30
- """Set up test fixtures."""
31
- self.temp_dir = tempfile.mkdtemp()
32
- self.manager = MCPHostConfigurationManager()
33
-
34
- # We'll use mocks instead of real data objects to avoid validation issues
35
-
36
- @regression_test
37
- def test_sync_from_environment_to_single_host(self):
38
- """Test basic environment-to-host synchronization."""
39
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
40
- mock_result = SyncResult(
41
- success=True,
42
- results=[ConfigurationResult(success=True, hostname="claude-desktop")],
43
- servers_synced=2,
44
- hosts_updated=1
45
- )
46
- mock_sync.return_value = mock_result
47
-
48
- result = self.manager.sync_configurations(
49
- from_env="test-env",
50
- to_hosts=["claude-desktop"]
51
- )
52
-
53
- self.assertTrue(result.success)
54
- self.assertEqual(result.servers_synced, 2)
55
- self.assertEqual(result.hosts_updated, 1)
56
- mock_sync.assert_called_once()
57
-
58
- @integration_test(scope="component")
59
- def test_sync_from_environment_to_multiple_hosts(self):
60
- """Test environment-to-multiple-hosts synchronization."""
61
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
62
- mock_result = SyncResult(
63
- success=True,
64
- results=[
65
- ConfigurationResult(success=True, hostname="claude-desktop"),
66
- ConfigurationResult(success=True, hostname="cursor")
67
- ],
68
- servers_synced=4,
69
- hosts_updated=2
70
- )
71
- mock_sync.return_value = mock_result
72
-
73
- result = self.manager.sync_configurations(
74
- from_env="test-env",
75
- to_hosts=["claude-desktop", "cursor"]
76
- )
77
-
78
- self.assertTrue(result.success)
79
- self.assertEqual(result.servers_synced, 4)
80
- self.assertEqual(result.hosts_updated, 2)
81
-
82
- @integration_test(scope="component")
83
- def test_sync_from_host_to_host(self):
84
- """Test host-to-host configuration synchronization."""
85
- # This test will validate the new host-to-host sync functionality
86
- # that needs to be implemented
87
- with patch.object(self.manager.host_registry, 'get_strategy') as mock_get_strategy:
88
- mock_strategy = MagicMock()
89
- mock_strategy.read_configuration.return_value = MagicMock()
90
- mock_strategy.write_configuration.return_value = True
91
- mock_get_strategy.return_value = mock_strategy
92
-
93
- # Mock the sync_configurations method that we'll implement
94
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
95
- mock_result = SyncResult(
96
- success=True,
97
- results=[ConfigurationResult(success=True, hostname="cursor")],
98
- servers_synced=2,
99
- hosts_updated=1
100
- )
101
- mock_sync.return_value = mock_result
102
-
103
- result = self.manager.sync_configurations(
104
- from_host="claude-desktop",
105
- to_hosts=["cursor"]
106
- )
107
-
108
- self.assertTrue(result.success)
109
- self.assertEqual(result.hosts_updated, 1)
110
-
111
- @integration_test(scope="component")
112
- def test_sync_with_server_name_filter(self):
113
- """Test synchronization with specific server names."""
114
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
115
- mock_result = SyncResult(
116
- success=True,
117
- results=[ConfigurationResult(success=True, hostname="claude-desktop")],
118
- servers_synced=1, # Only one server due to filtering
119
- hosts_updated=1
120
- )
121
- mock_sync.return_value = mock_result
122
-
123
- result = self.manager.sync_configurations(
124
- from_env="test-env",
125
- to_hosts=["claude-desktop"],
126
- servers=["weather-toolkit"]
127
- )
128
-
129
- self.assertTrue(result.success)
130
- self.assertEqual(result.servers_synced, 1)
131
-
132
- @integration_test(scope="component")
133
- def test_sync_with_pattern_filter(self):
134
- """Test synchronization with regex pattern filter."""
135
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
136
- mock_result = SyncResult(
137
- success=True,
138
- results=[ConfigurationResult(success=True, hostname="claude-desktop")],
139
- servers_synced=1, # Only servers matching pattern
140
- hosts_updated=1
141
- )
142
- mock_sync.return_value = mock_result
143
-
144
- result = self.manager.sync_configurations(
145
- from_env="test-env",
146
- to_hosts=["claude-desktop"],
147
- pattern="weather-.*"
148
- )
149
-
150
- self.assertTrue(result.success)
151
- self.assertEqual(result.servers_synced, 1)
152
-
153
- @regression_test
154
- def test_sync_invalid_source_environment(self):
155
- """Test synchronization with non-existent source environment."""
156
- with patch.object(self.manager, 'sync_configurations') as mock_sync:
157
- mock_result = SyncResult(
158
- success=False,
159
- results=[ConfigurationResult(
160
- success=False,
161
- hostname="claude-desktop",
162
- error_message="Environment 'nonexistent' not found"
163
- )],
164
- servers_synced=0,
165
- hosts_updated=0
166
- )
167
- mock_sync.return_value = mock_result
168
-
169
- result = self.manager.sync_configurations(
170
- from_env="nonexistent",
171
- to_hosts=["claude-desktop"]
172
- )
173
-
174
- self.assertFalse(result.success)
175
- self.assertEqual(result.servers_synced, 0)
176
-
177
- @regression_test
178
- def test_sync_no_source_specified(self):
179
- """Test synchronization without source specification."""
180
- with self.assertRaises(ValueError) as context:
181
- self.manager.sync_configurations(to_hosts=["claude-desktop"])
182
-
183
- self.assertIn("Must specify either from_env or from_host", str(context.exception))
184
-
185
- @regression_test
186
- def test_sync_both_sources_specified(self):
187
- """Test synchronization with both env and host sources."""
188
- with self.assertRaises(ValueError) as context:
189
- self.manager.sync_configurations(
190
- from_env="test-env",
191
- from_host="claude-desktop",
192
- to_hosts=["cursor"]
193
- )
194
-
195
- self.assertIn("Cannot specify both from_env and from_host", str(context.exception))
196
-
197
-
198
- class TestMCPSyncCommandParsing(unittest.TestCase):
199
- """Test suite for MCP sync command argument parsing."""
200
-
201
- @regression_test
202
- def test_sync_command_basic_parsing(self):
203
- """Test basic sync command argument parsing."""
204
- test_args = [
205
- 'hatch', 'mcp', 'sync',
206
- '--from-env', 'test-env',
207
- '--to-host', 'claude-desktop'
208
- ]
209
-
210
- with patch('sys.argv', test_args):
211
- with patch('hatch.cli_hatch.HatchEnvironmentManager'):
212
- with patch('hatch.cli_hatch.handle_mcp_sync', return_value=0) as mock_handler:
213
- try:
214
- main()
215
- mock_handler.assert_called_once_with(
216
- from_env='test-env',
217
- from_host=None,
218
- to_hosts='claude-desktop',
219
- servers=None,
220
- pattern=None,
221
- dry_run=False,
222
- auto_approve=False,
223
- no_backup=False
224
- )
225
- except SystemExit as e:
226
- self.assertEqual(e.code, 0)
227
-
228
- @regression_test
229
- def test_sync_command_with_filters(self):
230
- """Test sync command with server filters."""
231
- test_args = [
232
- 'hatch', 'mcp', 'sync',
233
- '--from-env', 'test-env',
234
- '--to-host', 'claude-desktop,cursor',
235
- '--servers', 'weather-api,file-manager',
236
- '--dry-run'
237
- ]
238
-
239
- with patch('sys.argv', test_args):
240
- with patch('hatch.cli_hatch.HatchEnvironmentManager'):
241
- with patch('hatch.cli_hatch.handle_mcp_sync', return_value=0) as mock_handler:
242
- try:
243
- main()
244
- mock_handler.assert_called_once_with(
245
- from_env='test-env',
246
- from_host=None,
247
- to_hosts='claude-desktop,cursor',
248
- servers='weather-api,file-manager',
249
- pattern=None,
250
- dry_run=True,
251
- auto_approve=False,
252
- no_backup=False
253
- )
254
- except SystemExit as e:
255
- self.assertEqual(e.code, 0)
256
-
257
-
258
- class TestMCPSyncCommandHandler(unittest.TestCase):
259
- """Test suite for MCP sync command handler."""
260
-
261
- @integration_test(scope="component")
262
- def test_handle_sync_environment_to_host(self):
263
- """Test sync handler for environment-to-host operation."""
264
- with patch('hatch.cli_hatch.MCPHostConfigurationManager') as mock_manager_class:
265
- mock_manager = MagicMock()
266
- mock_result = SyncResult(
267
- success=True,
268
- results=[ConfigurationResult(success=True, hostname="claude-desktop")],
269
- servers_synced=2,
270
- hosts_updated=1
271
- )
272
- mock_manager.sync_configurations.return_value = mock_result
273
- mock_manager_class.return_value = mock_manager
274
-
275
- with patch('builtins.print') as mock_print:
276
- with patch('hatch.cli_hatch.parse_host_list') as mock_parse:
277
- with patch('hatch.cli_hatch.request_confirmation', return_value=True) as mock_confirm:
278
- from hatch.mcp_host_config.models import MCPHostType
279
- mock_parse.return_value = [MCPHostType.CLAUDE_DESKTOP]
280
-
281
- result = handle_mcp_sync(
282
- from_env="test-env",
283
- to_hosts="claude-desktop"
284
- )
285
-
286
- self.assertEqual(result, 0)
287
- mock_manager.sync_configurations.assert_called_once()
288
- mock_confirm.assert_called_once()
289
-
290
- # Verify success output
291
- print_calls = [call[0][0] for call in mock_print.call_args_list]
292
- self.assertTrue(any("[SUCCESS] Synchronization completed" in call for call in print_calls))
293
-
294
- @integration_test(scope="component")
295
- def test_handle_sync_dry_run(self):
296
- """Test sync handler dry-run functionality."""
297
- with patch('builtins.print') as mock_print:
298
- with patch('hatch.cli_hatch.parse_host_list') as mock_parse:
299
- from hatch.mcp_host_config.models import MCPHostType
300
- mock_parse.return_value = [MCPHostType.CLAUDE_DESKTOP]
301
-
302
- result = handle_mcp_sync(
303
- from_env="test-env",
304
- to_hosts="claude-desktop",
305
- dry_run=True
306
- )
307
-
308
- self.assertEqual(result, 0)
309
-
310
- # Verify dry-run output
311
- print_calls = [call[0][0] for call in mock_print.call_args_list]
312
- self.assertTrue(any("[DRY RUN] Would synchronize" in call for call in print_calls))
313
-
314
-
315
- if __name__ == '__main__':
316
- unittest.main()
@@ -1,359 +0,0 @@
1
- """
2
- Test suite for MCP user feedback reporting system.
3
-
4
- This module tests the FieldOperation and ConversionReport models,
5
- generate_conversion_report() function, and display_report() function.
6
- """
7
-
8
- import unittest
9
- import sys
10
- from pathlib import Path
11
- from io import StringIO
12
-
13
- # Add the parent directory to the path to import wobble
14
- sys.path.insert(0, str(Path(__file__).parent.parent))
15
-
16
- try:
17
- from wobble.decorators import regression_test
18
- except ImportError:
19
- # Fallback decorator if wobble is not available
20
- def regression_test(func):
21
- return func
22
-
23
- from hatch.mcp_host_config.reporting import (
24
- FieldOperation,
25
- ConversionReport,
26
- generate_conversion_report,
27
- display_report
28
- )
29
- from hatch.mcp_host_config.models import (
30
- MCPServerConfigOmni,
31
- MCPHostType
32
- )
33
-
34
-
35
- class TestFieldOperation(unittest.TestCase):
36
- """Test suite for FieldOperation model."""
37
-
38
- @regression_test
39
- def test_field_operation_updated_str_representation(self):
40
- """Test UPDATED operation string representation."""
41
- field_op = FieldOperation(
42
- field_name="command",
43
- operation="UPDATED",
44
- old_value="old_command",
45
- new_value="new_command"
46
- )
47
-
48
- result = str(field_op)
49
-
50
- # Verify ASCII arrow used (not Unicode)
51
- self.assertIn("-->", result)
52
- self.assertNotIn("→", result)
53
-
54
- # Verify format
55
- self.assertEqual(result, "command: UPDATED 'old_command' --> 'new_command'")
56
-
57
- @regression_test
58
- def test_field_operation_updated_with_none_old_value(self):
59
- """Test UPDATED operation with None old_value (field added)."""
60
- field_op = FieldOperation(
61
- field_name="timeout",
62
- operation="UPDATED",
63
- old_value=None,
64
- new_value=30000
65
- )
66
-
67
- result = str(field_op)
68
-
69
- # Verify None is displayed
70
- self.assertEqual(result, "timeout: UPDATED None --> 30000")
71
-
72
- @regression_test
73
- def test_field_operation_unsupported_str_representation(self):
74
- """Test UNSUPPORTED operation string representation."""
75
- field_op = FieldOperation(
76
- field_name="envFile",
77
- operation="UNSUPPORTED",
78
- new_value=".env"
79
- )
80
-
81
- result = str(field_op)
82
-
83
- # Verify format
84
- self.assertEqual(result, "envFile: UNSUPPORTED")
85
-
86
- @regression_test
87
- def test_field_operation_unchanged_str_representation(self):
88
- """Test UNCHANGED operation string representation."""
89
- field_op = FieldOperation(
90
- field_name="name",
91
- operation="UNCHANGED",
92
- new_value="my-server"
93
- )
94
-
95
- result = str(field_op)
96
-
97
- # Verify format
98
- self.assertEqual(result, "name: UNCHANGED 'my-server'")
99
-
100
-
101
- class TestConversionReport(unittest.TestCase):
102
- """Test suite for ConversionReport model."""
103
-
104
- @regression_test
105
- def test_conversion_report_create_operation(self):
106
- """Test ConversionReport with create operation."""
107
- report = ConversionReport(
108
- operation="create",
109
- server_name="my-server",
110
- target_host=MCPHostType.GEMINI,
111
- field_operations=[
112
- FieldOperation(field_name="command", operation="UPDATED", old_value=None, new_value="python")
113
- ]
114
- )
115
-
116
- self.assertEqual(report.operation, "create")
117
- self.assertEqual(report.server_name, "my-server")
118
- self.assertEqual(report.target_host, MCPHostType.GEMINI)
119
- self.assertTrue(report.success)
120
- self.assertIsNone(report.error_message)
121
- self.assertEqual(len(report.field_operations), 1)
122
- self.assertFalse(report.dry_run)
123
-
124
- @regression_test
125
- def test_conversion_report_update_operation(self):
126
- """Test ConversionReport with update operation."""
127
- report = ConversionReport(
128
- operation="update",
129
- server_name="my-server",
130
- target_host=MCPHostType.VSCODE,
131
- field_operations=[
132
- FieldOperation(field_name="command", operation="UPDATED", old_value="old", new_value="new"),
133
- FieldOperation(field_name="name", operation="UNCHANGED", new_value="my-server")
134
- ]
135
- )
136
-
137
- self.assertEqual(report.operation, "update")
138
- self.assertEqual(len(report.field_operations), 2)
139
-
140
- @regression_test
141
- def test_conversion_report_migrate_operation(self):
142
- """Test ConversionReport with migrate operation."""
143
- report = ConversionReport(
144
- operation="migrate",
145
- server_name="my-server",
146
- source_host=MCPHostType.GEMINI,
147
- target_host=MCPHostType.VSCODE,
148
- field_operations=[]
149
- )
150
-
151
- self.assertEqual(report.operation, "migrate")
152
- self.assertEqual(report.source_host, MCPHostType.GEMINI)
153
- self.assertEqual(report.target_host, MCPHostType.VSCODE)
154
-
155
-
156
- class TestGenerateConversionReport(unittest.TestCase):
157
- """Test suite for generate_conversion_report() function."""
158
-
159
- @regression_test
160
- def test_generate_report_create_operation_all_supported(self):
161
- """Test generate_conversion_report for create with all supported fields."""
162
- omni = MCPServerConfigOmni(
163
- name="gemini-server",
164
- command="npx",
165
- args=["-y", "server"],
166
- cwd="/path/to/dir",
167
- timeout=30000
168
- )
169
-
170
- report = generate_conversion_report(
171
- operation="create",
172
- server_name="gemini-server",
173
- target_host=MCPHostType.GEMINI,
174
- omni=omni
175
- )
176
-
177
- # Verify all fields are UPDATED (create operation)
178
- self.assertEqual(report.operation, "create")
179
- self.assertEqual(report.server_name, "gemini-server")
180
- self.assertEqual(report.target_host, MCPHostType.GEMINI)
181
-
182
- # All set fields should be UPDATED
183
- updated_ops = [op for op in report.field_operations if op.operation == "UPDATED"]
184
- self.assertEqual(len(updated_ops), 5) # name, command, args, cwd, timeout
185
-
186
- # No unsupported fields
187
- unsupported_ops = [op for op in report.field_operations if op.operation == "UNSUPPORTED"]
188
- self.assertEqual(len(unsupported_ops), 0)
189
-
190
- @regression_test
191
- def test_generate_report_create_operation_with_unsupported(self):
192
- """Test generate_conversion_report with unsupported fields."""
193
- omni = MCPServerConfigOmni(
194
- name="gemini-server",
195
- command="python",
196
- cwd="/path/to/dir", # Gemini field
197
- envFile=".env" # VS Code field (unsupported by Gemini)
198
- )
199
-
200
- report = generate_conversion_report(
201
- operation="create",
202
- server_name="gemini-server",
203
- target_host=MCPHostType.GEMINI,
204
- omni=omni
205
- )
206
-
207
- # Verify Gemini fields are UPDATED
208
- updated_ops = [op for op in report.field_operations if op.operation == "UPDATED"]
209
- updated_fields = {op.field_name for op in updated_ops}
210
- self.assertIn("name", updated_fields)
211
- self.assertIn("command", updated_fields)
212
- self.assertIn("cwd", updated_fields)
213
-
214
- # Verify VS Code field is UNSUPPORTED
215
- unsupported_ops = [op for op in report.field_operations if op.operation == "UNSUPPORTED"]
216
- self.assertEqual(len(unsupported_ops), 1)
217
- self.assertEqual(unsupported_ops[0].field_name, "envFile")
218
-
219
- @regression_test
220
- def test_generate_report_update_operation(self):
221
- """Test generate_conversion_report for update operation."""
222
- old_config = MCPServerConfigOmni(
223
- name="my-server",
224
- command="python",
225
- args=["old.py"]
226
- )
227
-
228
- new_omni = MCPServerConfigOmni(
229
- name="my-server",
230
- command="python",
231
- args=["new.py"]
232
- )
233
-
234
- report = generate_conversion_report(
235
- operation="update",
236
- server_name="my-server",
237
- target_host=MCPHostType.GEMINI,
238
- omni=new_omni,
239
- old_config=old_config
240
- )
241
-
242
- # Verify name and command are UNCHANGED
243
- unchanged_ops = [op for op in report.field_operations if op.operation == "UNCHANGED"]
244
- unchanged_fields = {op.field_name for op in unchanged_ops}
245
- self.assertIn("name", unchanged_fields)
246
- self.assertIn("command", unchanged_fields)
247
-
248
- # Verify args is UPDATED
249
- updated_ops = [op for op in report.field_operations if op.operation == "UPDATED"]
250
- self.assertEqual(len(updated_ops), 1)
251
- self.assertEqual(updated_ops[0].field_name, "args")
252
- self.assertEqual(updated_ops[0].old_value, ["old.py"])
253
- self.assertEqual(updated_ops[0].new_value, ["new.py"])
254
-
255
- @regression_test
256
- def test_generate_report_dynamic_field_derivation(self):
257
- """Test that generate_conversion_report uses dynamic field derivation."""
258
- omni = MCPServerConfigOmni(
259
- name="test-server",
260
- command="python"
261
- )
262
-
263
- # Generate report for Gemini
264
- report_gemini = generate_conversion_report(
265
- operation="create",
266
- server_name="test-server",
267
- target_host=MCPHostType.GEMINI,
268
- omni=omni
269
- )
270
-
271
- # All fields should be UPDATED (no unsupported)
272
- unsupported_ops = [op for op in report_gemini.field_operations if op.operation == "UNSUPPORTED"]
273
- self.assertEqual(len(unsupported_ops), 0)
274
-
275
-
276
- class TestDisplayReport(unittest.TestCase):
277
- """Test suite for display_report() function."""
278
-
279
- @regression_test
280
- def test_display_report_create_operation(self):
281
- """Test display_report for create operation."""
282
- report = ConversionReport(
283
- operation="create",
284
- server_name="my-server",
285
- target_host=MCPHostType.GEMINI,
286
- field_operations=[
287
- FieldOperation(field_name="command", operation="UPDATED", old_value=None, new_value="python")
288
- ]
289
- )
290
-
291
- # Capture stdout
292
- captured_output = StringIO()
293
- sys.stdout = captured_output
294
-
295
- display_report(report)
296
-
297
- sys.stdout = sys.__stdout__
298
- output = captured_output.getvalue()
299
-
300
- # Verify header
301
- self.assertIn("Server 'my-server' created for host", output)
302
- self.assertIn("gemini", output.lower())
303
-
304
- # Verify field operation displayed
305
- self.assertIn("command: UPDATED", output)
306
-
307
- @regression_test
308
- def test_display_report_update_operation(self):
309
- """Test display_report for update operation."""
310
- report = ConversionReport(
311
- operation="update",
312
- server_name="my-server",
313
- target_host=MCPHostType.VSCODE,
314
- field_operations=[
315
- FieldOperation(field_name="args", operation="UPDATED", old_value=["old.py"], new_value=["new.py"])
316
- ]
317
- )
318
-
319
- # Capture stdout
320
- captured_output = StringIO()
321
- sys.stdout = captured_output
322
-
323
- display_report(report)
324
-
325
- sys.stdout = sys.__stdout__
326
- output = captured_output.getvalue()
327
-
328
- # Verify header
329
- self.assertIn("Server 'my-server' updated for host", output)
330
-
331
- @regression_test
332
- def test_display_report_dry_run(self):
333
- """Test display_report for dry-run mode."""
334
- report = ConversionReport(
335
- operation="create",
336
- server_name="my-server",
337
- target_host=MCPHostType.GEMINI,
338
- field_operations=[],
339
- dry_run=True
340
- )
341
-
342
- # Capture stdout
343
- captured_output = StringIO()
344
- sys.stdout = captured_output
345
-
346
- display_report(report)
347
-
348
- sys.stdout = sys.__stdout__
349
- output = captured_output.getvalue()
350
-
351
- # Verify dry-run header and footer
352
- self.assertIn("[DRY RUN]", output)
353
- self.assertIn("Preview of changes", output)
354
- self.assertIn("No changes were made", output)
355
-
356
-
357
- if __name__ == '__main__':
358
- unittest.main()
359
-