fix(qqbot): always clean up temp stt wav

This commit is contained in:
Zhekinmaksim 2026-06-03 21:17:28 +05:00 committed by Teknium
parent fef1b8cbbe
commit a2d8d3afc5
2 changed files with 75 additions and 7 deletions

View file

@ -1936,15 +1936,15 @@ class QQAdapter(BasePlatformAdapter):
)
return None
# 4. Call STT API
# 4. Call STT API and always clean up the temp WAV afterward.
logger.debug("[%s] STT: calling ASR on %s", self._log_tag, wav_path)
transcript = await self._call_stt(wav_path)
# 5. Cleanup temp file
try:
os.unlink(wav_path)
except OSError:
pass
transcript = await self._call_stt(wav_path)
finally:
try:
os.unlink(wav_path)
except OSError:
pass
if transcript:
logger.debug("[%s] STT success: %r", self._log_tag, transcript[:100])

View file

@ -5,6 +5,7 @@ import os
from types import SimpleNamespace
from unittest import mock
import httpx
import pytest
from gateway.config import PlatformConfig
@ -215,6 +216,73 @@ class TestVoiceAttachmentSSRFProtection:
assert connected_explicit is False
# ---------------------------------------------------------------------------
# Voice attachment temp-file cleanup
# ---------------------------------------------------------------------------
class TestVoiceAttachmentTempCleanup:
def _make_adapter(self, **extra):
from gateway.platforms.qqbot import QQAdapter
return QQAdapter(_make_config(**extra))
def _setup_download_mocks(self, adapter, content=b"RIFFmock-wav-audio-data"):
response = mock.Mock()
response.content = content
response.headers = {"content-type": "audio/wav"}
response.raise_for_status = mock.Mock()
adapter._http_client = mock.AsyncMock()
adapter._http_client.get = mock.AsyncMock(return_value=response)
def test_temp_wav_cleaned_up_on_stt_failure(self):
adapter = self._make_adapter(app_id="a", client_secret="b")
self._setup_download_mocks(adapter)
seen = {}
async def _raise_transport_error(path):
seen["wav_path"] = path
raise httpx.TransportError("boom")
with mock.patch("tools.url_safety.is_safe_url", return_value=True):
adapter._call_stt = mock.AsyncMock(side_effect=_raise_transport_error)
transcript = asyncio.run(
adapter._stt_voice_attachment(
"https://cdn.qq.com/voice.silk",
"audio/silk",
"voice.silk",
voice_wav_url="https://cdn.qq.com/voice.wav",
)
)
assert transcript is None
assert "wav_path" in seen
assert not os.path.exists(seen["wav_path"])
def test_temp_wav_cleaned_up_on_stt_success(self):
adapter = self._make_adapter(app_id="a", client_secret="b")
self._setup_download_mocks(adapter)
seen = {}
async def _return_transcript(path):
seen["wav_path"] = path
return "hello from qq voice"
with mock.patch("tools.url_safety.is_safe_url", return_value=True):
adapter._call_stt = mock.AsyncMock(side_effect=_return_transcript)
transcript = asyncio.run(
adapter._stt_voice_attachment(
"https://cdn.qq.com/voice.silk",
"audio/silk",
"voice.silk",
voice_wav_url="https://cdn.qq.com/voice.wav",
)
)
assert transcript == "hello from qq voice"
assert "wav_path" in seen
assert not os.path.exists(seen["wav_path"])
# ---------------------------------------------------------------------------
# WebSocket proxy handling
# ---------------------------------------------------------------------------