from __future__ import annotations import asyncio import random from typing import Any from src.agent.tools.feedback import ToolFeedbackController class _FakePlayHandle: def __init__(self) -> None: self._done = False self.stop_calls = 0 def done(self) -> bool: return self._done def stop(self) -> None: self.stop_calls += 1 self._done = True class _FakeBackgroundAudioPlayer: def __init__(self) -> None: self.start_calls = 0 self.play_calls: list[tuple[Any, bool]] = [] self.closed = False self.last_handle: _FakePlayHandle | None = None async def start(self, *, room: Any, agent_session: Any) -> None: self.start_calls += 1 def play(self, clip: Any, *, loop: bool = False) -> _FakePlayHandle: self.play_calls.append((clip, loop)) handle = _FakePlayHandle() self.last_handle = handle return handle async def aclose(self) -> None: self.closed = True def test_tool_feedback_rotates_fallback_phrases_without_repeats_in_one_cycle() -> None: phrases = ("one", "two", "three") controller = ToolFeedbackController( enabled=False, fallback_phrases=phrases, rng=random.Random(7), ) first_cycle = [controller.next_fallback_phrase() for _ in range(len(phrases))] assert sorted(first_cycle) == sorted(phrases) def test_tool_feedback_typing_sound_lifecycle() -> None: async def _run() -> _FakeBackgroundAudioPlayer: fake_player = _FakeBackgroundAudioPlayer() controller = ToolFeedbackController( enabled=True, audio_player_factory=lambda: fake_player, typing_timeout_sec=1.0, rng=random.Random(1), ) await controller.start(room=object(), session=object()) # type: ignore[arg-type] await controller.start_typing_sound() await controller.start_typing_sound() await controller.stop_typing_sound(reason="test") await controller.aclose() return fake_player fake_player = asyncio.run(_run()) assert fake_player.start_calls == 1 assert len(fake_player.play_calls) == 1 assert fake_player.last_handle is not None assert fake_player.last_handle.stop_calls == 1 assert fake_player.closed is True def test_tool_feedback_typing_sound_auto_stops_on_timeout() -> None: async def _run() -> _FakeBackgroundAudioPlayer: fake_player = _FakeBackgroundAudioPlayer() controller = ToolFeedbackController( enabled=True, audio_player_factory=lambda: fake_player, typing_timeout_sec=0.01, rng=random.Random(1), ) await controller.start(room=object(), session=object()) # type: ignore[arg-type] await controller.start_typing_sound() await asyncio.sleep(0.05) await controller.aclose() return fake_player fake_player = asyncio.run(_run()) assert fake_player.last_handle is not None assert fake_player.last_handle.stop_calls == 1