Files
xiaoxia-saas/tests/unit/test_preset_voices.py
T
灵应 44dc89360a
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 170h24m47s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 170h24m52s
Deploy / Deploy Staging (push) Failing after 170h27m34s
CI/CD Pipeline / Frontend Lint (push) Failing after 170h28m8s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 170h28m14s
feat: 配音列表 API 增强 — 预置音色 + 克隆音色统一接口
- 新增预置音色配置(8 个 CosyVoice 真实音色)
- 增强 GET /api/v1/voices 接口,支持 type 查询参数(preset/clone/all)
- 新增 GET /api/v1/voices/presets 端点
- 统一响应格式,包含 preset_count 和 clone_count
- 保留原有 CRUD 端点向后兼容
- 添加 18 个预置音色单元测试(全部通过)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 10:26:14 +08:00

188 lines
6.2 KiB
Python

"""预置音色配置单元测试 — Phase 3 CosyVoice 集成."""
from __future__ import annotations
import pytest
from packages.domain.preset_voices import (
PRESET_VOICES,
PresetVoice,
get_preset_voice_by_id,
get_preset_voices,
is_preset_voice,
)
class TestPresetVoice:
"""测试 PresetVoice 数据类。"""
def test_preset_voice_fields(self) -> None:
"""预置音色字段完整。"""
voice = PresetVoice(
voice_id="test_voice",
name="测试音色",
description="测试描述",
gender="female",
language="zh-CN",
preview_url="https://example.com/preview.mp3",
tags=["测试"],
)
assert voice.voice_id == "test_voice"
assert voice.name == "测试音色"
assert voice.description == "测试描述"
assert voice.gender == "female"
assert voice.language == "zh-CN"
assert voice.preview_url == "https://example.com/preview.mp3"
assert voice.tags == ["测试"]
def test_preset_voice_defaults(self) -> None:
"""预置音色默认值。"""
voice = PresetVoice(
voice_id="test",
name="测试",
description="描述",
gender="male",
)
assert voice.language == "zh-CN"
assert voice.preview_url == ""
assert voice.tags is None
def test_preset_voice_frozen(self) -> None:
"""预置音色不可变。"""
voice = PresetVoice(
voice_id="test",
name="测试",
description="描述",
gender="male",
)
with pytest.raises(AttributeError):
voice.name = "新名称"
def test_preset_voice_to_dict(self) -> None:
"""序列化。"""
voice = PresetVoice(
voice_id="longxiaochun",
name="龙小淳",
description="温柔女声",
gender="female",
tags=["温柔", "女声"],
)
result = voice.to_dict()
assert result["voice_id"] == "longxiaochun"
assert result["name"] == "龙小淳"
assert result["description"] == "温柔女声"
assert result["gender"] == "female"
assert result["language"] == "zh-CN"
assert result["preview_url"] == ""
assert result["tags"] == ["温柔", "女声"]
def test_preset_voice_to_dict_no_tags(self) -> None:
"""tags 为 None 时序列化为空列表。"""
voice = PresetVoice(
voice_id="test",
name="测试",
description="描述",
gender="male",
)
result = voice.to_dict()
assert result["tags"] == []
class TestPresetVoicesConfig:
"""测试预置音色配置列表。"""
def test_preset_voices_not_empty(self) -> None:
"""预置音色列表不为空。"""
assert len(PRESET_VOICES) >= 5
def test_preset_voices_has_8_voices(self) -> None:
"""应有 8 个预置音色。"""
assert len(PRESET_VOICES) == 8
def test_all_voices_have_required_fields(self) -> None:
"""所有预置音色都有必填字段。"""
for voice in PRESET_VOICES:
assert voice.voice_id, f"voice_id 为空: {voice}"
assert voice.name, f"name 为空: {voice}"
assert voice.description, f"description 为空: {voice}"
assert voice.gender in ("male", "female"), f"gender 无效: {voice}"
assert voice.language == "zh-CN", f"language 应为 zh-CN: {voice}"
def test_all_voices_have_unique_ids(self) -> None:
"""所有预置音色 ID 唯一。"""
ids = [v.voice_id for v in PRESET_VOICES]
assert len(ids) == len(set(ids)), "存在重复的 voice_id"
def test_all_voices_have_unique_names(self) -> None:
"""所有预置音色名称唯一。"""
names = [v.name for v in PRESET_VOICES]
assert len(names) == len(set(names)), "存在重复的 name"
def test_cosyvoice_voice_ids(self) -> None:
"""音色 ID 应为 CosyVoice 真实可用的音色名。"""
expected_ids = {
"longxiaochun",
"longxiaoxia",
"longxiaochen",
"longyue",
"longshu",
"longjing",
"longbo",
"longtian",
}
actual_ids = {v.voice_id for v in PRESET_VOICES}
assert actual_ids == expected_ids
def test_gender_distribution(self) -> None:
"""男女音色分布合理。"""
male_count = sum(1 for v in PRESET_VOICES if v.gender == "male")
female_count = sum(1 for v in PRESET_VOICES if v.gender == "female")
assert male_count == 3
assert female_count == 5
def test_all_have_tags(self) -> None:
"""所有预置音色都有标签。"""
for voice in PRESET_VOICES:
assert voice.tags is not None, f"tags 为 None: {voice.name}"
assert len(voice.tags) >= 2, f"标签太少: {voice.name}"
class TestPresetVoiceHelpers:
"""测试辅助函数。"""
def test_get_preset_voices(self) -> None:
"""get_preset_voices 返回完整列表。"""
voices = get_preset_voices()
assert voices == PRESET_VOICES
assert len(voices) == 8
def test_get_preset_voice_by_id_found(self) -> None:
"""按 ID 查找存在的音色。"""
voice = get_preset_voice_by_id("longxiaochun")
assert voice is not None
assert voice.name == "龙小淳"
assert voice.voice_id == "longxiaochun"
def test_get_preset_voice_by_id_not_found(self) -> None:
"""按 ID 查找不存在的音色。"""
voice = get_preset_voice_by_id("nonexistent_voice")
assert voice is None
def test_is_preset_voice_true(self) -> None:
"""判断预置音色返回 True。"""
assert is_preset_voice("longxiaochun") is True
assert is_preset_voice("longxiaoxia") is True
assert is_preset_voice("longbo") is True
def test_is_preset_voice_false(self) -> None:
"""判断非预置音色返回 False。"""
assert is_preset_voice("nonexistent") is False
assert is_preset_voice("") is False
assert is_preset_voice("user_custom_voice") is False