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
403 lines
13 KiB
Python
Executable File
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
|