"""TTS 配音配置领域模型单元测试.""" from __future__ import annotations from packages.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" class TestTtsConfigParse: """parse 方法测试.""" def test_parse_none_returns_default(self): config = TtsConfig.parse(None) assert config.enabled is False def test_parse_empty_dict_returns_default(self): config = TtsConfig.parse({}) assert config.enabled is False def test_parse_not_dict_returns_default(self): config = TtsConfig.parse("invalid") assert config.enabled is False config2 = TtsConfig.parse(123) assert config2.enabled is False config3 = TtsConfig.parse([]) assert config3.enabled is False def test_parse_enabled_false_ignores_other_fields(self): data = { "enabled": False, "voice_id": "test_voice", "speed": 2.0, "text": "hello", } config = TtsConfig.parse(data) assert config.enabled is False assert config.voice_id == "" assert config.speed == 1.0 def test_parse_basic_enabled(self): data = {"enabled": True, "voice_id": "voice_001"} config = TtsConfig.parse(data) assert config.enabled is True assert config.voice_id == "voice_001" assert config.speed == 1.0 assert config.pitch == 0.0 assert config.volume == 0.8 def test_parse_full_config(self): data = { "enabled": True, "voice_id": "voice_001", "speed": 1.5, "pitch": 2.0, "volume": 0.9, "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.0 assert config.volume == 0.9 assert config.text == "测试配音文本" assert config.align_mode == "subtitle" assert config.overlap_mode == "mix" def test_parse_enabled_non_bool_fallback(self): data = {"enabled": "true", "voice_id": "v1"} config = TtsConfig.parse(data) assert config.enabled is False def test_parse_voice_id_non_string_fallback(self): data = {"enabled": True, "voice_id": 123} config = TtsConfig.parse(data) assert config.voice_id == "" def test_parse_speed_non_numeric_fallback(self): data = {"enabled": True, "speed": "fast"} config = TtsConfig.parse(data) assert config.speed == 1.0 def test_parse_pitch_non_numeric_fallback(self): data = {"enabled": True, "pitch": "high"} config = TtsConfig.parse(data) assert config.pitch == 0.0 def test_parse_volume_non_numeric_fallback(self): data = {"enabled": True, "volume": "loud"} config = TtsConfig.parse(data) assert config.volume == 0.8 def test_parse_text_non_string_fallback(self): data = {"enabled": True, "text": 12345} config = TtsConfig.parse(data) assert config.text == "" def test_parse_align_mode_invalid_fallback(self): data = {"enabled": True, "align_mode": "invalid"} config = TtsConfig.parse(data) assert config.align_mode == "full" def test_parse_overlap_mode_invalid_fallback(self): data = {"enabled": True, "overlap_mode": "invalid"} config = TtsConfig.parse(data) assert config.overlap_mode == "replace" class TestTtsConfigClamp: """边界钳制测试.""" def test_speed_below_min_clamped(self): data = {"enabled": True, "speed": 0.1} config = TtsConfig.parse(data) assert config.speed == 0.5 def test_speed_above_max_clamped(self): data = {"enabled": True, "speed": 3.0} config = TtsConfig.parse(data) assert config.speed == 2.0 def test_speed_at_min_ok(self): data = {"enabled": True, "speed": 0.5} config = TtsConfig.parse(data) assert config.speed == 0.5 def test_speed_at_max_ok(self): data = {"enabled": True, "speed": 2.0} config = TtsConfig.parse(data) assert config.speed == 2.0 def test_pitch_below_min_clamped(self): data = {"enabled": True, "pitch": -20} config = TtsConfig.parse(data) assert config.pitch == -12 def test_pitch_above_max_clamped(self): data = {"enabled": True, "pitch": 20} config = TtsConfig.parse(data) assert config.pitch == 12 def test_pitch_at_min_ok(self): data = {"enabled": True, "pitch": -12} config = TtsConfig.parse(data) assert config.pitch == -12 def test_pitch_at_max_ok(self): data = {"enabled": True, "pitch": 12} config = TtsConfig.parse(data) assert config.pitch == 12 def test_volume_below_min_clamped(self): data = {"enabled": True, "volume": -0.5} config = TtsConfig.parse(data) assert config.volume == 0.0 def test_volume_above_max_clamped(self): data = {"enabled": True, "volume": 2.0} config = TtsConfig.parse(data) assert config.volume == 1.0 def test_volume_at_min_ok(self): data = {"enabled": True, "volume": 0.0} config = TtsConfig.parse(data) assert config.volume == 0.0 def test_volume_at_max_ok(self): data = {"enabled": True, "volume": 1.0} config = TtsConfig.parse(data) assert config.volume == 1.0 def test_int_speed_converted_to_float(self): data = {"enabled": True, "speed": 1} config = TtsConfig.parse(data) assert isinstance(config.speed, float) assert config.speed == 1.0 def test_int_pitch_converted_to_float(self): data = {"enabled": True, "pitch": 5} config = TtsConfig.parse(data) assert isinstance(config.pitch, float) assert config.pitch == 5.0 def test_int_volume_converted_to_float(self): data = {"enabled": True, "volume": 1} config = TtsConfig.parse(data) assert isinstance(config.volume, float) assert config.volume == 1.0 class TestTtsConfigTextEdge: """文本字段边界测试.""" def test_long_text_preserved(self): long_text = "配音文本" * 500 data = {"enabled": True, "voice_id": "v1", "text": long_text} config = TtsConfig.parse(data) assert config.text == long_text assert len(config.text) == 2000 def test_unicode_text_preserved(self): data = {"enabled": True, "voice_id": "v1", "text": "こんにちは世界🎵"} config = TtsConfig.parse(data) assert config.text == "こんにちは世界🎵" def test_special_chars_text_preserved(self): data = {"enabled": True, "voice_id": "v1", "text": "line1\nline2\t tab <>&\"'"} config = TtsConfig.parse(data) assert config.text == "line1\nline2\t tab <>&\"'" def test_empty_text_ok(self): data = {"enabled": True, "voice_id": "v1", "text": ""} config = TtsConfig.parse(data) assert config.text == "" def test_text_none_fallback(self): data = {"enabled": True, "voice_id": "v1", "text": None} config = TtsConfig.parse(data) assert config.text == "" class TestTtsConfigVoiceIdEdge: """voice_id 边界测试.""" def test_very_long_voice_id_preserved(self): long_id = "voice_" + "x" * 200 data = {"enabled": True, "voice_id": long_id} config = TtsConfig.parse(data) assert config.voice_id == long_id def test_voice_id_empty_string_ok(self): data = {"enabled": True, "voice_id": ""} config = TtsConfig.parse(data) assert config.voice_id == "" def test_voice_id_unicode_ok(self): data = {"enabled": True, "voice_id": "音色_测试_001"} config = TtsConfig.parse(data) assert config.voice_id == "音色_测试_001" class TestTtsConfigClampEdge: """钳制边界附近值测试.""" def test_speed_just_below_min_clamped(self): data = {"enabled": True, "speed": 0.499} config = TtsConfig.parse(data) assert config.speed == 0.5 def test_speed_just_above_max_clamped(self): data = {"enabled": True, "speed": 2.001} config = TtsConfig.parse(data) assert config.speed == 2.0 def test_pitch_just_below_min_clamped(self): data = {"enabled": True, "pitch": -12.1} config = TtsConfig.parse(data) assert config.pitch == -12 def test_pitch_just_above_max_clamped(self): data = {"enabled": True, "pitch": 12.1} config = TtsConfig.parse(data) assert config.pitch == 12 def test_volume_just_below_min_clamped(self): data = {"enabled": True, "volume": -0.001} config = TtsConfig.parse(data) assert config.volume == 0.0 def test_volume_just_above_max_clamped(self): data = {"enabled": True, "volume": 1.001} config = TtsConfig.parse(data) assert config.volume == 1.0 def test_direct_construct_clamp_speed(self): config = TtsConfig(enabled=True, speed=0.1) config._clamp() assert config.speed == 0.5 def test_direct_construct_clamp_pitch_volume(self): config = TtsConfig(enabled=True, pitch=-20, volume=2.0) config._clamp() assert config.pitch == -12 assert config.volume == 1.0 class TestTtsConfigAlignOverlapEdge: """对齐与叠加模式边界.""" def test_align_mode_empty_string_fallback(self): data = {"enabled": True, "align_mode": ""} config = TtsConfig.parse(data) assert config.align_mode == "full" def test_overlap_mode_empty_string_fallback(self): data = {"enabled": True, "overlap_mode": ""} config = TtsConfig.parse(data) assert config.overlap_mode == "replace" def test_align_mode_case_sensitive(self): data = {"enabled": True, "align_mode": "SUBTITLE"} config = TtsConfig.parse(data) assert config.align_mode == "full" class TestTtsConfigEquality: """相等性与独立性测试.""" def test_same_config_equal(self): c1 = TtsConfig(enabled=True, voice_id="v1", speed=1.5) c2 = TtsConfig(enabled=True, voice_id="v1", speed=1.5) assert c1 == c2 def test_different_config_not_equal(self): c1 = TtsConfig(enabled=True, voice_id="v1") c2 = TtsConfig(enabled=True, voice_id="v2") assert c1 != c2 def test_modify_one_does_not_affect_other(self): c1 = TtsConfig(enabled=True, voice_id="v1") c2 = TtsConfig(enabled=True, voice_id="v1") c2.speed = 2.0 assert c1.speed == 1.0 assert c1 != c2