Skip to content

Commit 5d4a5ea

Browse files
committed
test: add comprehensive test suite for MCP fetch_tools functionality
Add 12 new tests covering MCP-backed tool fetching to match Node SDK test coverage in stackone.mcp-fetch.spec.ts. Tests are organized into logical groups for account and provider/action filtering scenarios. Test Coverage: Account Filtering (TestAccountFiltering): - fetch_tools_with_single_account_id: Single account filtering - fetch_tools_with_multiple_account_ids: Multiple account filtering - fetch_tools_with_set_accounts: Using set_accounts() method - fetch_tools_account_ids_override_set_accounts: Parameter precedence - fetch_tools_with_constructor_account_id: Constructor-based account - fetch_tools_with_empty_account_ids: Empty list handling Provider/Action Filtering (TestProviderAndActionFiltering): - fetch_tools_with_provider_filter: Single provider filtering - fetch_tools_with_multiple_providers: Multiple provider filtering - fetch_tools_with_action_glob_pattern: Glob pattern matching - fetch_tools_with_exact_action_match: Exact action name matching - fetch_tools_with_provider_and_action_filters: Combined filtering - fetch_tools_with_exclusion_pattern: Negative glob patterns Removed: - Deleted old fetch_tools tests that used non-MCP approach - These tests were testing the old implementation that loaded from OpenAPI specs instead of the MCP server The test suite uses comprehensive mocking of MCP client components: - ClientSession for MCP protocol communication - streamablehttp_client for HTTP transport - Tool list responses with pagination support - Proper async context manager handling All tests verify: - Correct tool count after filtering - Proper tool presence/absence - Account ID preservation in tool context - Filter precedence and interaction
1 parent 62e838d commit 5d4a5ea

File tree

2 files changed

+272
-205
lines changed

2 files changed

+272
-205
lines changed

tests/test_toolset.py

Lines changed: 0 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from unittest.mock import MagicMock, patch
22

3-
import pytest
4-
53
from stackone_ai.models import ExecuteConfig, ToolDefinition, ToolParameters
64
from stackone_ai.toolset import StackOneToolSet
75

