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
@@ -0,0 +1,211 @@
1
+ """Regression tests for TableFormatter class.
2
+
3
+ Tests focus on behavioral contracts for table rendering:
4
+ - Column alignment (left, right, center)
5
+ - Auto-width calculation
6
+ - Header and separator rendering
7
+ - Row data handling
8
+
9
+ Reference: R02 §5 (02-list_output_format_specification_v2.md)
10
+ Reference: R06 §3.6 (06-dependency_analysis_v0.md)
11
+ """
12
+
13
+ import pytest
14
+
15
+
16
+ class TestColumnDef:
17
+ """Tests for ColumnDef dataclass."""
18
+
19
+ def test_column_def_has_required_fields(self):
20
+ """ColumnDef must have name, width, and align fields."""
21
+ from hatch.cli.cli_utils import ColumnDef
22
+
23
+ col = ColumnDef(name="Test", width=10)
24
+ assert col.name == "Test"
25
+ assert col.width == 10
26
+ assert col.align == "left" # Default alignment
27
+
28
+ def test_column_def_accepts_auto_width(self):
29
+ """ColumnDef width can be 'auto' for auto-calculation."""
30
+ from hatch.cli.cli_utils import ColumnDef
31
+
32
+ col = ColumnDef(name="Test", width="auto")
33
+ assert col.width == "auto"
34
+
35
+ def test_column_def_accepts_alignment_options(self):
36
+ """ColumnDef supports left, right, and center alignment."""
37
+ from hatch.cli.cli_utils import ColumnDef
38
+
39
+ left = ColumnDef(name="Left", width=10, align="left")
40
+ right = ColumnDef(name="Right", width=10, align="right")
41
+ center = ColumnDef(name="Center", width=10, align="center")
42
+
43
+ assert left.align == "left"
44
+ assert right.align == "right"
45
+ assert center.align == "center"
46
+
47
+
48
+ class TestTableFormatter:
49
+ """Tests for TableFormatter class."""
50
+
51
+ def test_table_formatter_accepts_column_definitions(self):
52
+ """TableFormatter initializes with column definitions."""
53
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
54
+
55
+ columns = [
56
+ ColumnDef(name="Name", width=20),
57
+ ColumnDef(name="Value", width=10),
58
+ ]
59
+ formatter = TableFormatter(columns)
60
+ assert formatter is not None
61
+
62
+ def test_add_row_stores_data(self):
63
+ """add_row stores row data for rendering."""
64
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
65
+
66
+ columns = [ColumnDef(name="Col1", width=10)]
67
+ formatter = TableFormatter(columns)
68
+ formatter.add_row(["value1"])
69
+ formatter.add_row(["value2"])
70
+
71
+ # Verify rows are stored (implementation detail, but necessary for render)
72
+ assert len(formatter._rows) == 2
73
+
74
+ def test_render_produces_string_output(self):
75
+ """render() returns a string with table content."""
76
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
77
+
78
+ columns = [ColumnDef(name="Name", width=10)]
79
+ formatter = TableFormatter(columns)
80
+ formatter.add_row(["Test"])
81
+
82
+ output = formatter.render()
83
+ assert isinstance(output, str)
84
+ assert len(output) > 0
85
+
86
+ def test_render_includes_header_row(self):
87
+ """Rendered output includes column headers."""
88
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
89
+
90
+ columns = [
91
+ ColumnDef(name="Name", width=15),
92
+ ColumnDef(name="Status", width=10),
93
+ ]
94
+ formatter = TableFormatter(columns)
95
+ formatter.add_row(["test-item", "active"])
96
+
97
+ output = formatter.render()
98
+ assert "Name" in output
99
+ assert "Status" in output
100
+
101
+ def test_render_includes_separator_line(self):
102
+ """Rendered output includes separator line after headers."""
103
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
104
+
105
+ columns = [ColumnDef(name="Name", width=10)]
106
+ formatter = TableFormatter(columns)
107
+ formatter.add_row(["Test"])
108
+
109
+ output = formatter.render()
110
+ # Separator uses box-drawing character or dashes
111
+ assert "─" in output or "-" in output
112
+
113
+ def test_render_includes_data_rows(self):
114
+ """Rendered output includes all added data rows."""
115
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
116
+
117
+ columns = [ColumnDef(name="Item", width=15)]
118
+ formatter = TableFormatter(columns)
119
+ formatter.add_row(["first-item"])
120
+ formatter.add_row(["second-item"])
121
+ formatter.add_row(["third-item"])
122
+
123
+ output = formatter.render()
124
+ assert "first-item" in output
125
+ assert "second-item" in output
126
+ assert "third-item" in output
127
+
128
+ def test_left_alignment_pads_right(self):
129
+ """Left-aligned columns pad values on the right."""
130
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
131
+
132
+ columns = [ColumnDef(name="Name", width=10, align="left")]
133
+ formatter = TableFormatter(columns)
134
+ formatter.add_row(["abc"])
135
+
136
+ output = formatter.render()
137
+ lines = output.strip().split("\n")
138
+ # Find data row (skip header and separator)
139
+ data_line = lines[-1]
140
+ # Left-aligned: value followed by spaces
141
+ assert "abc" in data_line
142
+
143
+ def test_right_alignment_pads_left(self):
144
+ """Right-aligned columns pad values on the left."""
145
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
146
+
147
+ columns = [ColumnDef(name="Count", width=10, align="right")]
148
+ formatter = TableFormatter(columns)
149
+ formatter.add_row(["42"])
150
+
151
+ output = formatter.render()
152
+ lines = output.strip().split("\n")
153
+ data_line = lines[-1]
154
+ # Right-aligned: spaces followed by value
155
+ assert "42" in data_line
156
+
157
+ def test_auto_width_calculates_from_content(self):
158
+ """Auto width calculates based on header and data content."""
159
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
160
+
161
+ columns = [ColumnDef(name="Name", width="auto")]
162
+ formatter = TableFormatter(columns)
163
+ formatter.add_row(["short"])
164
+ formatter.add_row(["much-longer-value"])
165
+
166
+ output = formatter.render()
167
+ # Output should accommodate the longest value
168
+ assert "much-longer-value" in output
169
+
170
+ def test_empty_table_renders_headers_only(self):
171
+ """Table with no rows renders headers and separator."""
172
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
173
+
174
+ columns = [ColumnDef(name="Empty", width=10)]
175
+ formatter = TableFormatter(columns)
176
+
177
+ output = formatter.render()
178
+ assert "Empty" in output
179
+ # Should have header and separator, but no data rows
180
+
181
+ def test_multiple_columns_separated(self):
182
+ """Multiple columns are visually separated."""
183
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
184
+
185
+ columns = [
186
+ ColumnDef(name="Col1", width=10),
187
+ ColumnDef(name="Col2", width=10),
188
+ ColumnDef(name="Col3", width=10),
189
+ ]
190
+ formatter = TableFormatter(columns)
191
+ formatter.add_row(["a", "b", "c"])
192
+
193
+ output = formatter.render()
194
+ assert "Col1" in output
195
+ assert "Col2" in output
196
+ assert "Col3" in output
197
+ assert "a" in output
198
+ assert "b" in output
199
+ assert "c" in output
200
+
201
+ def test_truncation_with_ellipsis(self):
202
+ """Values exceeding column width are truncated with ellipsis."""
203
+ from hatch.cli.cli_utils import TableFormatter, ColumnDef
204
+
205
+ columns = [ColumnDef(name="Name", width=8)]
206
+ formatter = TableFormatter(columns)
207
+ formatter.add_row(["very-long-value-that-exceeds-width"])
208
+
209
+ output = formatter.render()
210
+ # Should truncate and add ellipsis
211
+ assert "…" in output or "..." in output
File without changes
@@ -0,0 +1,162 @@
1
+ """Regression tests for field filtering (name/type exclusion).
2
+
3
+ Test IDs: RF-01 to RF-07 (per 02-test_architecture_rebuild_v0.md)
4
+ Scope: Prevent `name` and `type` field leakage in serialized output.
5
+ """
6
+
7
+ import unittest
8
+
9
+ from hatch.mcp_host_config.models import MCPServerConfig
10
+ from hatch.mcp_host_config.adapters import (
11
+ ClaudeAdapter,
12
+ CodexAdapter,
13
+ CursorAdapter,
14
+ GeminiAdapter,
15
+ KiroAdapter,
16
+ VSCodeAdapter,
17
+ )
18
+
19
+
20
+ class TestFieldFiltering(unittest.TestCase):
21
+ """Regression tests for field filtering (RF-01 to RF-07).
22
+
23
+ These tests ensure:
24
+ - `name` is NEVER in serialized output (it's Hatch metadata, not host config)
25
+ - `type` behavior varies by host (some include, some exclude)
26
+ """
27
+
28
+ def setUp(self):
29
+ """Create test configs for use across tests."""
30
+ # Config WITH type (for hosts that support type field)
31
+ self.stdio_config_with_type = MCPServerConfig(
32
+ name="test-server",
33
+ command="python",
34
+ args=["server.py"],
35
+ type="stdio",
36
+ )
37
+
38
+ # Config WITHOUT type (for hosts that don't support type field)
39
+ self.stdio_config_no_type = MCPServerConfig(
40
+ name="test-server",
41
+ command="python",
42
+ args=["server.py"],
43
+ )
44
+
45
+ self.sse_config_with_type = MCPServerConfig(
46
+ name="sse-server",
47
+ url="https://example.com/mcp",
48
+ type="sse",
49
+ )
50
+
51
+ self.sse_config_no_type = MCPServerConfig(
52
+ name="sse-server",
53
+ url="https://example.com/mcp",
54
+ )
55
+
56
+ def test_RF01_name_never_in_gemini_output(self):
57
+ """RF-01: `name` never appears in Gemini serialized output."""
58
+ adapter = GeminiAdapter()
59
+ result = adapter.serialize(self.stdio_config_no_type)
60
+
61
+ self.assertNotIn("name", result)
62
+
63
+ def test_RF02_name_never_in_claude_output(self):
64
+ """RF-02: `name` never appears in Claude serialized output."""
65
+ adapter = ClaudeAdapter()
66
+ result = adapter.serialize(self.stdio_config_with_type)
67
+
68
+ self.assertNotIn("name", result)
69
+
70
+ def test_RF03_type_not_in_gemini_output(self):
71
+ """RF-03: `type` should NOT be in Gemini output.
72
+
73
+ Gemini's config format infers type from the presence of
74
+ command/url/httpUrl fields.
75
+ """
76
+ adapter = GeminiAdapter()
77
+ result = adapter.serialize(self.stdio_config_no_type)
78
+
79
+ self.assertNotIn("type", result)
80
+
81
+ def test_RF04_type_not_in_kiro_output(self):
82
+ """RF-04: `type` should NOT be in Kiro output.
83
+
84
+ Kiro's config format infers type from the presence of
85
+ command/url fields.
86
+ """
87
+ adapter = KiroAdapter()
88
+ result = adapter.serialize(self.stdio_config_no_type)
89
+
90
+ self.assertNotIn("type", result)
91
+
92
+ def test_RF05_type_not_in_codex_output(self):
93
+ """RF-05: `type` should NOT be in Codex output.
94
+
95
+ Codex TOML format doesn't use type field - it uses section headers.
96
+ """
97
+ adapter = CodexAdapter()
98
+ result = adapter.serialize(self.stdio_config_no_type)
99
+
100
+ self.assertNotIn("type", result)
101
+
102
+ def test_RF06_type_IS_in_claude_output(self):
103
+ """RF-06: `type` SHOULD be in Claude output.
104
+
105
+ Claude Desktop/Code explicitly uses the type field for transport.
106
+ """
107
+ adapter = ClaudeAdapter()
108
+ result = adapter.serialize(self.stdio_config_with_type)
109
+
110
+ self.assertIn("type", result)
111
+ self.assertEqual(result["type"], "stdio")
112
+
113
+ def test_RF07_type_IS_in_vscode_output(self):
114
+ """RF-07: `type` SHOULD be in VS Code output.
115
+
116
+ VS Code explicitly uses the type field for transport.
117
+ """
118
+ adapter = VSCodeAdapter()
119
+ result = adapter.serialize(self.stdio_config_with_type)
120
+
121
+ self.assertIn("type", result)
122
+ self.assertEqual(result["type"], "stdio")
123
+
124
+ def test_name_never_in_any_adapter_output(self):
125
+ """Comprehensive test: `name` never appears in ANY adapter output.
126
+
127
+ Uses appropriate config for each adapter (with/without type field).
128
+ """
129
+ type_supporting_adapters = [
130
+ ClaudeAdapter(),
131
+ CursorAdapter(),
132
+ VSCodeAdapter(),
133
+ ]
134
+
135
+ type_rejecting_adapters = [
136
+ CodexAdapter(),
137
+ GeminiAdapter(),
138
+ KiroAdapter(),
139
+ ]
140
+
141
+ for adapter in type_supporting_adapters:
142
+ with self.subTest(adapter=adapter.host_name):
143
+ result = adapter.serialize(self.stdio_config_with_type)
144
+ self.assertNotIn("name", result)
145
+
146
+ for adapter in type_rejecting_adapters:
147
+ with self.subTest(adapter=adapter.host_name):
148
+ result = adapter.serialize(self.stdio_config_no_type)
149
+ self.assertNotIn("name", result)
150
+
151
+ def test_cursor_type_behavior(self):
152
+ """Test Cursor type field behavior (same as VS Code)."""
153
+ adapter = CursorAdapter()
154
+ result = adapter.serialize(self.stdio_config_with_type)
155
+
156
+ # Cursor should include type like VS Code
157
+ self.assertIn("type", result)
158
+
159
+
160
+ if __name__ == "__main__":
161
+ unittest.main()
162
+
tests/test_cli_version.py CHANGED
@@ -20,7 +20,8 @@ from io import StringIO
20
20
  # Add parent directory to path
