Files
xiaoxia-saas/tests/unit/domain/test_preset_voices.py
T
xiaoxia 2f26541188
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m16s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m36s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m26s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m17s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 3m46s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m9s
CI/CD Pipeline / Integration Tests (push) Successful in 1m52s
CI/CD Pipeline / Unit Tests (push) Successful in 8m39s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 31s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 40s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m1s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1086h16m20s
CI/CD Pipeline / Deploy Production (push) Failing after 1086h23m30s
CI/CD Pipeline / CI Gate (push) Failing after 1086h23m30s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1086h23m30s
CI/CD Pipeline / Build Production API Image (push) Failing after 1086h23m31s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1086h32m11s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1086h32m12s
CI/CD Pipeline / PR Build API Image (push) Failing after 1086h32m11s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1086h56m43s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1086h56m44s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1087h5m25s
CI/CD Pipeline / Frontend Lint (push) Failing after 1087h5m26s
fix: 修复预置音色voice_id并填充preview_url (#1216)
2026-08-02 08:55:14 +08:00

246 lines
8.1 KiB
Python
Executable File

"""preset_voices 预置音色配置单测."""
from dataclasses import FrozenInstanceError
import pytest
from domain.preset_voices import (
PRESET_VOICES,
PresetVoice,
get_preset_voice_by_id,
get_preset_voices,
is_preset_voice,
)
# ── PresetVoice dataclass ────────────────────────────────────────────────────
class TestPresetVoice:
"""PresetVoice dataclass"""
def test_minimal_creation(self):
v = PresetVoice(
voice_id="test_v1",
name="测试音色",
description="测试描述",
gender="female",
)
assert v.voice_id == "test_v1"
assert v.name == "测试音色"
assert v.description == "测试描述"
assert v.gender == "female"
assert v.language == "zh-CN"
assert v.preview_url == ""
assert v.tags is None
def test_full_creation(self):
v = PresetVoice(
voice_id="test_v2",
name="完整音色",
description="完整描述",
gender="male",
language="en-US",
preview_url="https://example.com/preview.mp3",
tags=["沉稳", "男声"],
)
assert v.gender == "male"
assert v.language == "en-US"
assert v.preview_url == "https://example.com/preview.mp3"
assert v.tags == ["沉稳", "男声"]
def test_frozen_immutable(self):
v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
with pytest.raises(FrozenInstanceError):
v.name = "NewName"
def test_equality(self):
v1 = PresetVoice(voice_id="same", name="同名", description="d", gender="female")
v2 = PresetVoice(voice_id="same", name="同名", description="d", gender="female")
assert v1 == v2
def test_inequality(self):
v1 = PresetVoice(voice_id="a", name="A", description="da", gender="female")
v2 = PresetVoice(voice_id="b", name="B", description="db", gender="male")
assert v1 != v2
def test_to_dict(self):
v = PresetVoice(
voice_id="test_v1",
name="测试音色",
description="测试描述",
gender="female",
language="zh-CN",
preview_url="https://x.com/a.mp3",
tags=["温柔", "女声"],
)
d = v.to_dict()
assert isinstance(d, dict)
assert d["voice_id"] == "test_v1"
assert d["name"] == "测试音色"
assert d["description"] == "测试描述"
assert d["gender"] == "female"
assert d["language"] == "zh-CN"
assert d["preview_url"] == "https://x.com/a.mp3"
assert d["tags"] == ["温柔", "女声"]
def test_to_dict_none_tags_becomes_empty_list(self):
v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
d = v.to_dict()
assert d["tags"] == []
assert isinstance(d["tags"], list)
def test_to_dict_has_all_keys(self):
v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
d = v.to_dict()
assert set(d.keys()) == {
"voice_id",
"name",
"description",
"gender",
"language",
"preview_url",
"tags",
}
def test_slots_no_extra_attrs(self):
v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
# frozen + slots dataclass 不允许动态添加属性
with pytest.raises((AttributeError, TypeError)):
v.nonexistent_field = "value"
# ── PRESET_VOICES 列表 ───────────────────────────────────────────────────────
class TestPresetVoicesList:
"""PRESET_VOICES 预置音色列表"""
def test_not_empty(self):
assert len(PRESET_VOICES) > 0
def test_count(self):
assert len(PRESET_VOICES) == 8
def test_all_are_preset_voice(self):
for v in PRESET_VOICES:
assert isinstance(v, PresetVoice)
def test_unique_voice_ids(self):
ids = [v.voice_id for v in PRESET_VOICES]
assert len(ids) == len(set(ids))
def test_unique_names(self):
names = [v.name for v in PRESET_VOICES]
assert len(names) == len(set(names))
def test_all_have_required_fields(self):
for v in PRESET_VOICES:
assert v.voice_id != ""
assert v.name != ""
assert v.description != ""
assert v.gender in ("male", "female")
def test_all_chinese(self):
for v in PRESET_VOICES:
assert v.language == "zh-CN"
def test_longxiaochun_voice(self):
v = next(v for v in PRESET_VOICES if v.voice_id == "longxiaochun_v3")
assert v.name == "龙小淳"
assert v.gender == "female"
assert "知性" in v.description
def test_longsanshu_voice(self):
v = next(v for v in PRESET_VOICES if v.voice_id == "longsanshu_v3")
assert v.name == "龙三叔"
assert v.gender == "male"
def test_male_voices_count(self):
males = [v for v in PRESET_VOICES if v.gender == "male"]
assert len(males) == 3 # 龙三叔/龙书/龙硕
def test_female_voices_count(self):
females = [v for v in PRESET_VOICES if v.gender == "female"]
assert len(females) == 5 # 龙小淳/龙小夏/龙悦/龙应静/龙甜
def test_all_have_tags(self):
for v in PRESET_VOICES:
assert v.tags is not None
assert len(v.tags) > 0
def test_voice_id_pattern(self):
# 所有音色 ID 都以 _v3 结尾
for v in PRESET_VOICES:
assert v.voice_id.endswith("_v3")
# ── 工具函数 ─────────────────────────────────────────────────────────────────
class TestGetPresetVoices:
"""get_preset_voices 函数"""
def test_returns_full_list(self):
result = get_preset_voices()
assert len(result) == len(PRESET_VOICES)
assert result is PRESET_VOICES # 返回同一列表引用
def test_all_are_preset_voice(self):
result = get_preset_voices()
for v in result:
assert isinstance(v, PresetVoice)
class TestGetPresetVoiceById:
"""get_preset_voice_by_id 函数"""
def test_get_existing_female(self):
v = get_preset_voice_by_id("longxiaochun_v3")
assert v is not None
assert v.voice_id == "longxiaochun_v3"
assert v.name == "龙小淳"
def test_get_existing_male(self):
v = get_preset_voice_by_id("longsanshu_v3")
assert v is not None
assert v.gender == "male"
def test_get_nonexistent_returns_none(self):
v = get_preset_voice_by_id("nonexistent_voice")
assert v is None
def test_get_empty_string_returns_none(self):
v = get_preset_voice_by_id("")
assert v is None
def test_returns_same_instance(self):
v1 = get_preset_voice_by_id("longyue_v3")
v2 = get_preset_voice_by_id("longyue_v3")
assert v1 is v2
def test_all_voices_reachable(self):
for v in PRESET_VOICES:
found = get_preset_voice_by_id(v.voice_id)
assert found is not None
assert found.voice_id == v.voice_id
class TestIsPresetVoice:
"""is_preset_voice 函数"""
def test_existing_voice_true(self):
assert is_preset_voice("longxiaochun_v3") is True
def test_all_existing_are_true(self):
for v in PRESET_VOICES:
assert is_preset_voice(v.voice_id) is True
def test_nonexistent_voice_false(self):
assert is_preset_voice("fake_voice") is False
def test_empty_string_false(self):
assert is_preset_voice("") is False
def test_consistent_with_get_by_id(self):
for v in PRESET_VOICES:
assert is_preset_voice(v.voice_id) == (get_preset_voice_by_id(v.voice_id) is not None)