@@ -273,206 +271,3 @@ def test_filter_by_action():
273271
# Test non-matching patterns
274272
assert not toolset._filter_by_action("crm_list_contacts", ["*_list_employees"])
275273
assert not toolset._filter_by_action("ats_create_job", ["hris_*"])
276-
277-
278-
@pytest.fixture
279-
def mock_tools_setup():
280-
"""Setup mocked tools for filtering tests"""
281-
# Create mock tool definitions
282-
tools_defs = {
283-
"hris_list_employees": ToolDefinition(
284-
description="List employees",
285-
parameters=ToolParameters(type="object", properties={}),
286-
execute=ExecuteConfig(
287-
method="GET",
288-
url="https://api.stackone.com/hris/employees",
289-
name="hris_list_employees",
290-
headers={},
291-
),
292-
),
293-
"hris_create_employee": ToolDefinition(
294-
description="Create employee",
295-
parameters=ToolParameters(type="object", properties={}),
296-
execute=ExecuteConfig(
297-
method="POST",
298-
url="https://api.stackone.com/hris/employees",
299-
name="hris_create_employee",
300-
headers={},
301-
),
302-
),
303-
"ats_list_employees": ToolDefinition(
304-
description="List ATS employees",
305-
parameters=ToolParameters(type="object", properties={}),
306-
execute=ExecuteConfig(
307-
method="GET",
308-
url="https://api.stackone.com/ats/employees",
309-
name="ats_list_employees",
310-
headers={},
311-
),
312-
),
313-
"crm_list_contacts": ToolDefinition(
314-
description="List contacts",
315-
parameters=ToolParameters(type="object", properties={}),
316-
execute=ExecuteConfig(
317-
method="GET",
318-
url="https://api.stackone.com/crm/contacts",
319-
name="crm_list_contacts",
320-
headers={},
321-
),
322-
),
323-
}
324-
325-
with (
326-
patch("stackone_ai.toolset.OAS_DIR") as mock_dir,
327-
patch("stackone_ai.toolset.OpenAPIParser") as mock_parser_class,
328-
):
329-
mock_path = MagicMock()
330-
mock_path.exists.return_value = True
331-
mock_dir.glob.return_value = [mock_path]
332-
333-
mock_parser = MagicMock()
334-
mock_parser.parse_tools.return_value = tools_defs
335-
mock_parser_class.return_value = mock_parser
336-
337-
yield
338-
339-
340-
def test_fetch_tools_no_filters(mock_tools_setup):
341-
"""Test fetch_tools without any filters"""
342-
toolset = StackOneToolSet(api_key="test_key")
343-
tools = toolset.fetch_tools()
344-
345-
# Should include all tools (4 regular + 1 feedback tool)
346-
assert len(tools) == 5
347-
348-
349-
def test_fetch_tools_provider_filter(mock_tools_setup):
350-
"""Test fetch_tools with provider filtering"""
351-
toolset = StackOneToolSet(api_key="test_key")
352-
353-
# Filter by single provider
354-
tools = toolset.fetch_tools(providers=["hris"])
355-
assert len(tools) == 2
356-
assert tools.get_tool("hris_list_employees") is not None
357-
assert tools.get_tool("hris_create_employee") is not None
358-
359-
# Filter by multiple providers
360-
tools = toolset.fetch_tools(providers=["hris", "ats"])
361-
assert len(tools) == 3
362-
assert tools.get_tool("hris_list_employees") is not None
363-
assert tools.get_tool("ats_list_employees") is not None
364-
365-
366-
def test_fetch_tools_action_filter(mock_tools_setup):
367-
"""Test fetch_tools with action filtering"""
368-
toolset = StackOneToolSet(api_key="test_key")
369-
370-
# Exact action match
371-
tools = toolset.fetch_tools(actions=["hris_list_employees"])
372-
assert len(tools) == 1
373-
assert tools.get_tool("hris_list_employees") is not None
374-
375-
# Glob pattern match
376-
tools = toolset.fetch_tools(actions=["*_list_employees"])
377-
assert len(tools) == 2
378-
assert tools.get_tool("hris_list_employees") is not None
379-
assert tools.get_tool("ats_list_employees") is not None
380-
381-
382-
def test_fetch_tools_combined_filters(mock_tools_setup):
383-
"""Test fetch_tools with combined filters"""
384-
toolset = StackOneToolSet(api_key="test_key")
385-
386-
# Combine provider and action filters
387-
tools = toolset.fetch_tools(providers=["hris"], actions=["*_list_*"])
388-
assert len(tools) == 1
389-
assert tools.get_tool("hris_list_employees") is not None
390-
assert tools.get_tool("hris_create_employee") is None
391-
392-
393-
def test_fetch_tools_with_set_accounts(mock_tools_setup):
394-
"""Test fetch_tools using set_accounts"""
395-
toolset = StackOneToolSet(api_key="test_key")
396-
toolset.set_accounts(["acc1"])
397-
398-
tools = toolset.fetch_tools(providers=["hris"])
399-
assert len(tools) == 2
400-
401-
402-
def test_fetch_tools_account_id_override(mock_tools_setup) -> None:
403-
"""Test that fetch_tools account_ids parameter overrides set_accounts"""
404-
toolset = StackOneToolSet(api_key="test_key")
405-
406-
# Set accounts via set_accounts
407-
toolset.set_accounts(["acc1", "acc2"])
408-
409-
# Override with different account IDs in fetch_tools
410-
# This should use acc3, not acc1/acc2
411-
tools = toolset.fetch_tools(account_ids=["acc3"], providers=["hris"])
412-
413-
# Should fetch tools for acc3 only
414-
# With 2 HRIS tools per account
415-
assert len(tools) == 2
416-
417-
# Verify that set_accounts state is not modified
418-
assert toolset._account_ids == ["acc1", "acc2"]
419-
420-
421-
def test_fetch_tools_uses_set_accounts_when_no_override(mock_tools_setup) -> None:
422-
"""Test that fetch_tools uses set_accounts when account_ids not provided"""
423-
toolset = StackOneToolSet(api_key="test_key")
424-
toolset.set_accounts(["acc1", "acc2"])
425-
426-
# Should use accounts from set_accounts
427-
tools = toolset.fetch_tools(providers=["hris"])
428-
429-
# Should fetch tools for both accounts
430-
# 2 HRIS tools × 2 accounts = 4 tools
431-
assert len(tools) == 4
432-
433-
434-
def test_fetch_tools_multiple_account_ids(mock_tools_setup) -> None:
435-
"""Test fetching tools for multiple account IDs"""
436-
toolset = StackOneToolSet(api_key="test_key")
437-
438-
# Fetch tools for multiple accounts
439-
tools = toolset.fetch_tools(account_ids=["acc1", "acc2", "acc3"])
440-
441-
# Should fetch all tools for all 3 accounts
442-
# (4 regular tools + 1 feedback tool) × 3 accounts = 15 tools
443-
assert len(tools) == 15
444-
445-
446-
def test_fetch_tools_preserves_account_context() -> None:
447-
"""Test that tools fetched with account_id maintain their account context"""
448-
with (
449-
patch("stackone_ai.toolset.OAS_DIR") as mock_dir,
450-
patch("stackone_ai.toolset.OpenAPIParser") as mock_parser_class,
451-
):
452-
# Create a simple tool definition
453-
tool_def = ToolDefinition(
454-
description="Test tool",
455-
parameters=ToolParameters(type="object", properties={}),
456-
execute=ExecuteConfig(
457-
method="GET",
458-
url="https://api.stackone.com/test",
459-
name="test_tool",
460-
headers={},
461-
),
462-
)
463-
464-
mock_path = MagicMock()
465-
mock_path.exists.return_value = True
466-
mock_dir.glob.return_value = [mock_path]
467-
468-
mock_parser = MagicMock()
469-
mock_parser.parse_tools.return_value = {"test_tool": tool_def}
470-
mock_parser_class.return_value = mock_parser
471-
472-
toolset = StackOneToolSet(api_key="test_key")
473-
tools = toolset.fetch_tools(account_ids=["specific-account"])
474-
475-
# Get a tool and verify it has the account ID
476-
tool = tools.get_tool("test_tool")
477-
assert tool is not None
478-
assert tool.get_account_id() == "specific-account"

0 commit comments

Comments
 (0)