44dispatch logic and error handling without network calls.
55"""
66import pytest
7- import sys
8- import os
9-
10- sys .path .insert (0 , os .path .dirname (os .path .dirname (os .path .abspath (__file__ ))))
11-
127from unittest .mock import AsyncMock , patch
138import httpx
149import mcp .types as types
1510
16- from handlers import call_tool , _safe_error_message
11+ from handlers import call_tool , _safe_error_message , _clamp_max_results
1712
1813
1914# -- Dispatch --
@@ -68,11 +63,35 @@ async def test_dna_calls_correct_endpoint(self, mock_get):
6863 assert "/repos/r1/dna" in call_path
6964
7065 @pytest .mark .asyncio
71- async def test_none_arguments_handled (self ):
66+ @patch ("handlers.api_get" , new_callable = AsyncMock )
67+ async def test_none_arguments_handled (self , mock_get ):
7268 """call_tool(name, None) should not crash."""
69+ mock_get .return_value = {"repositories" : []}
7370 result = await call_tool ("list_repositories" , None )
74- # Will fail on network, but should not crash on None args
7571 assert len (result ) == 1
72+ assert "No repositories indexed" in result [0 ].text
73+
74+
75+ # -- Input validation --
76+
77+ class TestClampMaxResults :
78+ def test_default_on_none (self ):
79+ assert _clamp_max_results (None ) == 10
80+
81+ def test_default_on_string (self ):
82+ assert _clamp_max_results ("abc" ) == 10
83+
84+ def test_clamps_zero_to_one (self ):
85+ assert _clamp_max_results (0 ) == 1
86+
87+ def test_clamps_negative (self ):
88+ assert _clamp_max_results (- 5 ) == 1
89+
90+ def test_clamps_over_max (self ):
91+ assert _clamp_max_results (500 ) == 100
92+
93+ def test_valid_value_passes (self ):
94+ assert _clamp_max_results (25 ) == 25
7695
7796
7897# -- Error handling --
@@ -99,11 +118,14 @@ def test_connect_error(self):
99118 msg = _safe_error_message ("search_code" , {}, error )
100119 assert "Cannot connect" in msg
101120
102- def test_value_error_passthrough (self ):
103- """ValueError messages are user-facing (e.g. missing API key) ."""
121+ def test_value_error_sanitized (self ):
122+ """ValueError should not leak internal details ."""
104123 error = ValueError ("No API_KEY configured" )
105- msg = _safe_error_message ("search_code" , {}, error )
106- assert "No API_KEY configured" in msg
124+ msg = _safe_error_message ("search_code" , {"repo_id" : "r1" }, error )
125+ assert "Tool input error" in msg
126+ assert "search_code" in msg
127+ # Internal message should NOT be in output
128+ assert "No API_KEY" not in msg
107129
108130 def test_generic_error_hides_details (self ):
109131 error = RuntimeError ("internal traceback info" )
0 commit comments