Compare commits

...

2 Commits

Author SHA1 Message Date
CI Bot e06112dfb6 style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 25s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m34s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m42s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m29s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m45s
AI Code Review / AI Code Review (pull_request) Successful in 2m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m45s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 4m41s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m27s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m14s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 9m21s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m35s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 20s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 41s
2026-07-29 11:01:02 +00:00
xiaoxia 72651cabc4 test(wave189): preset_bgm 预设BGM库 +44测
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 18s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 52s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m9s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m8s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 41s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m15s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
2026-07-29 18:49:33 +08:00
+299
View File
@@ -0,0 +1,299 @@
"""preset_bgm 预设BGM库单测."""
from dataclasses import FrozenInstanceError
import pytest
from domain.preset_bgm import (
BGM_STYLES,
PRESET_BGM_LIBRARY,
PresetBGM,
get_preset_bgm,
list_preset_bgm_by_style,
search_preset_bgm,
)
# ── PresetBGM dataclass ──────────────────────────────────────────────────────
class TestPresetBGM:
"""PresetBGM dataclass"""
def test_minimal_creation(self):
b = PresetBGM(id="test_001", name="测试音乐", style="upbeat", duration=60.0)
assert b.id == "test_001"
assert b.name == "测试音乐"
assert b.style == "upbeat"
assert b.duration == 60.0
assert b.artist == ""
assert b.description == ""
assert b.tags == []
assert b.audio_url == ""
def test_full_creation(self):
b = PresetBGM(
id="bgm_001",
name="阳光清晨",
style="upbeat",
duration=120.5,
artist="音乐人A",
description="轻快明亮的吉他",
tags=["轻快", "阳光"],
audio_url="https://cdn.example.com/bgm.mp3",
)
assert b.id == "bgm_001"
assert b.artist == "音乐人A"
assert b.description == "轻快明亮的吉他"
assert b.tags == ["轻快", "阳光"]
assert b.audio_url == "https://cdn.example.com/bgm.mp3"
def test_frozen_immutable(self):
b = PresetBGM(id="test", name="Test", style="relax", duration=100.0)
with pytest.raises(FrozenInstanceError):
b.name = "NewName"
def test_equality(self):
b1 = PresetBGM(id="same", name="同名", style="upbeat", duration=60.0)
b2 = PresetBGM(id="same", name="同名", style="upbeat", duration=60.0)
assert b1 == b2
def test_inequality(self):
b1 = PresetBGM(id="a", name="A", style="upbeat", duration=60.0)
b2 = PresetBGM(id="b", name="B", style="relax", duration=90.0)
assert b1 != b2
def test_not_hashable_due_to_list_tags(self):
# 包含 list 字段(tags)的 frozen dataclass 不可哈希
b = PresetBGM(id="test", name="Test", style="tech", duration=60.0)
with pytest.raises(TypeError):
hash(b)
# ── PRESET_BGM_LIBRARY 清单 ──────────────────────────────────────────────────
class TestPresetBGMLibrary:
"""预设BGM库清单"""
def test_not_empty(self):
assert len(PRESET_BGM_LIBRARY) > 0
def test_all_are_preset_bgm(self):
for bgm in PRESET_BGM_LIBRARY:
assert isinstance(bgm, PresetBGM)
def test_unique_ids(self):
ids = [b.id for b in PRESET_BGM_LIBRARY]
assert len(ids) == len(set(ids))
def test_all_have_required_fields(self):
for bgm in PRESET_BGM_LIBRARY:
assert bgm.id != ""
assert bgm.name != ""
assert bgm.style != ""
assert bgm.duration > 0
def test_upbeat_style_count(self):
upbeats = [b for b in PRESET_BGM_LIBRARY if b.style == "upbeat"]
assert len(upbeats) >= 3
def test_relax_style_count(self):
relax = [b for b in PRESET_BGM_LIBRARY if b.style == "relax"]
assert len(relax) >= 3
def test_tech_style_count(self):
tech = [b for b in PRESET_BGM_LIBRARY if b.style == "tech"]
assert len(tech) >= 2
def test_commerce_style_count(self):
commerce = [b for b in PRESET_BGM_LIBRARY if b.style == "commerce"]
assert len(commerce) >= 2
def test_sunny_morning_preset(self):
b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_upbeat_001")
assert b.name == "阳光清晨"
assert b.style == "upbeat"
assert b.duration == 120.0
assert "吉他" in b.description
assert "vlog" in b.tags
def test_quiet_time_preset(self):
b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_relax_001")
assert b.name == "静谧时光"
assert b.style == "relax"
assert b.duration == 180.0
def test_future_tech_preset(self):
b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_tech_001")
assert b.name == "未来科技"
assert b.style == "tech"
def test_all_durations_positive(self):
for bgm in PRESET_BGM_LIBRARY:
assert bgm.duration > 0
def test_all_tags_are_lists(self):
for bgm in PRESET_BGM_LIBRARY:
assert isinstance(bgm.tags, list)
# ── BGM_STYLES 风格字典 ─────────────────────────────────────────────────────
class TestBGMStyles:
"""BGM_STYLES 风格分类字典"""
def test_styles_exist(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_style_names_chinese(self):
assert BGM_STYLES["upbeat"] == "轻快"
assert BGM_STYLES["relax"] == "治愈"
assert BGM_STYLES["tech"] == "科技"
assert BGM_STYLES["commerce"] == "电商"
assert BGM_STYLES["emotional"] == "情感"
assert BGM_STYLES["cinematic"] == "电影"
def test_library_styles_are_defined(self):
# 库中的所有风格都应该在 BGM_STYLES 中有定义
styles_in_library = {b.style for b in PRESET_BGM_LIBRARY}
for style in styles_in_library:
assert style in BGM_STYLES, f"style {style} not defined in BGM_STYLES"
# ── get_preset_bgm ──────────────────────────────────────────────────────────
class TestGetPresetBGM:
"""get_preset_bgm 函数"""
def test_get_existing(self):
b = get_preset_bgm("bgm_upbeat_001")
assert b is not None
assert b.id == "bgm_upbeat_001"
assert b.name == "阳光清晨"
def test_get_relax(self):
b = get_preset_bgm("bgm_relax_002")
assert b is not None
assert b.style == "relax"
def test_get_nonexistent_returns_none(self):
b = get_preset_bgm("nonexistent_id")
assert b is None
def test_get_empty_string_returns_none(self):
b = get_preset_bgm("")
assert b is None
def test_returns_same_instance(self):
b1 = get_preset_bgm("bgm_upbeat_001")
b2 = get_preset_bgm("bgm_upbeat_001")
assert b1 is b2
# ── list_preset_bgm_by_style ─────────────────────────────────────────────────
class TestListPresetBGMByStyle:
"""list_preset_bgm_by_style 函数"""
def test_upbeat_style(self):
result = list_preset_bgm_by_style("upbeat")
assert len(result) >= 3
for b in result:
assert b.style == "upbeat"
def test_relax_style(self):
result = list_preset_bgm_by_style("relax")
assert len(result) >= 3
for b in result:
assert b.style == "relax"
def test_tech_style(self):
result = list_preset_bgm_by_style("tech")
assert len(result) >= 2
def test_commerce_style(self):
result = list_preset_bgm_by_style("commerce")
assert len(result) >= 2
def test_unknown_style_empty(self):
result = list_preset_bgm_by_style("nonexistent_style")
assert len(result) == 0
def test_empty_string_empty(self):
result = list_preset_bgm_by_style("")
assert len(result) == 0
def test_returns_new_list(self):
# 修改返回值不应影响原始列表
result = list_preset_bgm_by_style("upbeat")
result.clear()
assert len(list_preset_bgm_by_style("upbeat")) >= 3
# ── search_preset_bgm ────────────────────────────────────────────────────────
class TestSearchPresetBGM:
"""search_preset_bgm 函数"""
def test_search_by_name(self):
result = search_preset_bgm("阳光")
assert len(result) >= 1
assert any("阳光" in b.name for b in result)
def test_search_by_description(self):
result = search_preset_bgm("钢琴")
assert len(result) >= 1
# 应该匹配描述里有钢琴的
def test_search_by_tag(self):
result = search_preset_bgm("vlog")
assert len(result) >= 1
assert any("vlog" in b.tags for b in result)
def test_search_tech_keyword(self):
result = search_preset_bgm("科技")
assert len(result) >= 2
def test_search_case_insensitive(self):
r1 = search_preset_bgm("UPBEAT")
r2 = search_preset_bgm("upbeat")
assert len(r1) == len(r2)
def test_search_no_match(self):
result = search_preset_bgm("完全不存在的关键词_xyz123")
assert len(result) == 0
def test_search_empty_string(self):
# 空字符串应该匹配所有(因为 "" in any string 是 True
result = search_preset_bgm("")
assert len(result) == len(PRESET_BGM_LIBRARY)
def test_search_electronic(self):
result = search_preset_bgm("电子")
assert len(result) >= 2
def test_order_preserved(self):
# 搜索结果应该保持原列表顺序
result = search_preset_bgm("bgm")
ids = [b.id for b in result]
all_ids = [b.id for b in PRESET_BGM_LIBRARY]
# 验证相对顺序
pos_in_result = {bgm_id: i for i, bgm_id in enumerate(ids)}
prev_pos = -1
for bgm_id in all_ids:
if bgm_id in pos_in_result:
assert pos_in_result[bgm_id] > prev_pos
prev_pos = pos_in_result[bgm_id]
def test_search_partial_tag_match(self):
# 关键词是标签的子串也能匹配
result = search_preset_bgm("吉他")
assert len(result) >= 1