05e173b293
Replace the WebView shell with SwiftUI screens, add account-scoped Wiki and TTS APIs with adaptive review and photo scan support, and keep web/iOS pages usable while data loads asynchronously. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
5.0 KiB
Python
116 lines
5.0 KiB
Python
import base64
|
||
import json
|
||
import os
|
||
import tempfile
|
||
import unittest
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
from services.tts_service import TtsService, TtsServiceError, tts_service
|
||
|
||
|
||
class _FakeResponse:
|
||
def __init__(self, body: bytes, content_type: str = "application/json"):
|
||
self._body = body
|
||
self.headers = MagicMock()
|
||
self.headers.get_content_type.return_value = content_type
|
||
|
||
def read(self):
|
||
return self._body
|
||
|
||
def __enter__(self):
|
||
return self
|
||
|
||
def __exit__(self, exc_type, exc, tb):
|
||
return False
|
||
|
||
|
||
class TtsServiceTest(unittest.TestCase):
|
||
def tearDown(self):
|
||
get_settings = __import__("config", fromlist=["get_settings"]).get_settings
|
||
get_settings.cache_clear()
|
||
|
||
def test_not_configured_when_disabled(self):
|
||
with patch.dict(
|
||
os.environ,
|
||
{"TKMIND_TTS_ENABLED": "false", "TKMIND_TTS_BASE_URL": "http://127.0.0.1:19200"},
|
||
clear=False,
|
||
):
|
||
__import__("config", fromlist=["get_settings"]).get_settings.cache_clear()
|
||
self.assertFalse(tts_service.is_configured())
|
||
|
||
@patch("services.tts_service._open_tts_request")
|
||
def test_request_includes_user_agent(self, urlopen_mock: MagicMock):
|
||
wav = b"RIFFxxxx"
|
||
payload = {"code": 0, "data": base64.b64encode(wav).decode("ascii")}
|
||
urlopen_mock.return_value = _FakeResponse(json.dumps(payload).encode("utf-8"))
|
||
|
||
with patch.dict(
|
||
os.environ,
|
||
{"TKMIND_TTS_BASE_URL": "https://asr.tkmind.cn", "TKMIND_TTS_ENABLED": "true"},
|
||
clear=False,
|
||
):
|
||
__import__("config", fromlist=["get_settings"]).get_settings.cache_clear()
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
with patch("services.tts_service._CACHE_DIR", __import__("pathlib").Path(tmp)):
|
||
tts_service.synthesize("apple")
|
||
request = urlopen_mock.call_args.args[0]
|
||
self.assertEqual(request.get_header("User-agent"), "WordLoop-TTS/1.0")
|
||
|
||
@patch("services.tts_service._open_tts_request")
|
||
def test_synthesize_parses_json_base64(self, urlopen_mock: MagicMock):
|
||
wav = b"RIFFxxxx"
|
||
payload = {"code": 0, "message": "ok", "data": base64.b64encode(wav).decode("ascii")}
|
||
urlopen_mock.return_value = _FakeResponse(json.dumps(payload).encode("utf-8"))
|
||
|
||
with patch.dict(
|
||
os.environ,
|
||
{"TKMIND_TTS_BASE_URL": "http://127.0.0.1:19200", "TKMIND_TTS_ENABLED": "true"},
|
||
clear=False,
|
||
):
|
||
__import__("config", fromlist=["get_settings"]).get_settings.cache_clear()
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
with patch("services.tts_service._CACHE_DIR", __import__("pathlib").Path(tmp)):
|
||
audio, content_type = tts_service.synthesize("apple")
|
||
self.assertEqual(audio, wav)
|
||
self.assertEqual(content_type, "audio/wav")
|
||
|
||
@patch("services.tts_service._open_tts_request")
|
||
def test_synthesize_uses_cache_on_second_call(self, urlopen_mock: MagicMock):
|
||
wav = b"RIFFcached"
|
||
payload = {"code": 0, "data": base64.b64encode(wav).decode("ascii")}
|
||
urlopen_mock.return_value = _FakeResponse(json.dumps(payload).encode("utf-8"))
|
||
|
||
with patch.dict(
|
||
os.environ,
|
||
{"TKMIND_TTS_BASE_URL": "http://127.0.0.1:19200", "TKMIND_TTS_ENABLED": "true"},
|
||
clear=False,
|
||
):
|
||
__import__("config", fromlist=["get_settings"]).get_settings.cache_clear()
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
with patch("services.tts_service._CACHE_DIR", __import__("pathlib").Path(tmp)):
|
||
first, _ = tts_service.synthesize("apple")
|
||
second, _ = tts_service.synthesize("apple")
|
||
self.assertEqual(first, second)
|
||
self.assertEqual(urlopen_mock.call_count, 1)
|
||
|
||
@patch("services.tts_service._open_tts_request")
|
||
def test_upstream_error_message(self, urlopen_mock: MagicMock):
|
||
payload = {"code": 500, "message": "TTS 未启用(ENABLE_TTS=false)", "data": None}
|
||
urlopen_mock.return_value = _FakeResponse(json.dumps(payload).encode("utf-8"))
|
||
|
||
with patch.dict(
|
||
os.environ,
|
||
{"TKMIND_TTS_BASE_URL": "http://127.0.0.1:19200", "TKMIND_TTS_ENABLED": "true"},
|
||
clear=False,
|
||
):
|
||
__import__("config", fromlist=["get_settings"]).get_settings.cache_clear()
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
with patch("services.tts_service._CACHE_DIR", __import__("pathlib").Path(tmp)):
|
||
with self.assertRaises(TtsServiceError) as ctx:
|
||
tts_service.synthesize("apple")
|
||
self.assertIn("TTS 未启用", str(ctx.exception))
|
||
|
||
def test_extract_audio_base64_from_dict(self):
|
||
value = TtsService._extract_audio_base64({"audio": "abc"})
|
||
self.assertEqual(value, "abc")
|