from __future__ import annotations import pytest from vibe.core.tools.base import BaseToolState, ToolError from vibe.core.tools.builtins.ask_user_question import ( Answer, AskUserQuestion, AskUserQuestionArgs, AskUserQuestionConfig, AskUserQuestionResult, Choice, Question, ) from vibe.core.types import ToolCallEvent, ToolResultEvent @pytest.fixture def tool(): config = AskUserQuestionConfig() return AskUserQuestion(config_getter=lambda: config, state=BaseToolState()) @pytest.fixture def single_question_args(): return AskUserQuestionArgs( questions=[ Question( question="Which database?", header="DB", options=[ Choice(label="PostgreSQL", description="Relational DB"), Choice(label="MongoDB", description="Document DB"), ], ) ] ) @pytest.fixture def multi_question_args(): return AskUserQuestionArgs( questions=[ Question( question="Which database?", header="DB", options=[Choice(label="PostgreSQL"), Choice(label="MongoDB")], ), Question( question="Which framework?", header="Framework", options=[Choice(label="FastAPI"), Choice(label="Django")], ), ] ) async def run_tool_with_callback(tool, args, callback): from vibe.core.tools.base import InvokeContext ctx = InvokeContext(user_input_callback=callback, tool_call_id="123") result = None async for item in tool.run(args, ctx): result = item return result @pytest.mark.asyncio async def test_raises_error_without_callback(tool, single_question_args): with pytest.raises(ToolError) as err: async for _ in tool.run(single_question_args, ctx=None): pass assert "interactive UI" in str(err.value) @pytest.mark.asyncio async def test_calls_callback_and_returns_result(tool, single_question_args): expected_result = AskUserQuestionResult( answers=[ Answer(question="Which database?", answer="PostgreSQL", is_other=False) ], cancelled=False, ) async def mock_callback(args): assert args == single_question_args return expected_result result = await run_tool_with_callback(tool, single_question_args, mock_callback) assert result is not None assert result == expected_result assert result.answers[0].answer == "PostgreSQL" @pytest.mark.asyncio async def test_handles_cancelled_result(tool, single_question_args): expected_result = AskUserQuestionResult(answers=[], cancelled=True) async def mock_callback(args): return expected_result result = await run_tool_with_callback(tool, single_question_args, mock_callback) assert result is not None assert result.cancelled is True assert len(result.answers) == 0 @pytest.mark.asyncio async def test_handles_other_response(tool, single_question_args): expected_result = AskUserQuestionResult( answers=[Answer(question="Which database?", answer="SQLite", is_other=True)], cancelled=False, ) async def mock_callback(args): return expected_result result = await run_tool_with_callback(tool, single_question_args, mock_callback) assert result is not None assert result.answers[0].is_other is True assert result.answers[0].answer == "SQLite" class TestToolUIDisplay: def test_get_call_display_single_question(self, single_question_args): event = ToolCallEvent( tool_name="ask_user_question", tool_class=AskUserQuestion, args=single_question_args, tool_call_id="123", ) display = AskUserQuestion.get_call_display(event) assert "Which database?" in display.summary def test_get_call_display_multiple_questions(self, multi_question_args): event = ToolCallEvent( tool_name="ask_user_question", tool_class=AskUserQuestion, args=multi_question_args, tool_call_id="123", ) display = AskUserQuestion.get_call_display(event) assert "2 questions" in display.summary def test_get_result_display_success(self): result = AskUserQuestionResult( answers=[Answer(question="Q?", answer="A", is_other=False)], cancelled=False ) event = ToolResultEvent( tool_name="ask_user_question", tool_class=AskUserQuestion, result=result, tool_call_id="123", ) display = AskUserQuestion.get_result_display(event) assert display.success is True assert "A" in display.message def test_get_result_display_cancelled(self): result = AskUserQuestionResult(answers=[], cancelled=True) event = ToolResultEvent( tool_name="ask_user_question", tool_class=AskUserQuestion, result=result, tool_call_id="123", ) display = AskUserQuestion.get_result_display(event) assert display.success is False assert "cancelled" in display.message.lower() def test_get_result_display_other(self): result = AskUserQuestionResult( answers=[Answer(question="Q?", answer="Custom", is_other=True)], cancelled=False, ) event = ToolResultEvent( tool_name="ask_user_question", tool_class=AskUserQuestion, result=result, tool_call_id="123", ) display = AskUserQuestion.get_result_display(event) assert display.success is True assert "(Other)" in display.message