Skip to content

Commit 12949c8

Browse files
committed
refactor agw integration tests
1 parent 3933edb commit 12949c8

3 files changed

Lines changed: 120 additions & 4 deletions

File tree

.env_integration_tests.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CLOUD_SDK_CFG_ADMS_DEFAULT_RESOURCE=urn:sap:identity:application:provider:name:y
1111
CLOUD_SDK_CFG_AGW_DEFAULT_LANDSCAPE=your-landscape-here
1212
CLOUD_SDK_CFG_AGW_DEFAULT_TENANT_SUBDOMAIN=your-tenant-subdomain-here
1313
CLOUD_SDK_CFG_AGW_DEFAULT_USER_TOKEN=your-user-jwt-here
14+
CLOUD_SDK_CFG_AGW_DEFAULT_SAMPLE_MCP_TOOL=your-sample-mcp-tool-name-here
1415

1516
# AGENT MEMORY
1617
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL=https://your-agent-memory-api-url-here

tests/agentgateway/integration/agw_auth.feature

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,26 @@ Feature: Agent Gateway Auth Integration
4141
And the error message should mention "user_token is required"
4242

4343
Scenario: List MCP tools returns a non-empty list of tools
44+
Given I have a valid user token
4445
When I call list_mcp_tools
4546
Then the result should be a list of MCPTool
4647
And the list should be non-empty
4748
And each tool should have a non-empty name
4849
And each tool should have a non-empty url
50+
And each tool should have a valid input_schema
4951

50-
Scenario: Call search_workflows tool returns a non-empty result
52+
Scenario: Call sample MCP tool returns a non-empty result
5153
Given I have a valid user token
54+
And I have a sample MCP tool name
5255
When I call list_mcp_tools
53-
And I call call_mcp_tool with "search_workflows" and the user token
56+
And I call call_mcp_tool with the sample MCP tool and the user token
5457
Then the tool result should be a non-empty string
58+
59+
Scenario: Convert sample MCP tool to LangChain StructuredTool
60+
Given I have a valid user token
61+
And I have a sample MCP tool name
62+
When I call list_mcp_tools
63+
And I convert the sample MCP tool to a LangChain StructuredTool
64+
Then the LangChain tool should have the sample MCP tool name
65+
And the LangChain tool should have a non-empty description
66+
And the LangChain tool should require no arguments

tests/agentgateway/integration/test_agw_bdd.py

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
CLOUD_SDK_CFG_AGW_DEFAULT_TENANT_SUBDOMAIN=<tenant-subdomain> \\
66
CLOUD_SDK_CFG_AGW_DEFAULT_LANDSCAPE=<landscape> \\
77
CLOUD_SDK_CFG_AGW_DEFAULT_USER_TOKEN=<user-jwt> \\
8+
CLOUD_SDK_CFG_AGW_DEFAULT_SAMPLE_MCP_TOOL=<tool-name> \\
89
CLOUD_SDK_CFG_DESTINATION_DEFAULT_CLIENTID=... \\
910
CLOUD_SDK_CFG_DESTINATION_DEFAULT_CLIENTSECRET=... \\
1011
CLOUD_SDK_CFG_DESTINATION_DEFAULT_URL=... \\
@@ -15,13 +16,14 @@
1516

1617
import asyncio
1718
import os
18-
from typing import Optional
19+
from typing import Optional, Any
1920

2021
import pytest
2122
from pytest_bdd import scenarios, given, when, then, parsers
2223

2324
from sap_cloud_sdk.agentgateway import AgentGatewayClient, AuthResult, AgentGatewaySDKError
2425
from sap_cloud_sdk.agentgateway._models import MCPTool
26+
from sap_cloud_sdk.agentgateway.converters import mcp_tool_to_langchain
2527

2628
scenarios("agw_auth.feature")
2729

@@ -52,6 +54,8 @@ def __init__(self):
5254
self.user_token: Optional[str] = None
5355
self.tools: Optional[list[MCPTool]] = None
5456
self.tool_result: Optional[str] = None
57+
self.langchain_tool: Optional[Any] = None
58+
self.sample_mcp_tool_name: Optional[str] = None
5559

5660

5761
@pytest.fixture
@@ -78,6 +82,15 @@ def have_valid_user_token(context: ScenarioContext):
7882
context.user_token = token
7983

8084

85+
@given("I have a sample MCP tool name")
86+
def have_sample_mcp_tool_name(context: ScenarioContext):
87+
"""Load sample MCP tool name from environment variable."""
88+
tool_name = os.environ.get("CLOUD_SDK_CFG_AGW_DEFAULT_SAMPLE_MCP_TOOL", "")
89+
if not tool_name:
90+
pytest.skip("CLOUD_SDK_CFG_AGW_DEFAULT_SAMPLE_MCP_TOOL is not set — skipping tool scenario")
91+
context.sample_mcp_tool_name = tool_name
92+
93+
8194
# ==================== WHEN ====================
8295

8396