21
21
  sys.path.insert(0, str(Path(__file__).parent.parent))
22
22
 
23
- from hatch.cli_hatch import main, get_hatch_version
23
+ from hatch.cli_hatch import main
24
+ from hatch.cli.cli_utils import get_hatch_version
24
25
 
25
26
  try:
26
27
  from wobble.decorators import regression_test, integration_test
@@ -41,7 +42,7 @@ class TestVersionCommand(unittest.TestCase):
41
42
  @regression_test
42
43
  def test_get_hatch_version_retrieves_from_metadata(self):
43
44
  """Test get_hatch_version() retrieves version from importlib.metadata."""
44
- with patch('hatch.cli_hatch.version', return_value='0.7.0-dev.3') as mock_version:
45
+ with patch('hatch.cli.cli_utils.version', return_value='0.7.0-dev.3') as mock_version:
45
46
  result = get_hatch_version()
46
47
 
47
48
  self.assertEqual(result, '0.7.0-dev.3')
@@ -52,7 +53,7 @@ class TestVersionCommand(unittest.TestCase):
52
53
  """Test get_hatch_version() handles PackageNotFoundError gracefully."""
53
54
  from importlib.metadata import PackageNotFoundError
54
55
 
55
- with patch('hatch.cli_hatch.version', side_effect=PackageNotFoundError()):
56
+ with patch('hatch.cli.cli_utils.version', side_effect=PackageNotFoundError()):
56
57
  result = get_hatch_version()
