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
- 新增预置音色配置(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>
188 lines
6.2 KiB
Python
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
|