@@ -119,7 +132,7 @@ def call_get_user_auth_empty_token(context: ScenarioContext, agw_client: AgentGa
119132
@when("I call list_mcp_tools")
120133
def call_list_mcp_tools(context: ScenarioContext, agw_client: AgentGatewayClient):
121134
"""Call list_mcp_tools and store the result."""
122-
context.tools = run(agw_client.list_mcp_tools())
135+
context.tools = run(agw_client.list_mcp_tools(user_token=context.user_token))
123136

124137

125138
@when(parsers.parse('I call call_mcp_tool with "{tool_name}" and the user token'))
@@ -136,6 +149,34 @@ def call_call_mcp_tool(
136149
)
137150

138151

152+
@when("I call call_mcp_tool with the sample MCP tool and the user token")
153+
def call_call_mcp_tool_sample(context: ScenarioContext, agw_client: AgentGatewayClient):
154+
"""Find the sample MCP tool and call it."""
155+
assert context.tools is not None, "call list_mcp_tools before calling a tool"
156+
assert context.sample_mcp_tool_name is not None
157+
tool = next((t for t in context.tools if t.name == context.sample_mcp_tool_name), None)
158+
if tool is None:
159+
pytest.fail(f"Tool '{context.sample_mcp_tool_name}' not found in list_mcp_tools result")
160+
context.tool_result = run(
161+
agw_client.call_mcp_tool(tool, user_token=context.user_token)
162+
)
163+
164+
165+
@when("I convert the sample MCP tool to a LangChain StructuredTool")
166+
def convert_sample_tool_to_langchain(context: ScenarioContext, agw_client: AgentGatewayClient):
167+
"""Convert the sample MCP tool to a LangChain StructuredTool."""
168+
assert context.tools is not None, "call list_mcp_tools before converting a tool"
169+
assert context.sample_mcp_tool_name is not None
170+
tool = next((t for t in context.tools if t.name == context.sample_mcp_tool_name), None)
171+
if tool is None:
172+
pytest.fail(f"Tool '{context.sample_mcp_tool_name}' not found in list_mcp_tools result")
173+
context.langchain_tool = mcp_tool_to_langchain(
174+
tool,
175+
agw_client.call_mcp_tool,
176+
get_user_token=lambda: context.user_token,
177+
)
178+
179+
139180
# ==================== THEN ====================
140181

141182

@@ -237,9 +278,71 @@ def each_tool_has_non_empty_url(context: ScenarioContext):
237278
)
238279

239280

281+
@then("each tool should have a valid input_schema")
282+
def each_tool_has_valid_input_schema(context: ScenarioContext):
283+
"""Verify every tool has an input_schema dict with type=object."""
284+
assert context.tools is not None
285+
for tool in context.tools:
286+
assert isinstance(tool.input_schema, dict), (
287+
f"Tool '{tool.name}' input_schema is not a dict: {tool.input_schema!r}"
288+
)
289+
assert tool.input_schema.get("type") == "object", (
290+
f"Tool '{tool.name}' input_schema missing type=object: {tool.input_schema}"
291+
)
292+
293+
240294
@then("the tool result should be a non-empty string")
241295
def tool_result_is_non_empty_string(context: ScenarioContext):
242296
"""Verify the tool invocation returned a non-empty string."""
243297
assert context.tool_result is not None
244298
assert isinstance(context.tool_result, str)
245299
assert context.tool_result.strip(), "Expected a non-empty tool result"
300+
301+
302+
@when(parsers.parse('I convert the "{tool_name}" tool to a LangChain StructuredTool'))
303+
def convert_tool_to_langchain(
304+
context: ScenarioContext, agw_client: AgentGatewayClient, tool_name: str
305+
):
306+
"""Convert the named MCPTool to a LangChain StructuredTool."""
307+
assert context.tools is not None, "call list_mcp_tools before converting a tool"
308+
tool = next((t for t in context.tools if t.name == tool_name), None)
309+
if tool is None:
310+
pytest.fail(f"Tool '{tool_name}' not found in list_mcp_tools result")
311+
context.langchain_tool = mcp_tool_to_langchain(
312+
tool,
313+
agw_client.call_mcp_tool,
314+
get_user_token=lambda: context.user_token,
315+
)
316+
317+
318+
@then(parsers.parse('the LangChain tool should have name "{expected_name}"'))
319+
def langchain_tool_has_name(context: ScenarioContext, expected_name: str):
320+
"""Verify the converted LangChain tool has the expected name."""
321+
assert context.langchain_tool is not None
322+
assert context.langchain_tool.name == expected_name
323+
324+
325+
@then("the LangChain tool should have the sample MCP tool name")
326+
def langchain_tool_has_sample_name(context: ScenarioContext):
327+
"""Verify the converted LangChain tool name matches the sample MCP tool name."""
328+
assert context.langchain_tool is not None
329+
assert context.langchain_tool.name == context.sample_mcp_tool_name
330+
331+
332+
@then("the LangChain tool should have a non-empty description")
333+
def langchain_tool_has_description(context: ScenarioContext):
334+
"""Verify the converted LangChain tool has a non-empty description."""
335+
assert context.langchain_tool is not None
336+
assert isinstance(context.langchain_tool.description, str)
337+
assert context.langchain_tool.description.strip()
338+
339+
340+
@then("the LangChain tool should require no arguments")
341+
def langchain_tool_requires_no_arguments(context: ScenarioContext):
342+
"""Verify the converted LangChain tool has no required fields in its args schema."""
343+
assert context.langchain_tool is not None
344+
schema = context.langchain_tool.args_schema
345+
if schema is not None:
346+
fields = schema.model_fields
347+
required = [name for name, f in fields.items() if f.is_required()]
348+
assert required == [], f"Expected no required args, got: {required}"

0 commit comments

Comments
 (0)