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
246 lines
8.1 KiB
Python
Executable File
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)
|