diff --git a/tests/unit/test_preset_bgm.py b/tests/unit/test_preset_bgm.py new file mode 100755 index 000000000..2120fcf7d --- /dev/null +++ b/tests/unit/test_preset_bgm.py @@ -0,0 +1,201 @@ +"""Preset BGM 预设背景音乐单元测试。""" + +import pytest + +from packages.domain.preset_bgm import ( + BGM_STYLES, + PRESET_BGM_LIBRARY, + PresetBGM, + get_preset_bgm, + list_preset_bgm_by_style, + search_preset_bgm, +) + + +class TestPresetBGMDataclass: + def test_creation_required_fields(self): + bgm = PresetBGM(id="test_001", name="Test BGM", style="upbeat", duration=120.0) + assert bgm.id == "test_001" + assert bgm.name == "Test BGM" + assert bgm.style == "upbeat" + assert bgm.duration == 120.0 + assert bgm.artist == "" + assert bgm.description == "" + assert bgm.tags == [] + assert bgm.audio_url == "" + + def test_creation_all_fields(self): + bgm = PresetBGM( + id="test_002", + name="Full BGM", + style="relax", + duration=180.5, + artist="Artist Name", + description="A test description", + tags=["tag1", "tag2"], + audio_url="https://cdn/test.mp3", + ) + assert bgm.artist == "Artist Name" + assert bgm.description == "A test description" + assert bgm.tags == ["tag1", "tag2"] + assert bgm.audio_url == "https://cdn/test.mp3" + + def test_frozen_immutable(self): + bgm = PresetBGM(id="t1", name="T", style="upbeat", duration=60.0) + with pytest.raises(Exception): # FrozenInstanceError + bgm.name = "new name" + + def test_equality(self): + bgm1 = PresetBGM(id="same", name="N", style="upbeat", duration=60.0) + bgm2 = PresetBGM(id="same", name="N", style="upbeat", duration=60.0) + assert bgm1 == bgm2 + + def test_inequality(self): + bgm1 = PresetBGM(id="a", name="A", style="upbeat", duration=60.0) + bgm2 = PresetBGM(id="b", name="B", style="upbeat", duration=60.0) + assert bgm1 != bgm2 + + def test_frozen_with_list_field_not_hashable(self): + # 包含 list 字段的 frozen dataclass 仍然不可哈希(list 不可哈希) + bgm = PresetBGM(id="h1", name="H", style="upbeat", duration=60.0, tags=["a"]) + with pytest.raises(TypeError, match="unhashable"): + hash(bgm) + + +class TestPresetBGMLibrary: + def test_library_not_empty(self): + assert len(PRESET_BGM_LIBRARY) > 0 + + def test_library_has_entries(self): + assert len(PRESET_BGM_LIBRARY) >= 10 + + def test_all_have_unique_ids(self): + ids = [bgm.id for bgm in PRESET_BGM_LIBRARY] + assert len(ids) == len(set(ids)) + + def test_all_have_valid_styles(self): + for bgm in PRESET_BGM_LIBRARY: + assert bgm.style in BGM_STYLES + + def test_all_have_positive_duration(self): + for bgm in PRESET_BGM_LIBRARY: + assert bgm.duration > 0 + + def test_all_have_non_empty_name(self): + for bgm in PRESET_BGM_LIBRARY: + assert bgm.name.strip() != "" + + +class TestBGMStyles: + def test_styles_dict_keys(self): + assert "upbeat" in BGM_STYLES + assert "relax" in BGM_STYLES + assert "tech" in BGM_STYLES + assert "commerce" in BGM_STYLES + assert "emotional" in BGM_STYLES + assert "cinematic" in BGM_STYLES + + def test_styles_have_chinese_names(self): + for key, value in BGM_STYLES.items(): + assert isinstance(value, str) + assert len(value) > 0 + + +class TestGetPresetBGM: + def test_get_existing(self): + bgm = get_preset_bgm("bgm_upbeat_001") + assert bgm is not None + assert bgm.id == "bgm_upbeat_001" + assert bgm.name == "阳光清晨" + assert bgm.style == "upbeat" + + def test_get_nonexistent(self): + assert get_preset_bgm("nonexistent_id") is None + + def test_get_empty_string(self): + assert get_preset_bgm("") is None + + def test_get_returns_same_object(self): + bgm1 = get_preset_bgm("bgm_relax_001") + bgm2 = get_preset_bgm("bgm_relax_001") + assert bgm1 is bgm2 # 同一实例(引用同一列表中的对象) + + +class TestListPresetBGMByStyle: + def test_list_upbeat(self): + results = list_preset_bgm_by_style("upbeat") + assert len(results) >= 3 + for bgm in results: + assert bgm.style == "upbeat" + + def test_list_relax(self): + results = list_preset_bgm_by_style("relax") + assert len(results) >= 3 + for bgm in results: + assert bgm.style == "relax" + + def test_list_tech(self): + results = list_preset_bgm_by_style("tech") + assert len(results) >= 2 + for bgm in results: + assert bgm.style == "tech" + + def test_list_commerce(self): + results = list_preset_bgm_by_style("commerce") + assert len(results) >= 2 + for bgm in results: + assert bgm.style == "commerce" + + def test_list_empty_style(self): + results = list_preset_bgm_by_style("nonexistent_style") + assert results == [] + + def test_list_preserves_order(self): + results = list_preset_bgm_by_style("upbeat") + ids = [b.id for b in results] + # 应该按照在列表中的出现顺序排列 + assert ids == sorted(ids, key=lambda x: PRESET_BGM_LIBRARY.index(get_preset_bgm(x))) + + +class TestSearchPresetBGM: + def test_search_by_name(self): + results = search_preset_bgm("阳光") + assert len(results) >= 1 + assert any("阳光" in b.name for b in results) + + def test_search_by_description(self): + results = search_preset_bgm("钢琴") + assert len(results) >= 1 + # 钢琴出现在名称或描述或标签中 + found = False + for b in results: + if "钢琴" in b.description or "钢琴" in b.name or "钢琴" in b.tags: + found = True + break + assert found + + def test_search_by_tag(self): + results = search_preset_bgm("科技") + assert len(results) >= 1 + found_tech = any(b.style == "tech" for b in results) + assert found_tech + + def test_search_case_insensitive(self): + results1 = search_preset_bgm("Tech") + results2 = search_preset_bgm("tech") + assert len(results1) == len(results2) + + def test_search_no_match(self): + results = search_preset_bgm("zzzzzzzzzzz_nonexistent_keyword") + assert results == [] + + def test_search_empty_keyword(self): + # 空字符串应该匹配所有(因为空字符串 in 任何字符串都是 True) + results = search_preset_bgm("") + assert len(results) == len(PRESET_BGM_LIBRARY) + + def test_search_no_duplicates(self): + # 确保同一个 BGM 不会出现多次 + results = search_preset_bgm("电子") + ids = [b.id for b in results] + assert len(ids) == len(set(ids))