@@ -2215,6 +2215,123 @@ async def test_invoke_tool_rest_parameter_substitution(self, tool_service, mock_
22152215 headers = mock_tool .headers ,
22162216 )
22172217
2218+ @pytest .mark .asyncio
2219+ async def test_invoke_tool_rest_post_with_path_query_and_body_params (self , tool_service , mock_tool , mock_global_config_obj , test_db ):
2220+ """Test POST request with path parameters, query parameters (with templates), and body parameters.
2221+
2222+ This test demonstrates the complete parameter handling:
2223+ - Path parameters (e.g., {user_id}) are substituted into the URL path
2224+ - Query parameters can also use templates (e.g., ?api_key={api_key})
2225+ - Static query parameters (e.g., ?version=v2) are preserved as-is
2226+ - Query parameters are merged into the JSON body for POST requests
2227+ - Remaining payload goes to the JSON body alongside merged query params
2228+ """
2229+ mock_tool .integration_type = "REST"
2230+ mock_tool .request_type = "POST"
2231+ mock_tool .jsonpath_filter = ""
2232+ mock_tool .auth_value = None
2233+ # URL with path parameters AND templated query parameters
2234+ mock_tool .url = "http://example.com/api/users/{user_id}/posts?api_key={api_key}&version=v2"
2235+
2236+ # Payload contains: path param (user_id), query param (api_key), and body params (title, content)
2237+ payload = {
2238+ "user_id" : 456 , # Will be substituted into URL path
2239+ "api_key" : "secret123" , # Template in query string portion of URL; substituted then extracted as query param
2240+ "title" : "New Post" , # Will go to JSON body
2241+ "content" : "Hello World" , # Will go to JSON body
2242+ }
2243+
2244+ setup_db_execute_mock (test_db , mock_tool , mock_global_config_obj )
2245+
2246+ mock_response = Mock ()
2247+ mock_response .raise_for_status = Mock ()
2248+ mock_response .status_code = 200
2249+ mock_response .json = Mock (return_value = {"id" : 789 , "status" : "created" })
2250+
2251+ tool_service ._http_client .request = AsyncMock (return_value = mock_response )
2252+
2253+ await tool_service .invoke_tool (test_db , "test_tool" , payload , request_headers = None )
2254+
2255+ # Verify parameter handling for POST:
2256+ # 1. Path parameter substituted: /users/456/posts
2257+ # 2. Query param template substituted: api_key=secret123
2258+ # 3. Static query param preserved: version=v2
2259+ # 4. Query params merged into JSON body (backward-compatible behavior for POST)
2260+ # 5. Body params: title and content (user_id and api_key removed after path/query substitution)
2261+ tool_service ._http_client .request .assert_called_once_with (
2262+ "POST" ,
2263+ "http://example.com/api/users/456/posts" , # Path param substituted, query string stripped
2264+ json = {"title" : "New Post" , "content" : "Hello World" , "api_key" : "secret123" , "version" : "v2" }, # Body + merged query params
2265+ headers = mock_tool .headers ,
2266+ )
2267+
2268+ @pytest .mark .asyncio
2269+ async def test_invoke_tool_rest_get_with_static_query_params_no_mapping (self , tool_service , mock_tool , mock_global_config_obj , test_db ):
2270+ """Test GET request with static URL query params and no query_mapping.
2271+
2272+ Verifies that query params extracted from the URL are merged into the
2273+ payload and sent together via params= on the GET request.
2274+ """
2275+ mock_tool .integration_type = "REST"
2276+ mock_tool .request_type = "GET"
2277+ mock_tool .jsonpath_filter = ""
2278+ mock_tool .auth_value = None
2279+ mock_tool .url = "http://example.com/api/search?version=v2&format=json"
2280+
2281+ payload = {"q" : "hello" }
2282+
2283+ setup_db_execute_mock (test_db , mock_tool , mock_global_config_obj )
2284+
2285+ mock_response = Mock ()
2286+ mock_response .raise_for_status = Mock ()
2287+ mock_response .status_code = 200
2288+ mock_response .json = Mock (return_value = {"results" : []})
2289+
2290+ tool_service ._http_client .get = AsyncMock (return_value = mock_response )
2291+
2292+ await tool_service .invoke_tool (test_db , "test_tool" , payload , request_headers = None )
2293+
2294+ # URL query params (version, format) are merged into payload alongside the user-provided "q"
2295+ tool_service ._http_client .get .assert_called_once_with (
2296+ "http://example.com/api/search" ,
2297+ params = {"q" : "hello" , "version" : "v2" , "format" : "json" },
2298+ headers = mock_tool .headers ,
2299+ )
2300+
2301+ @pytest .mark .asyncio
2302+ async def test_invoke_tool_rest_put_with_query_params (self , tool_service , mock_tool , mock_global_config_obj , test_db ):
2303+ """Test PUT request with URL query params merges them into the JSON body.
2304+
2305+ Verifies that non-GET methods other than POST (e.g. PUT) also merge
2306+ URL query params into the JSON body for backward compatibility.
2307+ """
2308+ mock_tool .integration_type = "REST"
2309+ mock_tool .request_type = "PUT"
2310+ mock_tool .jsonpath_filter = ""
2311+ mock_tool .auth_value = None
2312+ mock_tool .url = "http://example.com/api/items/1?version=v2"
2313+
2314+ payload = {"name" : "updated" }
2315+
2316+ setup_db_execute_mock (test_db , mock_tool , mock_global_config_obj )
2317+
2318+ mock_response = Mock ()
2319+ mock_response .raise_for_status = Mock ()
2320+ mock_response .status_code = 200
2321+ mock_response .json = Mock (return_value = {"status" : "ok" })
2322+
2323+ tool_service ._http_client .request = AsyncMock (return_value = mock_response )
2324+
2325+ await tool_service .invoke_tool (test_db , "test_tool" , payload , request_headers = None )
2326+
2327+ # Query params merged into JSON body, same as POST
2328+ tool_service ._http_client .request .assert_called_once_with (
2329+ "PUT" ,
2330+ "http://example.com/api/items/1" ,
2331+ json = {"name" : "updated" , "version" : "v2" },
2332+ headers = mock_tool .headers ,
2333+ )
2334+
22182335 @pytest .mark .asyncio
22192336 async def test_invoke_tool_rest_jq_filter_error_returns_error (self , tool_service , mock_tool , mock_global_config_obj , test_db ):
22202337 """Test REST tool invocation marks result as error when jq filter returns TextContent error."""
0 commit comments