"""CosyVoice TTS 适配器单元测试.""" from __future__ import annotations from unittest.mock import MagicMock, patch import pytest from packages.domain.tts_config import TtsConfig class TestTtsConfig: """TTS 配置解析测试.""" def test_parse_full_config(self): """完整配置解析.""" cfg = TtsConfig.parse( { "enabled": True, "voice_id": "longxiaochun_v3", "text": "你好世界", "speed": 1.2, "pitch": 2.0, "volume": 0.7, "align_mode": "full", "overlap_mode": "mix", } ) assert cfg.enabled is True assert cfg.voice_id == "longxiaochun_v3" assert cfg.text == "你好世界" assert cfg.speed == 1.2 assert cfg.pitch == 2.0 assert cfg.volume == 0.7 assert cfg.align_mode == "full" assert cfg.overlap_mode == "mix" def test_parse_disabled(self): """禁用状态.""" cfg = TtsConfig.parse({"enabled": False}) assert cfg.enabled is False def test_parse_none(self): """空配置.""" cfg = TtsConfig.parse(None) assert cfg.enabled is False def test_parse_empty_text_still_enabled(self): """有 enabled 但无 text,配置仍然有效(调用方判断是否有文本).""" cfg = TtsConfig.parse({"enabled": True, "voice_id": "test"}) assert cfg.enabled is True assert cfg.text == "" def test_speed_clamp(self): """语速边界钳制.""" cfg = TtsConfig.parse({"enabled": True, "speed": 3.0}) assert cfg.speed == 2.0 cfg2 = TtsConfig.parse({"enabled": True, "speed": 0.1}) assert cfg2.speed == 0.5 def test_volume_clamp(self): """音量边界钳制.""" cfg = TtsConfig.parse({"enabled": True, "volume": 2.0}) assert cfg.volume == 1.0 def test_invalid_align_mode(self): """无效对齐模式回退到默认.""" cfg = TtsConfig.parse({"enabled": True, "align_mode": "invalid"}) assert cfg.align_mode == "full" class TestCosyVoiceTtsAdapter: """CosyVoice TTS 适配器测试.""" def test_import_ok(self): """适配器能正常导入.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService assert CosyVoiceTtsService is not None def test_provider_name(self): """provider_name 属性.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService # 用 mock 替换底层 CosyVoiceService with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = CosyVoiceTtsService() assert svc.provider_name == "cosyvoice" def test_available_voices(self): """可用音色列表来自预设音色.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = CosyVoiceTtsService() voices = svc.available_voices() assert len(voices) > 0 assert "longxiaochun_v3" in voices assert "longxiaoxia_v3" in voices def test_estimate_duration(self): """时长估算.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = CosyVoiceTtsService() d = svc.estimate_duration("你好世界", speed=1.0) assert d > 0 # 4 个字,4 字/秒 = 1 秒 assert abs(d - 1.0) < 0.1 def test_estimate_duration_speed(self): """语速影响时长估算.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = CosyVoiceTtsService() d_normal = svc.estimate_duration("你好世界", speed=1.0) d_fast = svc.estimate_duration("你好世界", speed=2.0) assert d_fast < d_normal assert abs(d_fast - d_normal / 2.0) < 0.01 def test_synthesize_empty_text_raises(self): """空文本抛出异常.""" from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService from packages.ports.tts_service import TtsError with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = CosyVoiceTtsService() with pytest.raises(TtsError, match="文本不能为空"): svc.synthesize(" ") class TestTtsServiceFactory: """TTS 服务工厂测试.""" def test_mock_provider(self): """mock provider 正常.""" from apps.worker.services.tts_service_factory import get_tts_service svc = get_tts_service("mock") assert svc.provider_name == "mock" def test_cosyvoice_provider(self): """cosyvoice provider 注册正常.""" from apps.worker.services.tts_service_factory import get_tts_service with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = get_tts_service("cosyvoice") assert svc.provider_name == "cosyvoice" def test_aliyun_alias(self): """aliyun 别名映射到 cosyvoice.""" from apps.worker.services.tts_service_factory import get_tts_service with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = get_tts_service("aliyun") assert svc.provider_name == "cosyvoice" def test_dashscope_alias(self): """dashscope 别名映射到 cosyvoice.""" from apps.worker.services.tts_service_factory import get_tts_service with patch("packages.application.cosyvoice_service.CosyVoiceService"): svc = get_tts_service("dashscope") assert svc.provider_name == "cosyvoice" def test_unknown_fallback_to_mock(self): """未知 provider 回退到 mock.""" from apps.worker.services.tts_service_factory import get_tts_service svc = get_tts_service("unknown_provider") assert svc.provider_name == "mock"