Files
xiaoxia-saas/tests/unit/test_voice_presets.py
xiaoxia 002384bad4
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
test(wave148): voice_presets 单测补全 +59 (#1071)
2026-07-28 12:24:10 +08:00

403 lines
13 KiB
Python
Executable File

"""voice_presets 配音音色预设单元测试."""
from __future__ import annotations
import pytest
from packages.domain.voice_presets import (
MOCK_VOICES,
VoiceGender,
VoicePreset,
VoiceStyle,
get_default_voice,
get_voice,
list_voices,
)
# ── VoiceGender 枚举测试 ─────────────────────────────────────────────────────
class TestVoiceGender:
"""VoiceGender 枚举测试."""
def test_male(self):
"""男声."""
assert VoiceGender.MALE.value == "male"
def test_female(self):
"""女声."""
assert VoiceGender.FEMALE.value == "female"
def test_child(self):
"""童声."""
assert VoiceGender.CHILD.value == "child"
def test_count(self):
"""共3种性别."""
assert len(VoiceGender) == 3
def test_is_str_enum(self):
"""可与字符串比较."""
assert VoiceGender.FEMALE == "female"
# ── VoiceStyle 枚举测试 ──────────────────────────────────────────────────────
class TestVoiceStyle:
"""VoiceStyle 枚举测试."""
def test_stable(self):
"""沉稳."""
assert VoiceStyle.STABLE.value == "stable"
def test_lively(self):
"""活泼."""
assert VoiceStyle.LIVELY.value == "lively"
def test_customer_service(self):
"""客服."""
assert VoiceStyle.CUSTOMER_SERVICE.value == "customer_service"
def test_narration(self):
"""旁白."""
assert VoiceStyle.NARRATION.value == "narration"
def test_news(self):
"""新闻."""
assert VoiceStyle.NEWS.value == "news"
def test_story(self):
"""故事."""
assert VoiceStyle.STORY.value == "story"
def test_count(self):
"""共6种风格."""
assert len(VoiceStyle) == 6
# ── VoicePreset 数据类测试 ───────────────────────────────────────────────────
class TestVoicePreset:
"""VoicePreset 数据类测试."""
def test_minimal_creation(self):
"""最小化创建(必填字段)."""
v = VoicePreset(voice_id="v1", name="测试音色")
assert v.voice_id == "v1"
assert v.name == "测试音色"
def test_default_values(self):
"""默认值正确."""
v = VoicePreset(voice_id="v1", name="T")
assert v.gender == VoiceGender.FEMALE
assert v.style == VoiceStyle.NARRATION
assert v.description == ""
assert v.provider == "mock"
assert v.provider_voice_id == ""
assert v.default_speed == 1.0
assert v.default_pitch == 0.0
assert v.sample_rate == 22050
assert v.language == "zh-CN"
def test_full_creation(self):
"""完整字段创建."""
v = VoicePreset(
voice_id="voice_full",
name="完整音色",
gender=VoiceGender.MALE,
style=VoiceStyle.NEWS,
description="测试描述",
provider="aliyun",
provider_voice_id="ali_001",
default_speed=0.9,
default_pitch=1.5,
sample_rate=44100,
language="en-US",
)
assert v.voice_id == "voice_full"
assert v.name == "完整音色"
assert v.gender == VoiceGender.MALE
assert v.style == VoiceStyle.NEWS
assert v.description == "测试描述"
assert v.provider == "aliyun"
assert v.provider_voice_id == "ali_001"
assert v.default_speed == 0.9
assert v.default_pitch == 1.5
assert v.sample_rate == 44100
assert v.language == "en-US"
# ── MOCK_VOICES 列表测试 ─────────────────────────────────────────────────────
class TestMockVoices:
"""MOCK_VOICES 预设列表测试."""
def test_not_empty(self):
"""列表不为空."""
assert len(MOCK_VOICES) > 0
def test_count(self):
"""共8个Mock音色."""
assert len(MOCK_VOICES) == 8
def test_all_have_voice_id(self):
"""每个音色都有voice_id."""
for v in MOCK_VOICES:
assert v.voice_id, f"音色缺少voice_id: {v}"
def test_all_have_name(self):
"""每个音色都有name."""
for v in MOCK_VOICES:
assert v.name, f"{v.voice_id} 缺少name"
def test_voice_ids_unique(self):
"""voice_id唯一."""
ids = [v.voice_id for v in MOCK_VOICES]
assert len(ids) == len(set(ids)), f"重复ID: {[i for i in ids if ids.count(i) > 1]}"
def test_all_mock_provider(self):
"""都是mock供应商."""
for v in MOCK_VOICES:
assert v.provider == "mock", f"{v.voice_id} provider不是mock"
def test_all_chinese(self):
"""都是中文语言."""
for v in MOCK_VOICES:
assert v.language == "zh-CN"
def test_sample_rate_consistent(self):
"""采样率一致为22050."""
for v in MOCK_VOICES:
assert v.sample_rate == 22050
def test_gender_distribution(self):
"""性别分布:至少有男/女/童声."""
genders = {v.gender for v in MOCK_VOICES}
assert VoiceGender.MALE in genders
assert VoiceGender.FEMALE in genders
assert VoiceGender.CHILD in genders
def test_style_coverage(self):
"""覆盖多种风格."""
styles = {v.style for v in MOCK_VOICES}
assert len(styles) >= 4
def test_specific_voices_exist(self):
"""特定音色存在."""
ids = {v.voice_id for v in MOCK_VOICES}
assert "female_warm" in ids
assert "male_stable" in ids
assert "female_lively" in ids
assert "child_cute" in ids
def test_default_speed_positive(self):
"""语速都大于0."""
for v in MOCK_VOICES:
assert v.default_speed > 0, f"{v.voice_id}语速<=0"
def test_descriptions_not_empty(self):
"""都有描述."""
for v in MOCK_VOICES:
assert v.description, f"{v.voice_id}缺少描述"
assert len(v.description) > 5
# ── get_voice 测试 ───────────────────────────────────────────────────────────
class TestGetVoice:
"""get_voice 函数测试."""
def test_get_existing_voice(self):
"""获取存在的音色."""
v = get_voice("female_warm")
assert v is not None
assert v.voice_id == "female_warm"
assert v.name == "温暖女声"
def test_get_male_stable(self):
"""获取沉稳男声."""
v = get_voice("male_stable")
assert v is not None
assert v.gender == VoiceGender.MALE
assert v.style == VoiceStyle.STABLE
def test_get_nonexistent_voice(self):
"""获取不存在的音色返回None."""
assert get_voice("nonexistent") is None
def test_get_empty_string(self):
"""空字符串返回None."""
assert get_voice("") is None
def test_mock_provider(self):
"""指定mock provider."""
v = get_voice("female_warm", provider="mock")
assert v is not None
assert v.provider == "mock"
def test_other_provider_returns_none(self):
"""其他provider返回None."""
assert get_voice("female_warm", provider="aliyun") is None
assert get_voice("female_warm", provider="xunfei") is None
def test_returns_same_instance(self):
"""返回同一个对象引用."""
v1 = get_voice("female_warm")
v2 = get_voice("female_warm")
assert v1 is v2
# ── list_voices 测试 ─────────────────────────────────────────────────────────
class TestListVoices:
"""list_voices 筛选函数测试."""
def test_no_filter_returns_all(self):
"""无筛选返回全部."""
result = list_voices()
assert len(result) == len(MOCK_VOICES)
def test_filter_by_gender_female(self):
"""按女性筛选."""
result = list_voices(gender="female")
assert len(result) > 0
assert all(v.gender == VoiceGender.FEMALE for v in result)
def test_filter_by_gender_male(self):
"""按男性筛选."""
result = list_voices(gender="male")
assert len(result) > 0
assert all(v.gender == VoiceGender.MALE for v in result)
def test_filter_by_gender_child(self):
"""按童声筛选."""
result = list_voices(gender="child")
assert len(result) >= 1
assert all(v.gender == VoiceGender.CHILD for v in result)
def test_filter_by_invalid_gender(self):
"""无效性别返回空."""
result = list_voices(gender="alien")
assert result == []
def test_filter_by_style_narration(self):
"""按旁白风格筛选."""
result = list_voices(style="narration")
assert len(result) > 0
assert all(v.style == VoiceStyle.NARRATION for v in result)
def test_filter_by_style_news(self):
"""按新闻风格筛选."""
result = list_voices(style="news")
assert len(result) >= 1
assert all(v.style == VoiceStyle.NEWS for v in result)
def test_filter_by_style_story(self):
"""按故事风格筛选."""
result = list_voices(style="story")
assert len(result) >= 1
assert all(v.style == VoiceStyle.STORY for v in result)
def test_filter_by_invalid_style(self):
"""无效风格返回空."""
result = list_voices(style="unknown")
assert result == []
def test_filter_by_provider_mock(self):
"""mock provider返回全部."""
result = list_voices(provider="mock")
assert len(result) == len(MOCK_VOICES)
def test_filter_by_provider_other(self):
"""其他provider返回空."""
result = list_voices(provider="aliyun")
assert result == []
def test_keyword_search_name(self):
"""关键词搜索name."""
result = list_voices(keyword="温暖")
assert len(result) >= 1
assert any(v.voice_id == "female_warm" for v in result)
def test_keyword_search_description(self):
"""关键词搜索description."""
result = list_voices(keyword="商务")
assert len(result) >= 1
assert any("商务" in v.description for v in result)
def test_keyword_search_voice_id(self):
"""关键词搜索voice_id."""
result = list_voices(keyword="male_stable")
assert len(result) >= 1
assert result[0].voice_id == "male_stable"
def test_keyword_case_insensitive(self):
"""关键词不区分大小写(英文)."""
r1 = list_voices(keyword="Female")
r2 = list_voices(keyword="female")
assert len(r1) == len(r2)
def test_keyword_no_match(self):
"""无匹配关键词返回空."""
result = list_voices(keyword="完全不存在的音色xyz")
assert result == []
def test_gender_and_style_combined(self):
"""性别+风格组合筛选."""
result = list_voices(gender="female", style="story")
assert len(result) >= 1
assert all(v.gender == VoiceGender.FEMALE for v in result)
assert all(v.style == VoiceStyle.STORY for v in result)
def test_returns_new_list(self):
"""返回新列表,修改不影响原数据."""
result = list_voices()
result.clear()
assert len(MOCK_VOICES) == 8
def test_empty_keyword_returns_all(self):
"""空关键词返回全部."""
result = list_voices(keyword="")
assert len(result) == len(MOCK_VOICES)
def test_none_keyword_returns_all(self):
"""None关键词返回全部."""
result = list_voices(keyword=None)
assert len(result) == len(MOCK_VOICES)
# ── get_default_voice 测试 ───────────────────────────────────────────────────
class TestGetDefaultVoice:
"""get_default_voice 测试."""
def test_default_voice_exists(self):
"""默认音色存在."""
v = get_default_voice()
assert v is not None
assert isinstance(v, VoicePreset)
def test_default_is_first_mock(self):
"""默认音色是列表第一个."""
v = get_default_voice()
assert v is MOCK_VOICES[0]
def test_default_is_female_warm(self):
"""默认是温暖女声."""
v = get_default_voice()
assert v.voice_id == "female_warm"
assert v.name == "温暖女声"
assert v.gender == VoiceGender.FEMALE
def test_default_same_instance(self):
"""多次调用返回同一实例."""
v1 = get_default_voice()
v2 = get_default_voice()
assert v1 is v2