"""Tests for elicitation tools."""
import sys
import pytest
from fastmcp.client import Client
from fastmcp.client.elicitation import ElicitResult
sys.path.insert(0, "src")
from mcpbin.app import mcp
# Store test responses for the mock handler
_mock_responses: dict = {}
async def mock_elicitation_handler(message: str, response_type: type, params, context):
"""Mock elicitation handler that returns predefined responses."""
# Check if we have a predefined response
if "decline" in _mock_responses:
return ElicitResult(action="decline")
if "cancel" in _mock_responses:
return ElicitResult(action="cancel")
# Return a response based on the response type
value = _mock_responses.get("value")
if value is not None:
return response_type(value=value)
# Default: accept with default values
return response_type(value="default")
@pytest.fixture
async def elicitation_client():
"""Client with elicitation handler for testing elicitation tools."""
_mock_responses.clear()
async with Client(transport=mcp, elicitation_handler=mock_elicitation_handler) as c:
yield c
@pytest.fixture
def set_mock_response():
"""Fixture to set mock response for elicitation handler."""
def _set(value=None, action=None):
_mock_responses.clear()
if action == "decline":
_mock_responses["decline"] = True
elif action == "cancel":
_mock_responses["cancel"] = True
elif value is not None:
_mock_responses["value"] = value
return _set
async def test_elicit_string_accept(elicitation_client: Client, set_mock_response):
"""Test elicit_string with accepted response."""
set_mock_response(value="Alice")
result = await elicitation_client.call_tool("elicit_string", {"prompt": "What is your name?"})
assert result.data["action"] == "accept"
assert result.data["data"] == "Alice"
async def test_elicit_string_decline(elicitation_client: Client, set_mock_response):
"""Test elicit_string with declined response."""
set_mock_response(action="decline")
result = await elicitation_client.call_tool("elicit_string", {"prompt": "What is your name?"})
assert result.data["action"] == "decline"
assert result.data["data"] is None
async def test_elicit_string_cancel(elicitation_client: Client, set_mock_response):
"""Test elicit_string with cancelled response."""
set_mock_response(action="cancel")
result = await elicitation_client.call_tool("elicit_string", {"prompt": "What is your name?"})
assert result.data["action"] == "cancel"
assert result.data["data"] is None
async def test_elicit_number_accept(elicitation_client: Client, set_mock_response):
"""Test elicit_number with accepted response."""
set_mock_response(value=42)
result = await elicitation_client.call_tool("elicit_number", {"prompt": "Pick a number"})
assert result.data["action"] == "accept"
assert result.data["data"] == 42
async def test_elicit_number_decline(elicitation_client: Client, set_mock_response):
"""Test elicit_number with declined response."""
set_mock_response(action="decline")
result = await elicitation_client.call_tool("elicit_number", {"prompt": "Pick a number"})
assert result.data["action"] == "decline"
assert result.data["data"] is None
async def test_elicit_boolean_true(elicitation_client: Client, set_mock_response):
"""Test elicit_boolean with true response."""
set_mock_response(value=True)
result = await elicitation_client.call_tool("elicit_boolean", {"prompt": "Do you agree?"})
assert result.data["action"] == "accept"
assert result.data["data"] is True
async def test_elicit_boolean_false(elicitation_client: Client, set_mock_response):
"""Test elicit_boolean with false response."""
set_mock_response(value=False)
result = await elicitation_client.call_tool("elicit_boolean", {"prompt": "Do you agree?"})
assert result.data["action"] == "accept"
assert result.data["data"] is False
async def test_elicit_choice_accept(elicitation_client: Client, set_mock_response):
"""Test elicit_choice with accepted response."""
set_mock_response(value="high")
result = await elicitation_client.call_tool(
"elicit_choice",
{"prompt": "Select priority", "choices": ["low", "medium", "high"]},
)
assert result.data["action"] == "accept"
assert result.data["data"] == "high"
async def test_elicit_choice_decline(elicitation_client: Client, set_mock_response):
"""Test elicit_choice with declined response."""
set_mock_response(action="decline")
result = await elicitation_client.call_tool(
"elicit_choice",
{"prompt": "Select priority", "choices": ["low", "medium", "high"]},
)
assert result.data["action"] == "decline"
assert result.data["data"] is None
async def test_elicit_structured_accept(elicitation_client: Client, set_mock_response):
"""Test elicit_structured with accepted response."""
# For structured types, the mock returns response_type(value=...)
# but PersonInfo needs name and age, so we need special handling
# This test may need adjustment based on actual FastMCP behavior
pass # TODO: implement after verifying FastMCP structured elicitation behavior
async def test_elicit_structured_cancel(elicitation_client: Client, set_mock_response):
"""Test elicit_structured with cancelled response."""
set_mock_response(action="cancel")
result = await elicitation_client.call_tool("elicit_structured", {"prompt": "Enter your info"})
assert result.data["action"] == "cancel"
assert result.data["data"] is None