"""CopilotSession unit tests.""" import asyncio from datetime import UTC, datetime from unittest.mock import AsyncMock, Mock from uuid import uuid4 import pytest from copilot.session import CopilotSession from copilot.session_events import ( AssistantMessageData, ExternalToolCompletedData, ExternalToolRequestedData, SessionEvent, SessionEventType, SessionIdleData, SessionMode, ) from copilot.tools import Tool, ToolResult def _event(data, event_type: SessionEventType) -> SessionEvent: return SessionEvent( data=data, id=uuid4(), timestamp=datetime.now(UTC), type=event_type, ) @pytest.mark.asyncio async def test_send_and_wait_skips_autopilot_continuation_idle(): client = Mock() client.request = AsyncMock(return_value={"messageId": "message-1"}) session = CopilotSession("session-1", client) pending = asyncio.create_task(session.send_and_wait("keep going")) await asyncio.sleep(0) client.request.assert_awaited_once() session._dispatch_event( _event( AssistantMessageData(content="intermediate", message_id="assistant-1"), SessionEventType.ASSISTANT_MESSAGE, ) ) session._dispatch_event( _event( SessionIdleData(mode=SessionMode.AUTOPILOT), SessionEventType.SESSION_IDLE, ) ) assert not pending.done() session._dispatch_event( _event( AssistantMessageData(content="final", message_id="assistant-2"), SessionEventType.ASSISTANT_MESSAGE, ) ) session._dispatch_event( _event( SessionIdleData(mode=SessionMode.INTERACTIVE), SessionEventType.SESSION_IDLE, ) ) result = await asyncio.wait_for(pending, timeout=1) assert result is not None assert isinstance(result.data, AssistantMessageData) assert result.data.content == "final" @pytest.mark.asyncio async def test_external_tool_completed_cancels_blocked_handler(): client = Mock() client.request = AsyncMock() session = CopilotSession("session-1", client) started = asyncio.Event() cancelled = asyncio.Event() async def blocked_tool(_invocation): started.set() try: await asyncio.Future() except asyncio.CancelledError: cancelled.set() return ToolResult(text_result_for_llm="late result") session._register_tools([Tool("blocked_tool", "Blocks", blocked_tool)]) session._dispatch_event( _event( ExternalToolRequestedData( request_id="request-1", session_id="session-1", tool_call_id="tool-call-1", tool_name="blocked_tool", ), SessionEventType.EXTERNAL_TOOL_REQUESTED, ) ) await asyncio.wait_for(started.wait(), timeout=1) session._dispatch_event( _event( ExternalToolCompletedData(request_id="request-1"), SessionEventType.EXTERNAL_TOOL_COMPLETED, ) ) await asyncio.wait_for(cancelled.wait(), timeout=1) await asyncio.sleep(0) client.request.assert_not_awaited() @pytest.mark.asyncio async def test_disconnect_from_tool_task_does_not_cancel_detach_request(): client = Mock() client.request = AsyncMock(return_value={"success": True}) session = CopilotSession("session-1", client) current_task = asyncio.current_task() assert current_task is not None session._pending_external_tools["request-1"] = current_task await session.disconnect() client.request.assert_awaited_once_with("session.detach", {"sessionId": "session-1"})