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,603 +0,0 @@
1
- """
2
- Test suite for Round 04 v4 Pydantic Model Hierarchy.
3
-
4
- This module tests the new model hierarchy including MCPServerConfigBase,
5
- host-specific models (Gemini, VS Code, Cursor, Claude), MCPServerConfigOmni,
6
- HOST_MODEL_REGISTRY, and from_omni() conversion methods.
7
- """
8
-
9
- import unittest
10
- import sys
11
- from pathlib import Path
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.models import (
24
- MCPServerConfigBase,
25
- MCPServerConfigGemini,
26
- MCPServerConfigVSCode,
27
- MCPServerConfigCursor,
28
- MCPServerConfigClaude,
29
- MCPServerConfigOmni,
30
- HOST_MODEL_REGISTRY,
31
- MCPHostType
32
- )
33
- from pydantic import ValidationError
34
-
35
-
36
- class TestMCPServerConfigBase(unittest.TestCase):
37
- """Test suite for MCPServerConfigBase model."""
38
-
39
- @regression_test
40
- def test_base_model_local_server_validation_success(self):
41
- """Test successful local server configuration with type inference."""
42
- config = MCPServerConfigBase(
43
- name="test-server",
44
- command="python",
45
- args=["server.py"],
46
- env={"API_KEY": "test"}
47
- )
48
-
49
- self.assertEqual(config.command, "python")
50
- self.assertEqual(config.type, "stdio") # Inferred from command
51
- self.assertEqual(len(config.args), 1)
52
- self.assertEqual(config.env["API_KEY"], "test")
53
-
54
- @regression_test
55
- def test_base_model_remote_server_validation_success(self):
56
- """Test successful remote server configuration with type inference."""
57
- config = MCPServerConfigBase(
58
- name="test-server",
59
- url="https://api.example.com/mcp",
60
- headers={"Authorization": "Bearer token"}
61
- )
62
-
63
- self.assertEqual(config.url, "https://api.example.com/mcp")
64
- self.assertEqual(config.type, "sse") # Inferred from url (default to sse)
65
- self.assertEqual(config.headers["Authorization"], "Bearer token")
66
-
67
- @regression_test
68
- def test_base_model_mutual_exclusion_validation_fails(self):
69
- """Test validation fails when both command and url provided."""
70
- with self.assertRaises(ValidationError) as context:
71
- MCPServerConfigBase(
72
- name="test-server",
73
- command="python",
74
- url="https://api.example.com/mcp"
75
- )
76
-
77
- self.assertIn("Cannot specify both 'command' and 'url'", str(context.exception))
78
-
79
- @regression_test
80
- def test_base_model_type_field_stdio_validation(self):
81
- """Test type=stdio validation."""
82
- # Valid: type=stdio with command
83
- config = MCPServerConfigBase(
84
- name="test-server",
85
- type="stdio",
86
- command="python"
87
- )
88
- self.assertEqual(config.type, "stdio")
89
- self.assertEqual(config.command, "python")
90
-
91
- # Invalid: type=stdio without command
92
- with self.assertRaises(ValidationError) as context:
93
- MCPServerConfigBase(
94
- name="test-server",
95
- type="stdio",
96
- url="https://api.example.com/mcp"
97
- )
98
- self.assertIn("'command' is required for stdio transport", str(context.exception))
99
-
100
- @regression_test
101
- def test_base_model_type_field_sse_validation(self):
102
- """Test type=sse validation."""
103
- # Valid: type=sse with url
104
- config = MCPServerConfigBase(
105
- name="test-server",
106
- type="sse",
107
- url="https://api.example.com/mcp"
108
- )
109
- self.assertEqual(config.type, "sse")
110
- self.assertEqual(config.url, "https://api.example.com/mcp")
111
-
112
- # Invalid: type=sse without url
113
- with self.assertRaises(ValidationError) as context:
114
- MCPServerConfigBase(
115
- name="test-server",
116
- type="sse",
117
- command="python"
118
- )
119
- self.assertIn("'url' is required for sse/http transports", str(context.exception))
120
-
121
- @regression_test
122
- def test_base_model_type_field_http_validation(self):
123
- """Test type=http validation."""
124
- # Valid: type=http with url
125
- config = MCPServerConfigBase(
126
- name="test-server",
127
- type="http",
128
- url="https://api.example.com/mcp"
129
- )
130
- self.assertEqual(config.type, "http")
131
- self.assertEqual(config.url, "https://api.example.com/mcp")
132
-
133
- # Invalid: type=http without url
134
- with self.assertRaises(ValidationError) as context:
135
- MCPServerConfigBase(
136
- name="test-server",
137
- type="http",
138
- command="python"
139
- )
140
- self.assertIn("'url' is required for sse/http transports", str(context.exception))
141
-
142
- @regression_test
143
- def test_base_model_type_field_invalid_value(self):
144
- """Test validation fails for invalid type value."""
145
- with self.assertRaises(ValidationError) as context:
146
- MCPServerConfigBase(
147
- name="test-server",
148
- type="invalid",
149
- command="python"
150
- )
151
-
152
- # Pydantic will reject invalid Literal value
153
- self.assertIn("Input should be 'stdio', 'sse' or 'http'", str(context.exception))
154
-
155
-
156
- class TestMCPServerConfigGemini(unittest.TestCase):
157
- """Test suite for MCPServerConfigGemini model."""
158
-
159
- @regression_test
160
- def test_gemini_model_with_all_fields(self):
161
- """Test Gemini model with all Gemini-specific fields."""
162
- config = MCPServerConfigGemini(
163
- name="gemini-server",
164
- command="npx",
165
- args=["-y", "server"],
166
- env={"API_KEY": "test"},
167
- cwd="/path/to/dir",
168
- timeout=30000,
169
- trust=True,
170
- includeTools=["tool1", "tool2"],
171
- excludeTools=["tool3"]
172
- )
173
-
174
- # Verify universal fields
175
- self.assertEqual(config.command, "npx")
176
- self.assertEqual(config.type, "stdio") # Inferred
177
-
178
- # Verify Gemini-specific fields
179
- self.assertEqual(config.cwd, "/path/to/dir")
180
- self.assertEqual(config.timeout, 30000)
181
- self.assertTrue(config.trust)
182
- self.assertEqual(len(config.includeTools), 2)
183
- self.assertEqual(len(config.excludeTools), 1)
184
-
185
- @regression_test
186
- def test_gemini_model_minimal_configuration(self):
187
- """Test Gemini model with minimal configuration."""
188
- config = MCPServerConfigGemini(
189
- name="gemini-server",
190
- command="python"
191
- )
192
-
193
- self.assertEqual(config.command, "python")
194
- self.assertEqual(config.type, "stdio") # Inferred
195
- self.assertIsNone(config.cwd)
196
- self.assertIsNone(config.timeout)
197
- self.assertIsNone(config.trust)
198
-
199
- @regression_test
200
- def test_gemini_model_field_filtering(self):
201
- """Test Gemini model field filtering with model_dump."""
202
- config = MCPServerConfigGemini(
203
- name="gemini-server",
204
- command="python",
205
- cwd="/path/to/dir"
206
- )
207
-
208
- # Use model_dump(exclude_unset=True) to get only set fields
209
- data = config.model_dump(exclude_unset=True)
210
-
211
- # Should include name, command, cwd, type (inferred)
212
- self.assertIn("name", data)
213
- self.assertIn("command", data)
214
- self.assertIn("cwd", data)
215
- self.assertIn("type", data)
216
-
217
- # Should NOT include unset fields
218
- self.assertNotIn("timeout", data)
219
- self.assertNotIn("trust", data)
220
-
221
-
222
- class TestMCPServerConfigVSCode(unittest.TestCase):
223
- """Test suite for MCPServerConfigVSCode model."""
224
-
225
- @regression_test
226
- def test_vscode_model_with_inputs_array(self):
227
- """Test VS Code model with inputs array."""
228
- config = MCPServerConfigVSCode(
229
- name="vscode-server",
230
- command="python",
231
- args=["server.py"],
232
- inputs=[
233
- {
234
- "type": "promptString",
235
- "id": "api-key",
236
- "description": "API Key",
237
- "password": True
238
- }
239
- ]
240
- )
241
-
242
- self.assertEqual(config.command, "python")
243
- self.assertEqual(len(config.inputs), 1)
244
- self.assertEqual(config.inputs[0]["id"], "api-key")
245
- self.assertTrue(config.inputs[0]["password"])
246
-
247
- @regression_test
248
- def test_vscode_model_with_envFile(self):
249
- """Test VS Code model with envFile field."""
250
- config = MCPServerConfigVSCode(
251
- name="vscode-server",
252
- command="python",
253
- envFile=".env"
254
- )
255
-
256
- self.assertEqual(config.command, "python")
257
- self.assertEqual(config.envFile, ".env")
258
-
259
- @regression_test
260
- def test_vscode_model_minimal_configuration(self):
261
- """Test VS Code model with minimal configuration."""
262
- config = MCPServerConfigVSCode(
263
- name="vscode-server",
264
- command="python"
265
- )
266
-
267
- self.assertEqual(config.command, "python")
268
- self.assertEqual(config.type, "stdio") # Inferred
269
- self.assertIsNone(config.envFile)
270
- self.assertIsNone(config.inputs)
271
-
272
-
273
- class TestMCPServerConfigCursor(unittest.TestCase):
274
- """Test suite for MCPServerConfigCursor model."""
275
-
276
- @regression_test
277
- def test_cursor_model_with_envFile(self):
278
- """Test Cursor model with envFile field."""
279
- config = MCPServerConfigCursor(
280
- name="cursor-server",
281
- command="python",
282
- envFile=".env"
283
- )
284
-
285
- self.assertEqual(config.command, "python")
286
- self.assertEqual(config.envFile, ".env")
287
-
288
- @regression_test
289
- def test_cursor_model_minimal_configuration(self):
290
- """Test Cursor model with minimal configuration."""
291
- config = MCPServerConfigCursor(
292
- name="cursor-server",
293
- command="python"
294
- )
295
-
296
- self.assertEqual(config.command, "python")
297
- self.assertEqual(config.type, "stdio") # Inferred
298
- self.assertIsNone(config.envFile)
299
-
300
- @regression_test
301
- def test_cursor_model_env_with_interpolation_syntax(self):
302
- """Test Cursor model with env containing interpolation syntax."""
303
- # Our code writes the literal string value
304
- # Cursor handles ${env:NAME}, ${userHome}, etc. expansion at runtime
305
- config = MCPServerConfigCursor(
306
- name="cursor-server",
307
- command="python",
308
- env={"API_KEY": "${env:API_KEY}", "HOME": "${userHome}"}
309
- )
310
-
311
- self.assertEqual(config.env["API_KEY"], "${env:API_KEY}")
312
- self.assertEqual(config.env["HOME"], "${userHome}")
313
-
314
-
315
- class TestMCPServerConfigClaude(unittest.TestCase):
316
- """Test suite for MCPServerConfigClaude model."""
317
-
318
- @regression_test
319
- def test_claude_model_universal_fields_only(self):
320
- """Test Claude model with universal fields only."""
321
- config = MCPServerConfigClaude(
322
- name="claude-server",
323
- command="python",
324
- args=["server.py"],
325
- env={"API_KEY": "test"}
326
- )
327
-
328
- # Verify universal fields work
329
- self.assertEqual(config.command, "python")
330
- self.assertEqual(config.type, "stdio") # Inferred
331
- self.assertEqual(len(config.args), 1)
332
- self.assertEqual(config.env["API_KEY"], "test")
333
-
334
- @regression_test
335
- def test_claude_model_all_transport_types(self):
336
- """Test Claude model supports all transport types."""
337
- # stdio transport
338
- config_stdio = MCPServerConfigClaude(
339
- name="claude-server",
340
- type="stdio",
341
- command="python"
342
- )
343
- self.assertEqual(config_stdio.type, "stdio")
344
-
345
- # sse transport
346
- config_sse = MCPServerConfigClaude(
347
- name="claude-server",
348
- type="sse",
349
- url="https://api.example.com/mcp"
350
- )
351
- self.assertEqual(config_sse.type, "sse")
352
-
353
- # http transport
354
- config_http = MCPServerConfigClaude(
355
- name="claude-server",
356
- type="http",
357
- url="https://api.example.com/mcp"
358
- )
359
- self.assertEqual(config_http.type, "http")
360
-
361
-
362
- class TestMCPServerConfigOmni(unittest.TestCase):
363
- """Test suite for MCPServerConfigOmni model."""
364
-
365
- @regression_test
366
- def test_omni_model_all_fields_optional(self):
367
- """Test Omni model with no fields (all optional)."""
368
- # Should not raise ValidationError
369
- config = MCPServerConfigOmni()
370
-
371
- self.assertIsNone(config.name)
372
- self.assertIsNone(config.command)
373
- self.assertIsNone(config.url)
374
-
375
- @regression_test
376
- def test_omni_model_with_mixed_host_fields(self):
377
- """Test Omni model with fields from multiple hosts."""
378
- config = MCPServerConfigOmni(
379
- name="omni-server",
380
- command="python",
381
- cwd="/path/to/dir", # Gemini field
382
- envFile=".env" # VS Code/Cursor field
383
- )
384
-
385
- self.assertEqual(config.command, "python")
386
- self.assertEqual(config.cwd, "/path/to/dir")
387
- self.assertEqual(config.envFile, ".env")
388
-
389
- @regression_test
390
- def test_omni_model_exclude_unset(self):
391
- """Test Omni model with exclude_unset."""
392
- config = MCPServerConfigOmni(
393
- name="omni-server",
394
- command="python",
395
- args=["server.py"]
396
- )
397
-
398
- # Use model_dump(exclude_unset=True)
399
- data = config.model_dump(exclude_unset=True)
400
-
401
- # Should only include set fields
402
- self.assertIn("name", data)
403
- self.assertIn("command", data)
404
- self.assertIn("args", data)
405
-
406
- # Should NOT include unset fields
407
- self.assertNotIn("url", data)
408
- self.assertNotIn("cwd", data)
409
- self.assertNotIn("envFile", data)
410
-
411
-
412
- class TestHostModelRegistry(unittest.TestCase):
413
- """Test suite for HOST_MODEL_REGISTRY dictionary dispatch."""
414
-
415
- @regression_test
416
- def test_registry_contains_all_host_types(self):
417
- """Test registry contains entries for all MCPHostType values."""
418
- # Verify registry has entries for all host types
419
- self.assertIn(MCPHostType.GEMINI, HOST_MODEL_REGISTRY)
420
- self.assertIn(MCPHostType.CLAUDE_DESKTOP, HOST_MODEL_REGISTRY)
421
- self.assertIn(MCPHostType.CLAUDE_CODE, HOST_MODEL_REGISTRY)
422
- self.assertIn(MCPHostType.VSCODE, HOST_MODEL_REGISTRY)
423
- self.assertIn(MCPHostType.CURSOR, HOST_MODEL_REGISTRY)
424
- self.assertIn(MCPHostType.LMSTUDIO, HOST_MODEL_REGISTRY)
425
-
426
- # Verify correct model classes
427
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.GEMINI], MCPServerConfigGemini)
428
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.CLAUDE_DESKTOP], MCPServerConfigClaude)
429
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.CLAUDE_CODE], MCPServerConfigClaude)
430
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.VSCODE], MCPServerConfigVSCode)
431
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.CURSOR], MCPServerConfigCursor)
432
- self.assertEqual(HOST_MODEL_REGISTRY[MCPHostType.LMSTUDIO], MCPServerConfigCursor)
433
-
434
- @regression_test
435
- def test_registry_dictionary_dispatch(self):
436
- """Test dictionary dispatch retrieves correct model class."""
437
- # Test Gemini
438
- gemini_class = HOST_MODEL_REGISTRY[MCPHostType.GEMINI]
439
- self.assertEqual(gemini_class, MCPServerConfigGemini)
440
-
441
- # Test VS Code
442
- vscode_class = HOST_MODEL_REGISTRY[MCPHostType.VSCODE]
443
- self.assertEqual(vscode_class, MCPServerConfigVSCode)
444
-
445
- # Test Cursor
446
- cursor_class = HOST_MODEL_REGISTRY[MCPHostType.CURSOR]
447
- self.assertEqual(cursor_class, MCPServerConfigCursor)
448
-
449
- # Test Claude Desktop
450
- claude_class = HOST_MODEL_REGISTRY[MCPHostType.CLAUDE_DESKTOP]
451
- self.assertEqual(claude_class, MCPServerConfigClaude)
452
-
453
-
454
- class TestFromOmniConversion(unittest.TestCase):
455
- """Test suite for from_omni() conversion methods."""
456
-
457
- @regression_test
458
- def test_gemini_from_omni_with_supported_fields(self):
459
- """Test Gemini from_omni with supported fields."""
460
- omni = MCPServerConfigOmni(
461
- name="gemini-server",
462
- command="npx",
463
- args=["-y", "server"],
464
- cwd="/path/to/dir",
465
- timeout=30000
466
- )
467
-
468
- # Convert to Gemini model
469
- gemini = MCPServerConfigGemini.from_omni(omni)
470
-
471
- # Verify all supported fields transferred
472
- self.assertEqual(gemini.name, "gemini-server")
473
- self.assertEqual(gemini.command, "npx")
474
- self.assertEqual(len(gemini.args), 2)
475
- self.assertEqual(gemini.cwd, "/path/to/dir")
476
- self.assertEqual(gemini.timeout, 30000)
477
-
478
- @regression_test
479
- def test_gemini_from_omni_with_unsupported_fields(self):
480
- """Test Gemini from_omni excludes unsupported fields."""
481
- omni = MCPServerConfigOmni(
482
- name="gemini-server",
483
- command="python",
484
- cwd="/path/to/dir", # Gemini field
485
- envFile=".env" # VS Code field (unsupported by Gemini)
486
- )
487
-
488
- # Convert to Gemini model
489
- gemini = MCPServerConfigGemini.from_omni(omni)
490
-
491
- # Verify Gemini fields transferred
492
- self.assertEqual(gemini.command, "python")
493
- self.assertEqual(gemini.cwd, "/path/to/dir")
494
-
495
- # Verify unsupported field NOT transferred
496
- # (Gemini model doesn't have envFile field)
497
- self.assertFalse(hasattr(gemini, 'envFile') and gemini.envFile is not None)
498
-
499
- @regression_test
500
- def test_vscode_from_omni_with_supported_fields(self):
501
- """Test VS Code from_omni with supported fields."""
502
- omni = MCPServerConfigOmni(
503
- name="vscode-server",
504
- command="python",
505
- args=["server.py"],
506
- envFile=".env",
507
- inputs=[{"type": "promptString", "id": "api-key"}]
508
- )
509
-
510
- # Convert to VS Code model
511
- vscode = MCPServerConfigVSCode.from_omni(omni)
512
-
513
- # Verify all supported fields transferred
514
- self.assertEqual(vscode.name, "vscode-server")
515
- self.assertEqual(vscode.command, "python")
516
- self.assertEqual(vscode.envFile, ".env")
517
- self.assertEqual(len(vscode.inputs), 1)
518
-
519
- @regression_test
520
- def test_cursor_from_omni_with_supported_fields(self):
521
- """Test Cursor from_omni with supported fields."""
522
- omni = MCPServerConfigOmni(
523
- name="cursor-server",
524
- command="python",
525
- args=["server.py"],
526
- envFile=".env"
527
- )
528
-
529
- # Convert to Cursor model
530
- cursor = MCPServerConfigCursor.from_omni(omni)
531
-
532
- # Verify all supported fields transferred
533
- self.assertEqual(cursor.name, "cursor-server")
534
- self.assertEqual(cursor.command, "python")
535
- self.assertEqual(cursor.envFile, ".env")
536
-
537
- @regression_test
538
- def test_claude_from_omni_with_universal_fields(self):
539
- """Test Claude from_omni with universal fields only."""
540
- omni = MCPServerConfigOmni(
541
- name="claude-server",
542
- command="python",
543
- args=["server.py"],
544
- env={"API_KEY": "test"},
545
- type="stdio"
546
- )
547
-
548
- # Convert to Claude model
549
- claude = MCPServerConfigClaude.from_omni(omni)
550
-
551
- # Verify universal fields transferred
552
- self.assertEqual(claude.name, "claude-server")
553
- self.assertEqual(claude.command, "python")
554
- self.assertEqual(claude.type, "stdio")
555
- self.assertEqual(len(claude.args), 1)
556
- self.assertEqual(claude.env["API_KEY"], "test")
557
-
558
-
559
- class TestGeminiDualTransport(unittest.TestCase):
560
- """Test suite for Gemini dual-transport validation (Issue 3)."""
561
-
562
- @regression_test
563
- def test_gemini_sse_transport_with_url(self):
564
- """Test Gemini SSE transport uses url field."""
565
- config = MCPServerConfigGemini(
566
- name="gemini-server",
567
- type="sse",
568
- url="https://api.example.com/mcp"
569
- )
570
-
571
- self.assertEqual(config.type, "sse")
572
- self.assertEqual(config.url, "https://api.example.com/mcp")
573
- self.assertIsNone(config.httpUrl)
574
-
575
- @regression_test
576
- def test_gemini_http_transport_with_httpUrl(self):
577
- """Test Gemini HTTP transport uses httpUrl field."""
578
- config = MCPServerConfigGemini(
579
- name="gemini-server",
580
- type="http",
581
- httpUrl="https://api.example.com/mcp"
582
- )
583
-
584
- self.assertEqual(config.type, "http")
585
- self.assertEqual(config.httpUrl, "https://api.example.com/mcp")
586
- self.assertIsNone(config.url)
587
-
588
- @regression_test
589
- def test_gemini_mutual_exclusion_url_and_httpUrl(self):
590
- """Test Gemini rejects both url and httpUrl simultaneously."""
591
- with self.assertRaises(ValidationError) as context:
592
- MCPServerConfigGemini(
593
- name="gemini-server",
594
- url="https://api.example.com/sse",
595
- httpUrl="https://api.example.com/http"
596
- )
597
-
598
- self.assertIn("Cannot specify both 'url' and 'httpUrl'", str(context.exception))
599
-
600
-
601
- if __name__ == '__main__':
602
- unittest.main()
603
-