Spaces:
Sleeping
Sleeping
feat: overhaul MCP architecture with structured tool schemas, comprehensive care-mode skill definitions, and enhanced test coverage for pipelines and service integration.
79df050 | import base64 | |
| import pytest | |
| from fastapi import WebSocketDisconnect | |
| import app | |
| from services.tts_service import TTSService, get_emotion_rate | |
| def test_google_tts_voice_aliases_are_multilingual(): | |
| service = TTSService() | |
| assert service._voice_config("coral") == {"languageCode": "cmn-TW", "name": "cmn-TW-Wavenet-A"} | |
| assert service._voice_config("ja-jp") == {"languageCode": "ja-JP", "name": "ja-JP-Neural2-B"} | |
| assert service._voice_config("vi-vn") == {"languageCode": "vi-VN", "name": "vi-VN-Wavenet-A"} | |
| def test_google_tts_emotion_rate_is_conservative_for_care(): | |
| assert get_emotion_rate("happy") > 1.0 | |
| assert get_emotion_rate("sad") < 1.0 | |
| assert get_emotion_rate("neutral", care_mode=True) < 1.0 | |
| async def test_google_tts_requires_api_key(): | |
| service = TTSService() | |
| service.api_key = "" | |
| result = await service.synthesize("你好") | |
| assert result["success"] is False | |
| assert "GOOGLE_TTS_API_KEY" in result["error"] | |
| async def test_google_tts_decodes_audio_content(monkeypatch): | |
| service = TTSService() | |
| service.api_key = "test-key" | |
| captured = {} | |
| class FakeResponse: | |
| status = 200 | |
| async def __aenter__(self): | |
| return self | |
| async def __aexit__(self, *args): | |
| return False | |
| async def json(self, content_type=None): | |
| return {"audioContent": base64.b64encode(b"mp3").decode("ascii")} | |
| class FakeSession: | |
| async def __aenter__(self): | |
| return self | |
| async def __aexit__(self, *args): | |
| return False | |
| def post(self, url, params=None, json=None, timeout=None): | |
| captured["url"] = url | |
| captured["params"] = params | |
| captured["json"] = json | |
| return FakeResponse() | |
| monkeypatch.setattr("services.tts_service.aiohttp.ClientSession", FakeSession) | |
| result = await service.synthesize("你好", voice="coral", emotion="happy") | |
| assert result["success"] is True | |
| assert result["audio_data"] == b"mp3" | |
| assert captured["params"] == {"key": "test-key"} | |
| assert captured["json"]["voice"]["languageCode"] == "cmn-TW" | |
| assert captured["json"]["audioConfig"]["speakingRate"] > 1.0 | |
| async def test_tts_websocket_client_disconnect_is_not_treated_as_server_error(monkeypatch): | |
| events = [] | |
| class FakeWebSocket: | |
| def __init__(self): | |
| self.closed = False | |
| self.send_count = 0 | |
| async def accept(self): | |
| return None | |
| async def receive_json(self): | |
| return { | |
| "text": "你好", | |
| "voice": "nova", | |
| "language": "zh-TW", | |
| "persona": "xiaohua", | |
| "speaking_rate": 0.94, | |
| } | |
| async def send_json(self, payload): | |
| self.send_count += 1 | |
| events.append(payload["type"]) | |
| if payload["type"] == "tts_audio_chunk": | |
| raise WebSocketDisconnect() | |
| async def close(self): | |
| self.closed = True | |
| class FakeTTSService: | |
| async def streaming_synthesize(self, **kwargs): | |
| yield b"\x00\x01" | |
| monkeypatch.setattr(app, "logger", type("Logger", (), { | |
| "info": lambda *args, **kwargs: events.append("log:info"), | |
| "debug": lambda *args, **kwargs: events.append("log:debug"), | |
| "error": lambda *args, **kwargs: events.append("log:error"), | |
| "exception": lambda *args, **kwargs: events.append("log:exception"), | |
| })()) | |
| import services.tts_service as tts_module | |
| monkeypatch.setattr(tts_module, "tts_service", FakeTTSService()) | |
| websocket = FakeWebSocket() | |
| await app.tts_stream_websocket(websocket) | |
| assert events[:2] == ["tts_stream_start", "tts_audio_chunk"] | |
| assert "log:debug" in events | |
| assert "log:error" not in events | |
| assert "log:exception" not in events | |
| assert websocket.closed is True | |
| async def test_tts_websocket_logs_chunk_stats_before_client_disconnect(monkeypatch): | |
| events = [] | |
| class FakeWebSocket: | |
| def __init__(self): | |
| self.closed = False | |
| async def accept(self): | |
| return None | |
| async def receive_json(self): | |
| return { | |
| "text": "你好", | |
| "voice": "nova", | |
| "language": "zh-TW", | |
| "persona": "xiaohua", | |
| "speaking_rate": 0.94, | |
| } | |
| async def send_json(self, payload): | |
| if payload["type"] == "tts_audio_chunk": | |
| raise WebSocketDisconnect() | |
| async def close(self): | |
| self.closed = True | |
| class FakeTTSService: | |
| async def streaming_synthesize(self, **kwargs): | |
| yield b"\x00\x01" | |
| monkeypatch.setattr(app, "logger", type("Logger", (), { | |
| "info": lambda self, message, *args, **kwargs: events.append(message % args if args else message), | |
| "debug": lambda self, message, *args, **kwargs: events.append(message % args if args else message), | |
| "error": lambda self, *args, **kwargs: None, | |
| "exception": lambda self, *args, **kwargs: None, | |
| })()) | |
| import services.tts_service as tts_module | |
| monkeypatch.setattr(tts_module, "tts_service", FakeTTSService()) | |
| websocket = FakeWebSocket() | |
| await app.tts_stream_websocket(websocket) | |
| assert any("chunks=1" in event for event in events) | |
| assert any("bytes=2" in event for event in events) | |
| assert websocket.closed is True | |