From aa3d017b13c03224e60dae4729ed103eac64a65e Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 29 Jul 2026 18:37:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?test(wave185):=20audio=5Ftrack=5Fconfig=20?= =?UTF-8?q?=E5=A4=9A=E8=BD=A8=E9=81=93=E9=9F=B3=E9=A2=91=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=20+80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/domain/test_audio_track_config.py | 522 +++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100755 tests/unit/domain/test_audio_track_config.py diff --git a/tests/unit/domain/test_audio_track_config.py b/tests/unit/domain/test_audio_track_config.py new file mode 100755 index 000000000..831ca3908 --- /dev/null +++ b/tests/unit/domain/test_audio_track_config.py @@ -0,0 +1,522 @@ +"""audio_track_config 多轨道音频配置单测.""" + +import pytest + +from domain.audio_track_config import ( + ALLOWED_AUDIO_EXTENSIONS, + DEFAULT_VOLUMES, + MAX_AUDIO_TRACKS, + TRACK_TYPE_AMBIENT, + TRACK_TYPE_BGM, + TRACK_TYPE_MAIN, + TRACK_TYPE_SFX, + TRACK_TYPE_VOICEOVER, + AudioTrack, + MultiTrackMixConfig, + clamp_volume, + is_valid_audio_extension, +) + +# ── 常量测试 ────────────────────────────────────────────────────────────────── + + +class TestConstants: + """模块常量""" + + def test_track_type_constants(self): + assert TRACK_TYPE_MAIN == "main" + assert TRACK_TYPE_BGM == "bgm" + assert TRACK_TYPE_VOICEOVER == "voiceover" + assert TRACK_TYPE_SFX == "sfx" + assert TRACK_TYPE_AMBIENT == "ambient" + + def test_max_tracks(self): + assert MAX_AUDIO_TRACKS == 8 + + def test_default_volumes(self): + assert DEFAULT_VOLUMES[TRACK_TYPE_MAIN] == 1.0 + assert DEFAULT_VOLUMES[TRACK_TYPE_BGM] == 0.3 + assert DEFAULT_VOLUMES[TRACK_TYPE_VOICEOVER] == 1.0 + assert DEFAULT_VOLUMES[TRACK_TYPE_SFX] == 0.7 + assert DEFAULT_VOLUMES[TRACK_TYPE_AMBIENT] == 0.2 + + def test_allowed_extensions(self): + assert ".mp3" in ALLOWED_AUDIO_EXTENSIONS + assert ".wav" in ALLOWED_AUDIO_EXTENSIONS + assert ".aac" in ALLOWED_AUDIO_EXTENSIONS + assert ".ogg" in ALLOWED_AUDIO_EXTENSIONS + assert ".flac" in ALLOWED_AUDIO_EXTENSIONS + assert ".m4a" in ALLOWED_AUDIO_EXTENSIONS + assert ".wma" in ALLOWED_AUDIO_EXTENSIONS + + +# ── AudioTrack ─────────────────────────────────────────────────────────────── + + +class TestAudioTrackDefaults: + """AudioTrack 默认值""" + + def test_default_values(self): + t = AudioTrack() + assert t.track_id == "" + assert t.track_type == TRACK_TYPE_SFX + assert t.audio_path == "" + assert t.volume == 1.0 + assert t.fade_in == 0.0 + assert t.fade_out == 0.0 + assert t.start_time == 0.0 + assert t.duration == 0.0 + assert t.enabled is True + + def test_custom_track(self): + t = AudioTrack( + track_id="bgm_001", + track_type=TRACK_TYPE_BGM, + audio_path="/music/bgm.mp3", + volume=0.5, + fade_in=1.5, + fade_out=2.0, + start_time=3.0, + duration=30.0, + enabled=False, + ) + assert t.track_id == "bgm_001" + assert t.track_type == TRACK_TYPE_BGM + assert t.audio_path == "/music/bgm.mp3" + assert t.volume == 0.5 + assert t.fade_in == 1.5 + assert t.start_time == 3.0 + assert t.duration == 30.0 + assert t.enabled is False + + +class TestAudioTrackFromDict: + """AudioTrack.from_dict""" + + def test_empty_dict(self): + t = AudioTrack.from_dict({}) + assert t.track_type == TRACK_TYPE_SFX + assert t.audio_path == "" + assert t.volume == DEFAULT_VOLUMES[TRACK_TYPE_SFX] + assert t.enabled is True + + def test_full_dict(self): + t = AudioTrack.from_dict( + { + "track_id": "t1", + "track_type": "bgm", + "audio_path": "/a.mp3", + "volume": 0.8, + "fade_in": 1.0, + "fade_out": 2.0, + "start_time": 5.0, + "duration": 60.0, + "enabled": True, + } + ) + assert t.track_id == "t1" + assert t.track_type == "bgm" + assert t.volume == 0.8 + assert t.fade_in == 1.0 + assert t.duration == 60.0 + + def test_volume_clamped_to_zero(self): + t = AudioTrack.from_dict({"volume": -0.5}) + assert t.volume == 0.0 + + def test_volume_clamped_to_two(self): + t = AudioTrack.from_dict({"volume": 3.0}) + assert t.volume == 2.0 + + def test_invalid_volume_falls_back_to_default(self): + t = AudioTrack.from_dict({"track_type": "bgm", "volume": "abc"}) + assert t.volume == DEFAULT_VOLUMES[TRACK_TYPE_BGM] + + def test_invalid_fade_in_falls_back(self): + t = AudioTrack.from_dict({"fade_in": "bad"}) + assert t.fade_in == 0.0 + + def test_negative_fade_in_clamped(self): + t = AudioTrack.from_dict({"fade_in": -1.0}) + assert t.fade_in == 0.0 + + def test_invalid_fade_out_falls_back(self): + t = AudioTrack.from_dict({"fade_out": None}) + assert t.fade_out == 0.0 + + def test_negative_start_time_clamped(self): + t = AudioTrack.from_dict({"start_time": -5.0}) + assert t.start_time == 0.0 + + def test_invalid_duration_falls_back(self): + t = AudioTrack.from_dict({"duration": "long"}) + assert t.duration == 0.0 + + def test_bgm_default_volume(self): + t = AudioTrack.from_dict({"track_type": "bgm"}) + assert t.volume == 0.3 + + def test_main_default_volume(self): + t = AudioTrack.from_dict({"track_type": "main"}) + assert t.volume == 1.0 + + def test_voiceover_default_volume(self): + t = AudioTrack.from_dict({"track_type": "voiceover"}) + assert t.volume == 1.0 + + def test_ambient_default_volume(self): + t = AudioTrack.from_dict({"track_type": "ambient"}) + assert t.volume == 0.2 + + def test_unknown_type_default_volume(self): + t = AudioTrack.from_dict({"track_type": "unknown_type"}) + assert t.volume == 1.0 + + def test_enabled_false(self): + t = AudioTrack.from_dict({"enabled": False}) + assert t.enabled is False + + +class TestAudioTrackValidate: + """AudioTrack.validate""" + + def test_empty_path_invalid(self): + t = AudioTrack(audio_path="") + valid, msg = t.validate() + assert valid is False + assert "audio_path" in msg + + def test_valid_track(self): + t = AudioTrack(audio_path="/a.mp3", volume=0.5) + valid, msg = t.validate() + assert valid is True + assert msg == "" + + def test_volume_below_zero_invalid(self): + t = AudioTrack(audio_path="/a.mp3", volume=-0.1) + valid, msg = t.validate() + assert valid is False + assert "volume" in msg + + def test_volume_above_two_invalid(self): + t = AudioTrack(audio_path="/a.mp3", volume=2.1) + valid, msg = t.validate() + assert valid is False + assert "volume" in msg + + def test_volume_zero_valid(self): + t = AudioTrack(audio_path="/a.mp3", volume=0.0) + valid, _ = t.validate() + assert valid is True + + def test_volume_two_valid(self): + t = AudioTrack(audio_path="/a.mp3", volume=2.0) + valid, _ = t.validate() + assert valid is True + + def test_negative_fade_in_invalid(self): + t = AudioTrack(audio_path="/a.mp3", fade_in=-1.0) + valid, msg = t.validate() + assert valid is False + assert "fade_in" in msg + + def test_negative_fade_out_invalid(self): + t = AudioTrack(audio_path="/a.mp3", fade_out=-1.0) + valid, msg = t.validate() + assert valid is False + assert "fade_out" in msg + + def test_negative_start_time_invalid(self): + t = AudioTrack(audio_path="/a.mp3", start_time=-0.5) + valid, msg = t.validate() + assert valid is False + assert "start_time" in msg + + def test_negative_duration_invalid(self): + t = AudioTrack(audio_path="/a.mp3", duration=-1.0) + valid, msg = t.validate() + assert valid is False + assert "duration" in msg + + +class TestAudioTrackIsEffective: + """AudioTrack.is_effective 属性""" + + def test_enabled_with_path_effective(self): + t = AudioTrack(audio_path="/a.mp3", enabled=True) + assert t.is_effective is True + + def test_disabled_not_effective(self): + t = AudioTrack(audio_path="/a.mp3", enabled=False) + assert t.is_effective is False + + def test_no_path_not_effective(self): + t = AudioTrack(audio_path="", enabled=True) + assert t.is_effective is False + + def test_disabled_no_path_not_effective(self): + t = AudioTrack(audio_path="", enabled=False) + assert t.is_effective is False + + +# ── MultiTrackMixConfig ────────────────────────────────────────────────────── + + +class TestMultiTrackMixConfigDefaults: + """MultiTrackMixConfig 默认值""" + + def test_default_values(self): + c = MultiTrackMixConfig() + assert c.tracks == [] + assert c.master_volume == 1.0 + assert c.normalize is True + assert c.max_output_volume == 1.5 + + def test_custom_config(self): + t1 = AudioTrack(track_id="t1", audio_path="/a.mp3") + c = MultiTrackMixConfig( + tracks=[t1], + master_volume=0.8, + normalize=False, + max_output_volume=2.0, + ) + assert len(c.tracks) == 1 + assert c.master_volume == 0.8 + assert c.normalize is False + assert c.max_output_volume == 2.0 + + +class TestMultiTrackFromConfigDict: + """MultiTrackMixConfig.from_config_dict""" + + def test_none_returns_default(self): + c = MultiTrackMixConfig.from_config_dict(None) + assert len(c.tracks) == 0 + assert c.master_volume == 1.0 + + def test_empty_dict_returns_default(self): + c = MultiTrackMixConfig.from_config_dict({}) + assert len(c.tracks) == 0 + + def test_non_dict_returns_default(self): + c = MultiTrackMixConfig.from_config_dict("not a dict") + assert len(c.tracks) == 0 + + def test_single_track(self): + c = MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "t1", "track_type": "bgm", "audio_path": "/bgm.mp3", "volume": 0.5}, + ], + } + ) + assert len(c.tracks) == 1 + assert c.tracks[0].track_id == "t1" + assert c.tracks[0].volume == 0.5 + + def test_multiple_tracks(self): + c = MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "t1", "track_type": "main", "audio_path": "/main.wav"}, + {"track_id": "t2", "track_type": "bgm", "audio_path": "/bgm.mp3"}, + {"track_id": "t3", "track_type": "sfx", "audio_path": "/sfx.wav"}, + ], + } + ) + assert len(c.tracks) == 3 + assert c.tracks[0].track_type == "main" + assert c.tracks[1].track_type == "bgm" + assert c.tracks[2].track_type == "sfx" + + def test_skip_disabled_tracks(self): + c = MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "t1", "audio_path": "/a.mp3", "enabled": True}, + {"track_id": "t2", "audio_path": "/b.mp3", "enabled": False}, + {"track_id": "t3", "audio_path": "/c.mp3"}, + ], + } + ) + assert len(c.tracks) == 2 + ids = [t.track_id for t in c.tracks] + assert "t1" in ids + assert "t2" not in ids + assert "t3" in ids + + def test_skip_no_path_tracks(self): + c = MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "t1", "audio_path": "/a.mp3"}, + {"track_id": "t2", "audio_path": ""}, + {"track_id": "t3"}, + ], + } + ) + assert len(c.tracks) == 1 + assert c.tracks[0].track_id == "t1" + + def test_skip_non_dict_tracks(self): + c = MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "t1", "audio_path": "/a.mp3"}, + "not a dict", + 123, + None, + ], + } + ) + assert len(c.tracks) == 1 + + def test_master_volume_clamped(self): + c = MultiTrackMixConfig.from_config_dict({"master_volume": 3.0}) + assert c.master_volume == 2.0 + + def test_master_volume_negative_clamped(self): + c = MultiTrackMixConfig.from_config_dict({"master_volume": -1.0}) + assert c.master_volume == 0.0 + + def test_invalid_master_volume_falls_back(self): + c = MultiTrackMixConfig.from_config_dict({"master_volume": "high"}) + assert c.master_volume == 1.0 + + def test_normalize_false(self): + c = MultiTrackMixConfig.from_config_dict({"normalize": False}) + assert c.normalize is False + + def test_max_output_volume_custom(self): + c = MultiTrackMixConfig.from_config_dict({"max_output_volume": 2.0}) + assert c.max_output_volume == 2.0 + + def test_invalid_max_output_volume_falls_back(self): + c = MultiTrackMixConfig.from_config_dict({"max_output_volume": "big"}) + assert c.max_output_volume == 1.5 + + def test_tracks_not_list_ignored(self): + c = MultiTrackMixConfig.from_config_dict({"tracks": "not a list"}) + assert len(c.tracks) == 0 + + +class TestMultiTrackProperties: + """MultiTrackMixConfig 属性方法""" + + def _make_config(self): + return MultiTrackMixConfig.from_config_dict( + { + "tracks": [ + {"track_id": "m1", "track_type": "main", "audio_path": "/m.wav"}, + {"track_id": "b1", "track_type": "bgm", "audio_path": "/b1.mp3"}, + {"track_id": "b2", "track_type": "bgm", "audio_path": "/b2.mp3", "enabled": False}, + {"track_id": "s1", "track_type": "sfx", "audio_path": "/s.wav"}, + {"track_id": "x", "track_type": "ambient", "audio_path": ""}, + ], + } + ) + + def test_has_effect_true(self): + c = self._make_config() + assert c.has_effect is True + + def test_has_effect_false(self): + c = MultiTrackMixConfig() + assert c.has_effect is False + + def test_effective_track_count(self): + c = self._make_config() + # m1 + b1 + s1 = 3个有效(b2禁用,x无路径) + assert c.effective_track_count == 3 + + def test_main_tracks(self): + c = self._make_config() + mains = c.main_tracks + assert len(mains) == 1 + assert mains[0].track_id == "m1" + + def test_bgm_tracks(self): + c = self._make_config() + bgms = c.bgm_tracks + assert len(bgms) == 1 # 只有b1有效 + assert bgms[0].track_id == "b1" + + def test_empty_tracks(self): + c = MultiTrackMixConfig() + assert c.effective_track_count == 0 + assert c.main_tracks == [] + assert c.bgm_tracks == [] + + +# ── 工具函数 ───────────────────────────────────────────────────────────────── + + +class TestIsValidAudioExtension: + """is_valid_audio_extension 函数""" + + def test_mp3(self): + assert is_valid_audio_extension("song.mp3") is True + + def test_wav(self): + assert is_valid_audio_extension("sound.wav") is True + + def test_aac(self): + assert is_valid_audio_extension("audio.aac") is True + + def test_ogg(self): + assert is_valid_audio_extension("music.ogg") is True + + def test_flac(self): + assert is_valid_audio_extension("lossless.flac") is True + + def test_m4a(self): + assert is_valid_audio_extension("apple.m4a") is True + + def test_wma(self): + assert is_valid_audio_extension("windows.wma") is True + + def test_uppercase_extension(self): + assert is_valid_audio_extension("SONG.MP3") is True + + def test_mixed_case_extension(self): + assert is_valid_audio_extension("song.Mp3") is True + + def test_mp4_not_valid(self): + assert is_valid_audio_extension("video.mp4") is False + + def test_txt_not_valid(self): + assert is_valid_audio_extension("notes.txt") is False + + def test_no_extension(self): + assert is_valid_audio_extension("README") is False + + def test_full_path(self): + assert is_valid_audio_extension("/home/user/music/song.mp3") is True + + +class TestClampVolume: + """clamp_volume 函数""" + + def test_within_range(self): + assert clamp_volume(0.5) == 0.5 + + def test_exact_min(self): + assert clamp_volume(0.0) == 0.0 + + def test_exact_max(self): + assert clamp_volume(2.0) == 2.0 + + def test_below_min(self): + assert clamp_volume(-1.0) == 0.0 + + def test_above_max(self): + assert clamp_volume(3.0) == 2.0 + + def test_custom_bounds(self): + assert clamp_volume(5.0, min_vol=1.0, max_vol=10.0) == 5.0 + + def test_custom_below_min(self): + assert clamp_volume(0.5, min_vol=1.0) == 1.0 + + def test_custom_above_max(self): + assert clamp_volume(15.0, max_vol=10.0) == 10.0 -- 2.54.0 From c368373d05a28571ffef9487e574b9d7532a20c8 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Wed, 29 Jul 2026 10:43:59 +0000 Subject: [PATCH 2/2] style: auto-format with black + isort + prettier [skip ci-format-check] --- tests/unit/domain/test_audio_track_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/domain/test_audio_track_config.py b/tests/unit/domain/test_audio_track_config.py index 831ca3908..2d356ae40 100755 --- a/tests/unit/domain/test_audio_track_config.py +++ b/tests/unit/domain/test_audio_track_config.py @@ -1,7 +1,6 @@ """audio_track_config 多轨道音频配置单测.""" import pytest - from domain.audio_track_config import ( ALLOWED_AUDIO_EXTENSIONS, DEFAULT_VOLUMES, -- 2.54.0