1+ """Test report_missing functionality."""
2+
3+ import pytest
4+
5+ from mcpcat import MCPCatOptions , track
6+
7+ from .test_utils .client import create_test_client
8+ from .test_utils .todo_server import create_todo_server
9+
10+
11+ class TestReportMissing :
12+ """Test report_missing functionality."""
13+
14+ @pytest .mark .asyncio
15+ async def test_report_missing_tool_injection (self ):
16+ """Test that report_missing tool is properly injected when enabled."""
17+ # Create a new server instance
18+ server = create_todo_server ()
19+
20+ # Track the server with report_missing enabled
21+ options = MCPCatOptions (enable_report_missing = True )
22+ track (server , "test_project" , options )
23+
24+ # Use client to list all tools and verify report_missing is injected
25+ async with create_test_client (server ) as client :
26+ # List all tools on the server
27+ tools_result = await client .list_tools ()
28+
29+ # Get tool names
30+ tool_names = [tool .name for tool in tools_result .tools ]
31+
32+ # Verify original tools are present
33+ assert "add_todo" in tool_names
34+ assert "list_todos" in tool_names
35+ assert "complete_todo" in tool_names
36+
37+ # Verify report_missing tool was injected
38+ assert "report_missing" in tool_names
39+
40+ @pytest .mark .asyncio
41+ async def test_report_missing_disabled_by_default (self ):
42+ """Verify tool is NOT injected when enable_report_missing=False."""
43+ server = create_todo_server ()
44+
45+ # Track with report_missing disabled
46+ options = MCPCatOptions (enable_report_missing = False )
47+ track (server , "test_project" , options )
48+
49+ async with create_test_client (server ) as client :
50+ tools_result = await client .list_tools ()
51+ tool_names = [tool .name for tool in tools_result .tools ]
52+
53+ # Verify report_missing is NOT present
54+ assert "report_missing" not in tool_names
55+ # But original tools should still be there
56+ assert "add_todo" in tool_names
57+
58+ @pytest .mark .asyncio
59+ async def test_report_missing_tool_call_success (self ):
60+ """Call report_missing tool and verify it executes successfully."""
61+ server = create_todo_server ()
62+ options = MCPCatOptions (enable_report_missing = True )
63+ track (server , "test_project" , options )
64+
65+ async with create_test_client (server ) as client :
66+ result = await client .call_tool (
67+ "report_missing" ,
68+ {
69+ "missing_tool" : "translate_text" ,
70+ "description" : "Need a tool to translate text between languages"
71+ }
72+ )
73+
74+ # Verify successful response
75+ assert result .content
76+ assert len (result .content ) == 1
77+ assert result .content [0 ].type == "text"
78+ assert "translate_text" in result .content [0 ].text
79+ assert "Thank you for reporting" in result .content [0 ].text
80+
81+ @pytest .mark .asyncio
82+ async def test_report_missing_with_valid_params (self ):
83+ """Test with both required parameters (missing_tool, description)."""
84+ server = create_todo_server ()
85+ options = MCPCatOptions (enable_report_missing = True )
86+ track (server , "test_project" , options )
87+
88+ async with create_test_client (server ) as client :
89+ # Test with different valid parameters
90+ test_cases = [
91+ {
92+ "missing_tool" : "database_query" ,
93+ "description" : "Execute SQL queries on a database"
94+ },
95+ {
96+ "missing_tool" : "send_email" ,
97+ "description" : "Send emails with attachments"
98+ },
99+ {
100+ "missing_tool" : "generate_chart" ,
101+ "description" : "Create charts from data"
102+ }
103+ ]
104+
105+ for params in test_cases :
106+ result = await client .call_tool ("report_missing" , params )
107+ assert result .content [0 ].text
108+ assert params ["missing_tool" ] in result .content [0 ].text
109+
110+ @pytest .mark .asyncio
111+ async def test_report_missing_with_missing_params (self ):
112+ """Test error handling when required parameters are missing."""
113+ server = create_todo_server ()
114+ options = MCPCatOptions (enable_report_missing = True )
115+ track (server , "test_project" , options )
116+
117+ async with create_test_client (server ) as client :
118+ # Test with missing parameters - should still work but with empty strings
119+ result = await client .call_tool ("report_missing" , {})
120+ assert result .content [0 ].text
121+ assert "Thank you for reporting" in result .content [0 ].text
122+
123+ # Test with only one parameter
124+ result = await client .call_tool (
125+ "report_missing" ,
126+ {"missing_tool" : "test_tool" }
127+ )
128+ assert result .content [0 ].text
129+ assert "test_tool" in result .content [0 ].text
130+
131+ @pytest .mark .asyncio
132+ async def test_report_missing_with_extra_params (self ):
133+ """Test that extra parameters are ignored/handled properly."""
134+ server = create_todo_server ()
135+ options = MCPCatOptions (enable_report_missing = True )
136+ track (server , "test_project" , options )
137+
138+ async with create_test_client (server ) as client :
139+ result = await client .call_tool (
140+ "report_missing" ,
141+ {
142+ "missing_tool" : "api_caller" ,
143+ "description" : "Call external APIs" ,
144+ "extra_param" : "should be ignored" ,
145+ "another_extra" : 12345
146+ }
147+ )
148+
149+ # Should still work normally
150+ assert result .content [0 ].text
151+ assert "api_caller" in result .content [0 ].text
152+
153+ @pytest .mark .asyncio
154+ async def test_report_missing_with_other_tools (self ):
155+ """Verify report_missing doesn't interfere with existing server tools."""
156+ server = create_todo_server ()
157+ options = MCPCatOptions (enable_report_missing = True )
158+ track (server , "test_project" , options )
159+
160+ async with create_test_client (server ) as client :
161+ # First use a regular tool
162+ add_result = await client .call_tool (
163+ "add_todo" ,
164+ {"text" : "Test todo item" }
165+ )
166+ assert "Added todo" in add_result .content [0 ].text
167+
168+ # Then use report_missing
169+ report_result = await client .call_tool (
170+ "report_missing" ,
171+ {
172+ "missing_tool" : "delete_todo" ,
173+ "description" : "Delete a todo item"
174+ }
175+ )
176+ assert "delete_todo" in report_result .content [0 ].text
177+
178+ # Verify the original tool still works
179+ list_result = await client .call_tool ("list_todos" )
180+ assert "Test todo item" in list_result .content [0 ].text
181+
182+ @pytest .mark .asyncio
183+ async def test_multiple_report_missing_calls (self ):
184+ """Test calling report_missing multiple times in succession."""
185+ server = create_todo_server ()
186+ options = MCPCatOptions (enable_report_missing = True )
187+ track (server , "test_project" , options )
188+
189+ async with create_test_client (server ) as client :
190+ # Call report_missing multiple times
191+ tools_to_report = [
192+ ("tool1" , "Description 1" ),
193+ ("tool2" , "Description 2" ),
194+ ("tool3" , "Description 3" )
195+ ]
196+
197+ for tool_name , description in tools_to_report :
198+ result = await client .call_tool (
199+ "report_missing" ,
200+ {
201+ "missing_tool" : tool_name ,
202+ "description" : description
203+ }
204+ )
205+ # Each call should work identically
206+ assert result .content [0 ].text
207+ assert tool_name in result .content [0 ].text
208+ assert "Thank you for reporting" in result .content [0 ].text
209+
210+ @pytest .mark .asyncio
211+ async def test_report_missing_with_context_enabled (self ):
212+ """Test interaction when both report_missing and tool_context are enabled."""
213+ server = create_todo_server ()
214+ options = MCPCatOptions (
215+ enable_report_missing = True ,
216+ enable_tool_call_context = True
217+ )
218+ track (server , "test_project" , options )
219+
220+ async with create_test_client (server ) as client :
221+ tools_result = await client .list_tools ()
222+
223+ # Find the report_missing tool
224+ report_missing_tool = None
225+ other_tool = None
226+ for tool in tools_result .tools :
227+ if tool .name == "report_missing" :
228+ report_missing_tool = tool
229+ elif tool .name == "add_todo" :
230+ other_tool = tool
231+
232+ assert report_missing_tool is not None
233+ assert other_tool is not None
234+
235+ # Verify context is NOT added to report_missing tool
236+ assert "context" not in report_missing_tool .inputSchema .get ("properties" , {})
237+
238+ # But context should be added to other tools
239+ assert "context" in other_tool .inputSchema .get ("properties" , {})
240+
241+ @pytest .mark .skip (reason = "Creating empty low-level server is complex and already tested via FastMCP" )
242+ @pytest .mark .asyncio
243+ async def test_report_missing_on_server_without_tools (self ):
244+ """Test on a server that has no tools initially."""
245+ # This is a complex edge case that would require creating a low-level
246+ # server with proper handler setup. The functionality is already tested
247+ # through other tests using FastMCP servers.
248+ pass
249+
250+ @pytest .mark .asyncio
251+ async def test_report_missing_with_null_values (self ):
252+ """Test with null/None values for parameters."""
253+ server = create_todo_server ()
254+ options = MCPCatOptions (enable_report_missing = True )
255+ track (server , "test_project" , options )
256+
257+ async with create_test_client (server ) as client :
258+ # Test with None values - they should be treated as empty strings
259+ result = await client .call_tool (
260+ "report_missing" ,
261+ {
262+ "missing_tool" : None ,
263+ "description" : None
264+ }
265+ )
266+ # Should still return a valid response
267+ assert result .content [0 ].text
268+ assert "Thank you for reporting" in result .content [0 ].text
269+
270+ @pytest .mark .asyncio
271+ async def test_report_missing_with_invalid_types (self ):
272+ """Test with wrong parameter types (e.g., number instead of string)."""
273+ server = create_todo_server ()
274+ options = MCPCatOptions (enable_report_missing = True )
275+ track (server , "test_project" , options )
276+
277+ async with create_test_client (server ) as client :
278+ # Test with numbers instead of strings
279+ result = await client .call_tool (
280+ "report_missing" ,
281+ {
282+ "missing_tool" : 12345 ,
283+ "description" : 67890
284+ }
285+ )
286+ # Should convert to string and work
287+ assert result .content [0 ].text
288+ assert "12345" in result .content [0 ].text
0 commit comments