57
58
 
58
59
  self.assertEqual(result, 'unknown (development mode)')
@@ -63,7 +64,8 @@ class TestVersionCommand(unittest.TestCase):
63
64
  test_args = ['hatch', '--version']
64
65
 
65
66
  with patch('sys.argv', test_args):
66
- with patch('hatch.cli_hatch.get_hatch_version', return_value='0.7.0-dev.3'):
67
+ # Patch at point of use in __main__ (imported from cli_utils)
68
+ with patch('hatch.cli.__main__.get_hatch_version', return_value='0.7.0-dev.3'):
67
69
  with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
68
70
  with self.assertRaises(SystemExit) as cm:
69
71
  main()
@@ -98,7 +100,7 @@ class TestVersionCommand(unittest.TestCase):
98
100
  test_args = ['hatch', 'package', 'add', 'test-package', '-v', '1.0.0']
99
101
 
100
102
  with patch('sys.argv', test_args):
101
- with patch('hatch.cli_hatch.HatchEnvironmentManager') as mock_env:
103
+ with patch('hatch.environment_manager.HatchEnvironmentManager') as mock_env:
102
104
  mock_env_instance = MagicMock()
103
105
  mock_env.return_value = mock_env_instance
104
106
  mock_env_instance.add_package_to_environment.return_value = True
