hermes-agent/tests/gateway/test_auto_voice_reply_format.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

103 lines
3.6 KiB
Python

"""Tests for gateway auto-TTS voice reply audio format selection."""
import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from gateway.config import Platform
from gateway.platforms.base import MessageEvent, MessageType
from gateway.run import GatewayRunner
from gateway.session import SessionSource
class TestAutoVoiceReplyFormat:
@pytest.mark.asyncio
@pytest.mark.parametrize(
"platform",
[Platform.MATRIX, Platform.FEISHU, Platform.WHATSAPP, Platform.SIGNAL],
)
async def test_opus_platform_auto_voice_reply_requests_ogg(self, platform):
"""Every OPUS_VOICE_PLATFORMS member gets an explicit .ogg output path.
Regression for #14841 (Matrix) / #45557 (Feishu): _send_voice_reply
hardcoded .ogg for Telegram only, so Matrix/Feishu voice replies were
synthesized as MP3 and delivered as plain attachments instead of
native voice bubbles.
"""
runner = _make_runner()
adapter = _make_adapter(platform)
runner.adapters[platform] = adapter
event = _make_event(platform)
requested_paths = []
def fake_tts(*, text, output_path):
requested_paths.append(output_path)
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
Path(output_path).write_bytes(b"fake ogg opus")
return json.dumps({
"success": True,
"file_path": output_path,
"provider": "gemini",
"voice_compatible": True,
})
with patch("tools.tts_tool.text_to_speech_tool", side_effect=fake_tts):
await runner._send_voice_reply(event, "hello from auto tts")
assert requested_paths and requested_paths[0].endswith(".ogg")
adapter.send_voice.assert_awaited_once()
assert adapter.send_voice.await_args.kwargs["audio_path"].endswith(".ogg")
def test_should_send_voice_reply_streamed_global_auto_tts_fires(self):
"""Streamed reply + global voice.auto_tts (no /voice opt-in) sends voice.
Regression for the #51867/#23983 remainder: when streaming consumed
the text, the base adapter's auto-TTS gets text_content=None, and the
runner path used to consult only self._voice_mode — so a chat relying
purely on the global voice.auto_tts default silently lost its voice
reply.
"""
runner = _make_runner()
adapter = _make_adapter(Platform.TELEGRAM)
adapter._should_auto_tts_for_chat = MagicMock(return_value=True)
runner.adapters[Platform.TELEGRAM] = adapter
voice_event = _make_event(
Platform.TELEGRAM, chat_id="123", message_type=MessageType.VOICE
)
assert runner._should_send_voice_reply(
voice_event, "hello", [], already_sent=True
) is True
def _make_runner() -> GatewayRunner:
with patch("gateway.run.GatewayRunner._load_voice_modes", return_value={}):
runner = GatewayRunner.__new__(GatewayRunner)
runner._voice_mode = {}
runner.adapters = {}
return runner
def _make_adapter(platform: Platform) -> MagicMock:
adapter = MagicMock()
adapter.platform = platform
adapter.send_voice = AsyncMock()
return adapter
def _make_event(platform: Platform, chat_id: str = "123", message_type: MessageType = MessageType.TEXT) -> MessageEvent:
return MessageEvent(
text="trigger",
source=SessionSource(
platform=platform,
chat_id=chat_id,
user_id="u1",
user_name="User",
),
message_type=message_type,
message_id="456",
)