"""src.sentinels is the single place that recognises router sentinels (__AUDIO__: and __STEERED__) so adapters don't each hand-roll the prefix check. Offline, no subprocess/network involved. """ from __future__ import annotations import asyncio import pytest from src.sentinels import AUDIO_PREFIX, STEERED, audio_path, is_steered def test_audio_path_extracts_payload(): assert audio_path(f"{AUDIO_PREFIX}/tmp/echo-audio.wav") == "/tmp/echo-audio.wav" def test_audio_path_none_for_plain_text(): assert audio_path("salut, ce mai faci?") is None def test_audio_path_none_for_empty_and_none(): assert audio_path("") is None assert audio_path(None) is None def test_audio_path_none_for_steered(): assert audio_path(STEERED) is None def test_is_steered_true_only_for_exact_sentinel(): assert is_steered(STEERED) is True assert is_steered("__STEERED__ ") is False assert is_steered("some text") is False def test_is_steered_false_for_empty_and_none(): assert is_steered("") is False assert is_steered(None) is False def test_is_steered_false_for_audio_sentinel(): assert is_steered(f"{AUDIO_PREFIX}/tmp/x.wav") is False # --- Voice pipeline: a steered response must not be mirrored into the # text channel (blocker C2 in tasks/steering-plan.md). Reuses the fakes # from tests/test_pipeline_mirror.py. from unittest.mock import AsyncMock, MagicMock from src.voice.pipeline import VoiceSession def _make_text_channel(send_mock: AsyncMock) -> MagicMock: tc = MagicMock(name="text_channel") tc.send = send_mock return tc def _make_session(*, reply_text: str, text_channel) -> VoiceSession: bot = MagicMock(name="bot") bot.get_channel = MagicMock(return_value=text_channel) bot.get_user = MagicMock(return_value=None) ttsq = MagicMock(name="ttsq") ttsq.push_text = MagicMock() ttsq.clear = MagicMock() route_mock = MagicMock(name="route_message", return_value=(reply_text, False)) return VoiceSession( text_channel_id=1001, voice_channel_id=2002, guild_id=42, voice_client=MagicMock(name="voice_client"), bot=bot, ttsq=ttsq, whitelist=set(), record_enabled=False, mirror_enabled=True, transcripts_jsonl_path=None, loop=asyncio.get_event_loop_policy().new_event_loop(), router_route_message=route_mock, ) def _reply_chunks(send_mock: AsyncMock) -> list[str]: return [ call.args[0] for call in send_mock.call_args_list if not call.args[0].startswith("\U0001f3a4") ] @pytest.mark.asyncio async def test_voice_path_does_not_mirror_steered_sentinel(): send_mock = AsyncMock(name="text_send") text_channel = _make_text_channel(send_mock) session = _make_session(reply_text=STEERED, text_channel=text_channel) await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1) assert _reply_chunks(send_mock) == []