@@ -0,0 +1,184 @@
1
+ """Test fixtures for ResultReporter and ConversionReport integration tests.
2
+
3
+ This module provides reusable ConversionReport and FieldOperation samples
4
+ for testing the CLI reporter infrastructure. Fixtures are defined as Python
5
+ objects for type safety and IDE support.
6
+
7
+ Reference: R05 §4.2 (05-test_definition_v0.md)
8
+
9
+ Fixture Categories:
10
+ - Single field operation samples (one per operation type)
11
+ - ConversionReport samples (various scenarios)
12
+
13
+ Usage:
14
+ from tests.test_data.fixtures.cli_reporter_fixtures import (
15
+ REPORT_MIXED_OPERATIONS,
16
+ FIELD_OP_UPDATED,
17
+ )
18
+
19
+ def test_all_fields_mapped_no_data_loss(self):
20
+ reporter = ResultReporter("test")
21
+ reporter.add_from_conversion_report(REPORT_MIXED_OPERATIONS)
22
+ assert len(reporter.consequences[0].children) == 4
23
+ """
24
+
25
+ from hatch.mcp_host_config.reporting import ConversionReport, FieldOperation
26
+ from hatch.mcp_host_config.models import MCPHostType
27
+
28
+
29
+ # =============================================================================
30
+ # Single Field Operation Samples (one per operation type)
31
+ # =============================================================================
32
+
33
+ FIELD_OP_UPDATED = FieldOperation(
34
+ field_name="command",
35
+ operation="UPDATED",
36
+ old_value=None,
37
+ new_value="python"
38
+ )
39
+ """Field operation: UPDATED - field value changed from None to 'python'."""
40
+
41
+ FIELD_OP_UPDATED_WITH_OLD = FieldOperation(
42
+ field_name="command",
43
+ operation="UPDATED",
44
+ old_value="node",
45
+ new_value="python"
46
+ )
47
+ """Field operation: UPDATED - field value changed from 'node' to 'python'."""
48
+
49
+ FIELD_OP_UNSUPPORTED = FieldOperation(
50
+ field_name="timeout",
51
+ operation="UNSUPPORTED",
52
+ new_value=30
53
+ )
54
+ """Field operation: UNSUPPORTED - field not supported by target host."""
55
+
56
+ FIELD_OP_UNCHANGED = FieldOperation(
57
+ field_name="env",
58
+ operation="UNCHANGED",
59
+ new_value={}
60
+ )
61
+ """Field operation: UNCHANGED - field value remained the same."""
62
+
63
+
64
+ # =============================================================================
65
+ # ConversionReport Samples
66
+ # =============================================================================
67
+
68
+ REPORT_SINGLE_UPDATE = ConversionReport(
69
+ operation="create",
70
+ server_name="test-server",
71
+ target_host=MCPHostType.CLAUDE_DESKTOP,
72
+ field_operations=[FIELD_OP_UPDATED]
73
+ )
74
+ """ConversionReport: Single field update (create operation)."""
75
+
76
+ REPORT_MIXED_OPERATIONS = ConversionReport(
77
+ operation="update",
78
+ server_name="weather-server",
79
+ target_host=MCPHostType.CURSOR,
80
+ field_operations=[
81
+ FieldOperation(
82
+ field_name="command",
83
+ operation="UPDATED",
84
+ old_value="node",
85
+ new_value="python"
86
+ ),
87
+ FieldOperation(
88
+ field_name="args",
89
+ operation="UPDATED",
90
+ old_value=[],
91
+ new_value=["server.py"]
92
+ ),
93
+ FieldOperation(
94
+ field_name="env",
95
+ operation="UNCHANGED",
96
+ new_value={"API_KEY": "***"}
97
+ ),
98
+ FieldOperation(
99
+ field_name="timeout",
100
+ operation="UNSUPPORTED",
101
+ new_value=60
102
+ ),
103
+ ]
104
+ )
105
+ """ConversionReport: Mixed field operations (update operation).
106
+
107
+ Contains:
108
+ - 2 UPDATED fields (command, args)
109
+ - 1 UNCHANGED field (env)
110
+ - 1 UNSUPPORTED field (timeout)
111
+ """
112
+
113
+ REPORT_EMPTY_FIELDS = ConversionReport(
114
+ operation="create",
115
+ server_name="minimal-server",
116
+ target_host=MCPHostType.VSCODE,
117
+ field_operations=[]
118
+ )
119
+ """ConversionReport: Empty field operations list (edge case)."""
120
+
121
+ REPORT_ALL_UNSUPPORTED = ConversionReport(
122
+ operation="migrate",
123
+ server_name="legacy-server",
124
+ source_host=MCPHostType.CLAUDE_DESKTOP,
125
+ target_host=MCPHostType.KIRO,
126
+ field_operations=[
127
+ FieldOperation(
128
+ field_name="trust",
129
+ operation="UNSUPPORTED",
130
+ new_value=True
131
+ ),
132
+ FieldOperation(
133
+ field_name="cwd",
134
+ operation="UNSUPPORTED",
135
+ new_value="/app"
136
+ ),
137
+ ]
138
+ )
139
+ """ConversionReport: All fields unsupported (migrate operation)."""
140
+
141
+ REPORT_ALL_UNCHANGED = ConversionReport(
142
+ operation="update",
143
+ server_name="stable-server",
144
+ target_host=MCPHostType.CLAUDE_DESKTOP,
145
+ field_operations=[
146
+ FieldOperation(
147
+ field_name="command",
148
+ operation="UNCHANGED",
149
+ new_value="python"
150
+ ),
151
+ FieldOperation(
152
+ field_name="args",
153
+ operation="UNCHANGED",
154
+ new_value=["server.py"]
155
+ ),
156
+ ]
157
+ )
158
+ """ConversionReport: All fields unchanged (no-op update)."""
159
+
160
+ REPORT_DRY_RUN = ConversionReport(
161
+ operation="create",
162
+ server_name="preview-server",
163
+ target_host=MCPHostType.CURSOR,
164
+ field_operations=[
165
+ FieldOperation(
166
+ field_name="command",
167
+ operation="UPDATED",
168
+ old_value=None,
169
+ new_value="python"
170
+ ),
171
+ ],
172
+ dry_run=True
173
+ )
174
+ """ConversionReport: Dry-run mode enabled."""
175
+
176
+ REPORT_WITH_ERROR = ConversionReport(
177
+ operation="create",
178
+ server_name="failed-server",
179
+ target_host=MCPHostType.VSCODE,
180
+ success=False,
181
+ error_message="Configuration file not found",
182
+ field_operations=[]
183
+ )
184
+ """ConversionReport: Failed operation with error message."""
tests/unit/__init__.py ADDED
File without changes
File without changes