test(tools): add unit tests for clarify_tool.py
Add comprehensive test coverage for the clarify_tool module: - TestClarifyToolBasics: 5 tests for core functionality - Simple questions, questions with choices, error handling - TestClarifyToolChoicesValidation: 5 tests for choices parameter - MAX_CHOICES enforcement, empty/whitespace handling, type conversion - TestClarifyToolCallbackHandling: 3 tests for callback behavior - Exception handling, question/response trimming - TestCheckClarifyRequirements: 1 test verifying always-true behavior - TestClarifySchema: 6 tests verifying OpenAI function schema - Required/optional parameters, maxItems constraint Total: 20 tests covering all public functions and edge cases.
This commit is contained in:
195
tests/tools/test_clarify_tool.py
Normal file
195
tests/tools/test_clarify_tool.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Tests for tools/clarify_tool.py - Interactive clarifying questions."""
|
||||
|
||||
import json
|
||||
from typing import List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from tools.clarify_tool import (
|
||||
clarify_tool,
|
||||
check_clarify_requirements,
|
||||
MAX_CHOICES,
|
||||
CLARIFY_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
class TestClarifyToolBasics:
|
||||
"""Basic functionality tests for clarify_tool."""
|
||||
|
||||
def test_simple_question_with_callback(self):
|
||||
"""Should return user response for simple question."""
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
assert question == "What color?"
|
||||
assert choices is None
|
||||
return "blue"
|
||||
|
||||
result = json.loads(clarify_tool("What color?", callback=mock_callback))
|
||||
assert result["question"] == "What color?"
|
||||
assert result["choices_offered"] is None
|
||||
assert result["user_response"] == "blue"
|
||||
|
||||
def test_question_with_choices(self):
|
||||
"""Should pass choices to callback and return response."""
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
assert question == "Pick a number"
|
||||
assert choices == ["1", "2", "3"]
|
||||
return "2"
|
||||
|
||||
result = json.loads(clarify_tool(
|
||||
"Pick a number",
|
||||
choices=["1", "2", "3"],
|
||||
callback=mock_callback
|
||||
))
|
||||
assert result["question"] == "Pick a number"
|
||||
assert result["choices_offered"] == ["1", "2", "3"]
|
||||
assert result["user_response"] == "2"
|
||||
|
||||
def test_empty_question_returns_error(self):
|
||||
"""Should return error for empty question."""
|
||||
result = json.loads(clarify_tool("", callback=lambda q, c: "ignored"))
|
||||
assert "error" in result
|
||||
assert "required" in result["error"].lower()
|
||||
|
||||
def test_whitespace_only_question_returns_error(self):
|
||||
"""Should return error for whitespace-only question."""
|
||||
result = json.loads(clarify_tool(" \n\t ", callback=lambda q, c: "ignored"))
|
||||
assert "error" in result
|
||||
|
||||
def test_no_callback_returns_error(self):
|
||||
"""Should return error when no callback is provided."""
|
||||
result = json.loads(clarify_tool("What do you want?"))
|
||||
assert "error" in result
|
||||
assert "not available" in result["error"].lower()
|
||||
|
||||
|
||||
class TestClarifyToolChoicesValidation:
|
||||
"""Tests for choices parameter validation."""
|
||||
|
||||
def test_choices_trimmed_to_max(self):
|
||||
"""Should trim choices to MAX_CHOICES."""
|
||||
choices_passed = []
|
||||
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
choices_passed.extend(choices or [])
|
||||
return "picked"
|
||||
|
||||
many_choices = ["a", "b", "c", "d", "e", "f", "g"]
|
||||
clarify_tool("Pick one", choices=many_choices, callback=mock_callback)
|
||||
|
||||
assert len(choices_passed) == MAX_CHOICES
|
||||
|
||||
def test_empty_choices_become_none(self):
|
||||
"""Empty choices list should become None (open-ended)."""
|
||||
choices_received = ["marker"]
|
||||
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
choices_received.clear()
|
||||
if choices is not None:
|
||||
choices_received.extend(choices)
|
||||
return "answer"
|
||||
|
||||
clarify_tool("Open question?", choices=[], callback=mock_callback)
|
||||
assert choices_received == [] # Was cleared, nothing added
|
||||
|
||||
def test_choices_with_only_whitespace_stripped(self):
|
||||
"""Whitespace-only choices should be stripped out."""
|
||||
choices_received = []
|
||||
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
choices_received.extend(choices or [])
|
||||
return "answer"
|
||||
|
||||
clarify_tool("Pick", choices=["valid", " ", "", "also valid"], callback=mock_callback)
|
||||
assert choices_received == ["valid", "also valid"]
|
||||
|
||||
def test_invalid_choices_type_returns_error(self):
|
||||
"""Non-list choices should return error."""
|
||||
result = json.loads(clarify_tool(
|
||||
"Question?",
|
||||
choices="not a list", # type: ignore
|
||||
callback=lambda q, c: "ignored"
|
||||
))
|
||||
assert "error" in result
|
||||
assert "list" in result["error"].lower()
|
||||
|
||||
def test_choices_converted_to_strings(self):
|
||||
"""Non-string choices should be converted to strings."""
|
||||
choices_received = []
|
||||
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
choices_received.extend(choices or [])
|
||||
return "answer"
|
||||
|
||||
clarify_tool("Pick", choices=[1, 2, 3], callback=mock_callback) # type: ignore
|
||||
assert choices_received == ["1", "2", "3"]
|
||||
|
||||
|
||||
class TestClarifyToolCallbackHandling:
|
||||
"""Tests for callback error handling."""
|
||||
|
||||
def test_callback_exception_returns_error(self):
|
||||
"""Should return error if callback raises exception."""
|
||||
def failing_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
raise RuntimeError("User cancelled")
|
||||
|
||||
result = json.loads(clarify_tool("Question?", callback=failing_callback))
|
||||
assert "error" in result
|
||||
assert "Failed to get user input" in result["error"]
|
||||
assert "User cancelled" in result["error"]
|
||||
|
||||
def test_callback_receives_stripped_question(self):
|
||||
"""Callback should receive trimmed question."""
|
||||
received_question = []
|
||||
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
received_question.append(question)
|
||||
return "answer"
|
||||
|
||||
clarify_tool(" Question with spaces \n", callback=mock_callback)
|
||||
assert received_question[0] == "Question with spaces"
|
||||
|
||||
def test_user_response_stripped(self):
|
||||
"""User response should be stripped of whitespace."""
|
||||
def mock_callback(question: str, choices: Optional[List[str]]) -> str:
|
||||
return " response with spaces \n"
|
||||
|
||||
result = json.loads(clarify_tool("Q?", callback=mock_callback))
|
||||
assert result["user_response"] == "response with spaces"
|
||||
|
||||
|
||||
class TestCheckClarifyRequirements:
|
||||
"""Tests for the requirements check function."""
|
||||
|
||||
def test_always_returns_true(self):
|
||||
"""clarify tool has no external requirements."""
|
||||
assert check_clarify_requirements() is True
|
||||
|
||||
|
||||
class TestClarifySchema:
|
||||
"""Tests for the OpenAI function-calling schema."""
|
||||
|
||||
def test_schema_name(self):
|
||||
"""Schema should have correct name."""
|
||||
assert CLARIFY_SCHEMA["name"] == "clarify"
|
||||
|
||||
def test_schema_has_description(self):
|
||||
"""Schema should have a description."""
|
||||
assert "description" in CLARIFY_SCHEMA
|
||||
assert len(CLARIFY_SCHEMA["description"]) > 50
|
||||
|
||||
def test_schema_question_required(self):
|
||||
"""Question parameter should be required."""
|
||||
assert "question" in CLARIFY_SCHEMA["parameters"]["required"]
|
||||
|
||||
def test_schema_choices_optional(self):
|
||||
"""Choices parameter should be optional."""
|
||||
assert "choices" not in CLARIFY_SCHEMA["parameters"]["required"]
|
||||
|
||||
def test_schema_choices_max_items(self):
|
||||
"""Schema should specify max items for choices."""
|
||||
choices_spec = CLARIFY_SCHEMA["parameters"]["properties"]["choices"]
|
||||
assert choices_spec.get("maxItems") == MAX_CHOICES
|
||||
|
||||
def test_max_choices_is_four(self):
|
||||
"""MAX_CHOICES constant should be 4."""
|
||||
assert MAX_CHOICES == 4
|
||||
Reference in New Issue
Block a user