Skip to content

Commit d8076e2

Browse files
author
LittleCoinCoin
committed
test: add tests for user feedback reporting
Add test suite for MCP user feedback reporting system: Test Classes (14 regression tests total): - TestFieldOperation (4 tests) * Test __str__() for UPDATED, UNSUPPORTED, UNCHANGED operations * Test ASCII arrow usage (not Unicode) * Test None old_value handling - TestConversionReport (3 tests) * Test create, update, migrate operations * Test model field validation - TestGenerateConversionReport (4 tests) * Test create with all supported fields * Test create with unsupported fields * Test update with changed/unchanged fields * Test dynamic field derivation from HOST_MODEL_REGISTRY - TestDisplayReport (3 tests) * Test console output for create/update operations * Test dry-run mode with preview messaging Test results: 14/14 tests pass (100% pass rate) Total MCP tests: 221/221 pass (100% pass rate)
1 parent fa8fa42 commit d8076e2

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
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+

0 commit comments

Comments
 (0)