"""TtsConfig 单元测试.""" from __future__ import annotations import pytest from domain.tts_config import TtsConfig class TestTtsConfigDefaults: """默认值测试.""" def test_default_values(self): config = TtsConfig() assert config.enabled is False assert config.voice_id == "" assert config.speed == 1.0 assert config.pitch == 0.0 assert config.volume == 0.8 assert config.text == "" assert config.align_mode == "full" assert config.overlap_mode == "replace" def test_custom_construction(self): config = TtsConfig( enabled=True, voice_id="voice_001", speed=1.5, pitch=3.0, volume=0.9, text="hello", align_mode="subtitle", overlap_mode="mix", ) assert config.enabled is True assert config.voice_id == "voice_001" assert config.speed == 1.5 assert config.pitch == 3.0 assert config.volume == 0.9 assert config.text == "hello" assert config.align_mode == "subtitle" assert config.overlap_mode == "mix" def test_slots_no_extra_attrs(self): config = TtsConfig() with pytest.raises((AttributeError, TypeError)): config.new_attr = "value" # type: ignore[attr-defined] def test_equality_same_values(self): a = TtsConfig(enabled=True, voice_id="v1") b = TtsConfig(enabled=True, voice_id="v1") assert a == b def test_equality_different_values(self): a = TtsConfig(enabled=True) b = TtsConfig(enabled=False) assert a != b class TestTtsConfigParseNoneAndEmpty: """parse 空输入测试.""" def test_parse_none(self): config = TtsConfig.parse(None) assert config == TtsConfig() def test_parse_empty_dict(self): config = TtsConfig.parse({}) assert config == TtsConfig() def test_parse_non_dict_string(self): config = TtsConfig.parse("not a dict") # type: ignore[arg-type] assert config == TtsConfig() def test_parse_non_dict_list(self): config = TtsConfig.parse([]) # type: ignore[arg-type] assert config == TtsConfig() def test_parse_non_dict_number(self): config = TtsConfig.parse(123) # type: ignore[arg-type] assert config == TtsConfig() class TestTtsConfigParseDisabled: """parse disabled 场景.""" def test_parse_enabled_false_returns_default(self): config = TtsConfig.parse({"enabled": False}) assert config.enabled is False assert config.speed == 1.0 assert config.voice_id == "" def test_parse_enabled_false_ignores_other_fields(self): config = TtsConfig.parse( { "enabled": False, "voice_id": "v1", "speed": 1.5, } ) assert config.enabled is False assert config.voice_id == "" assert config.speed == 1.0 def test_parse_enabled_non_bool_falls_to_false(self): config = TtsConfig.parse({"enabled": "true"}) assert config.enabled is False def test_parse_enabled_int_falls_to_false(self): config = TtsConfig.parse({"enabled": 1}) assert config.enabled is False class TestTtsConfigParseNormal: """parse 正常数据测试.""" def test_parse_full_data(self): data = { "enabled": True, "voice_id": "voice_001", "speed": 1.5, "pitch": 2.5, "volume": 0.7, "text": "你好世界", "align_mode": "subtitle", "overlap_mode": "mix", } config = TtsConfig.parse(data) assert config.enabled is True assert config.voice_id == "voice_001" assert config.speed == 1.5 assert config.pitch == 2.5 assert config.volume == 0.7 assert config.text == "你好世界" assert config.align_mode == "subtitle" assert config.overlap_mode == "mix" def test_parse_int_speed_becomes_float(self): config = TtsConfig.parse({"enabled": True, "speed": 2}) assert isinstance(config.speed, float) assert config.speed == 2.0 def test_parse_int_pitch_becomes_float(self): config = TtsConfig.parse({"enabled": True, "pitch": -3}) assert isinstance(config.pitch, float) assert config.pitch == -3.0 class TestTtsConfigParseTypeFallback: """parse 类型错误回退测试.""" def test_parse_voice_id_non_string_fallback(self): config = TtsConfig.parse({"enabled": True, "voice_id": 123}) assert config.voice_id == "" def test_parse_speed_non_numeric_fallback(self): config = TtsConfig.parse({"enabled": True, "speed": "fast"}) assert config.speed == 1.0 def test_parse_pitch_non_numeric_fallback(self): config = TtsConfig.parse({"enabled": True, "pitch": "high"}) assert config.pitch == 0.0 def test_parse_volume_non_numeric_fallback(self): config = TtsConfig.parse({"enabled": True, "volume": "loud"}) assert config.volume == 0.8 def test_parse_text_non_string_fallback(self): config = TtsConfig.parse({"enabled": True, "text": 456}) assert config.text == "" def test_parse_voice_id_list_fallback(self): config = TtsConfig.parse({"enabled": True, "voice_id": ["v1"]}) assert config.voice_id == "" class TestTtsConfigParseClamp: """parse 边界钳制测试.""" def test_parse_speed_below_min_clamped(self): config = TtsConfig.parse({"enabled": True, "speed": 0.1}) assert config.speed == 0.5 def test_parse_speed_above_max_clamped(self): config = TtsConfig.parse({"enabled": True, "speed": 3.0}) assert config.speed == 2.0 def test_parse_speed_at_min_ok(self): config = TtsConfig.parse({"enabled": True, "speed": 0.5}) assert config.speed == 0.5 def test_parse_speed_at_max_ok(self): config = TtsConfig.parse({"enabled": True, "speed": 2.0}) assert config.speed == 2.0 def test_parse_pitch_below_min_clamped(self): config = TtsConfig.parse({"enabled": True, "pitch": -20}) assert config.pitch == -12 def test_parse_pitch_above_max_clamped(self): config = TtsConfig.parse({"enabled": True, "pitch": 20}) assert config.pitch == 12 def test_parse_pitch_at_min_ok(self): config = TtsConfig.parse({"enabled": True, "pitch": -12}) assert config.pitch == -12 def test_parse_pitch_at_max_ok(self): config = TtsConfig.parse({"enabled": True, "pitch": 12}) assert config.pitch == 12 def test_parse_volume_below_min_clamped(self): config = TtsConfig.parse({"enabled": True, "volume": -0.5}) assert config.volume == 0.0 def test_parse_volume_above_max_clamped(self): config = TtsConfig.parse({"enabled": True, "volume": 1.5}) assert config.volume == 1.0 def test_parse_volume_at_min_ok(self): config = TtsConfig.parse({"enabled": True, "volume": 0.0}) assert config.volume == 0.0 def test_parse_volume_at_max_ok(self): config = TtsConfig.parse({"enabled": True, "volume": 1.0}) assert config.volume == 1.0 class TestTtsConfigParseAlignMode: """align_mode 解析测试.""" def test_parse_align_mode_subtitle(self): config = TtsConfig.parse({"enabled": True, "align_mode": "subtitle"}) assert config.align_mode == "subtitle" def test_parse_align_mode_full(self): config = TtsConfig.parse({"enabled": True, "align_mode": "full"}) assert config.align_mode == "full" def test_parse_align_mode_invalid_fallback(self): config = TtsConfig.parse({"enabled": True, "align_mode": "auto"}) assert config.align_mode == "full" def test_parse_align_mode_empty_fallback(self): config = TtsConfig.parse({"enabled": True, "align_mode": ""}) assert config.align_mode == "full" class TestTtsConfigParseOverlapMode: """overlap_mode 解析测试.""" def test_parse_overlap_mode_replace(self): config = TtsConfig.parse({"enabled": True, "overlap_mode": "replace"}) assert config.overlap_mode == "replace" def test_parse_overlap_mode_mix(self): config = TtsConfig.parse({"enabled": True, "overlap_mode": "mix"}) assert config.overlap_mode == "mix" def test_parse_overlap_mode_invalid_fallback(self): config = TtsConfig.parse({"enabled": True, "overlap_mode": "add"}) assert config.overlap_mode == "replace" def test_parse_overlap_mode_empty_fallback(self): config = TtsConfig.parse({"enabled": True, "overlap_mode": ""}) assert config.overlap_mode == "replace" class TestTtsConfigClamp: """_clamp 直接调用测试.""" def test_clamp_speed_low(self): config = TtsConfig(enabled=True, speed=0.1) config._clamp() assert config.speed == 0.5 def test_clamp_speed_high(self): config = TtsConfig(enabled=True, speed=5.0) config._clamp() assert config.speed == 2.0 def test_clamp_speed_normal_unchanged(self): config = TtsConfig(enabled=True, speed=1.2) config._clamp() assert config.speed == 1.2 def test_clamp_pitch_low(self): config = TtsConfig(enabled=True, pitch=-20) config._clamp() assert config.pitch == -12 def test_clamp_pitch_high(self): config = TtsConfig(enabled=True, pitch=20) config._clamp() assert config.pitch == 12 def test_clamp_pitch_normal_unchanged(self): config = TtsConfig(enabled=True, pitch=5.0) config._clamp() assert config.pitch == 5.0 def test_clamp_volume_low(self): config = TtsConfig(enabled=True, volume=-1.0) config._clamp() assert config.volume == 0.0 def test_clamp_volume_high(self): config = TtsConfig(enabled=True, volume=2.0) config._clamp() assert config.volume == 1.0 def test_clamp_volume_normal_unchanged(self): config = TtsConfig(enabled=True, volume=0.5) config._clamp() assert config.volume == 0.5