"""MockTtsService 单测 — Mock TTS服务纯逻辑部分.""" from __future__ import annotations import pytest from packages.adapters.tts.mock_tts_service import _CHARS_PER_SECOND, MockTtsService # ── Fixtures ──────────────────────────────────────────────────────────────── @pytest.fixture def service(): return MockTtsService() # ── estimate_duration ────────────────────────────────────────────────────── class TestEstimateDuration: """estimate_duration 时长估算.""" def test_empty_text_returns_zero(self, service): assert service.estimate_duration("") == 0.0 def test_whitespace_only_returns_zero(self, service): assert service.estimate_duration(" \n\t ") == 0.0 def test_single_char(self, service): result = service.estimate_duration("你") assert abs(result - 1.0 / _CHARS_PER_SECOND) < 0.001 def test_default_speed(self, service): """默认 speed=1.0.""" text = "你好世界" # 4个字 result = service.estimate_duration(text) expected = 4.0 / _CHARS_PER_SECOND assert abs(result - expected) < 0.001 def test_faster_speed_shortens_duration(self, service): """语速越快,时长越短.""" text = "你好世界" normal = service.estimate_duration(text, speed=1.0) fast = service.estimate_duration(text, speed=2.0) assert fast < normal assert abs(fast - normal / 2) < 0.001 def test_slower_speed_lengthens_duration(self, service): """语速越慢,时长越长.""" text = "你好世界" normal = service.estimate_duration(text, speed=1.0) slow = service.estimate_duration(text, speed=0.5) assert slow > normal assert abs(slow - normal / 0.5) < 0.001 def test_speed_clamped_at_minimum(self, service): """speed < 0.1 时被钳制到 0.1,避免除零.""" text = "你好" # 传一个极小的值,不应该崩溃,且时长不会无限大 result = service.estimate_duration(text, speed=0.001) assert result > 0 # 应该等同于 speed=0.1 expected = 2.0 / _CHARS_PER_SECOND / 0.1 assert abs(result - expected) < 0.001 def test_chinese_and_english_mixed(self, service): """中英文混合时按非空白字符计数.""" text = "Hello 世界" # H-e-l-l-o + 世-界 = 7个非空白字符 result = service.estimate_duration(text) expected = 7.0 / _CHARS_PER_SECOND assert abs(result - expected) < 0.001 def test_negative_speed(self, service): """负语速按最小处理(取 max(0.1, speed)).""" text = "你好" result = service.estimate_duration(text, speed=-2.0) assert result > 0 # 等同于 speed=0.1 expected = 2.0 / _CHARS_PER_SECOND / 0.1 assert abs(result - expected) < 0.001 # ── _extract_freq ────────────────────────────────────────────────────────── class TestExtractFreq: """_extract_freq 基频提取.""" def test_sine_prefix_returns_freq(self, service): """sine_ 前缀的voice_id,从下划线后提取频率.""" result = service._extract_freq("sine_440", "female") assert result == 440.0 def test_sine_with_decimal(self, service): """支持小数频率.""" result = service._extract_freq("sine_261.63", "female") assert abs(result - 261.63) < 0.001 def test_sine_invalid_number_falls_back(self, service): """sine_ 后面不是数字时,fallback 到性别默认值.""" result = service._extract_freq("sine_abc", "female") assert result == 220.0 # female 默认 def test_sine_no_number_falls_back(self, service): """sine_ 后面没有内容时,fallback.""" result = service._extract_freq("sine_", "male") assert result == 120.0 # male 默认 def test_male_default(self, service): result = service._extract_freq("some_voice", "male") assert result == 120.0 def test_female_default(self, service): result = service._extract_freq("some_voice", "female") assert result == 220.0 def test_child_default(self, service): result = service._extract_freq("some_voice", "child") assert result == 350.0 def test_unknown_gender_defaults_to_female(self, service): """未知性别 fallback 到 female.""" result = service._extract_freq("some_voice", "alien") assert result == 220.0 def test_empty_gender_defaults_to_female(self, service): result = service._extract_freq("some_voice", "") assert result == 220.0 # ── provider_name / available_voices ─────────────────────────────────────── class TestProviderInfo: """provider_name 和 available_voices.""" def test_provider_name(self, service): assert service.provider_name == "mock" def test_available_voices_returns_list(self, service): voices = service.available_voices() assert isinstance(voices, list) assert len(voices) > 0