Files
xiaoxia-saas/tests/unit/test_tts_engine_pure.py
T
xiaoxia 9dd2226eb6
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 9s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 49s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 55s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m23s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 41s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 49s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m56s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 47s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 23s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m55s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m34s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 7m12s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m32s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
style: fix isort import order
2026-07-25 12:03:40 +08:00

185 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""TTS配音引擎纯逻辑测试 — 数据结构 + 边界情况(mock TTS服务)."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from video_processing.tts_engine import (
TtsEngine,
VoiceoverResult,
VoiceoverSegment,
)
from packages.domain.tts_config import TtsConfig
from packages.ports.tts_service import TtsError
class TestVoiceoverSegment:
"""VoiceoverSegment 数据结构测试."""
def test_default_values(self):
"""默认值正确."""
seg = VoiceoverSegment(text="你好")
assert seg.text == "你好"
assert seg.start_time == 0.0
assert seg.end_time == 0.0
assert seg.audio_path is None
assert seg.duration == 0.0
def test_full_values(self):
"""完整字段."""
seg = VoiceoverSegment(
text="测试",
start_time=1.5,
end_time=3.5,
audio_path=Path("/tmp/test.wav"),
duration=2.0,
)
assert seg.text == "测试"
assert seg.start_time == 1.5
assert seg.end_time == 3.5
assert seg.audio_path == Path("/tmp/test.wav")
assert seg.duration == 2.0
class TestVoiceoverResult:
"""VoiceoverResult 数据结构测试."""
def test_failure_default(self):
"""失败结果默认值."""
result = VoiceoverResult(success=False)
assert result.success is False
assert result.segments == []
assert result.total_duration == 0.0
assert result.error_message == ""
def test_success_with_segments(self):
"""成功结果带片段."""
seg = VoiceoverSegment(text="hi", duration=5.0)
result = VoiceoverResult(
success=True,
segments=[seg],
total_duration=5.0,
)
assert result.success is True
assert len(result.segments) == 1
assert result.total_duration == 5.0
def test_failure_with_message(self):
"""失败带错误信息."""
result = VoiceoverResult(success=False, error_message="TTS出错")
assert result.success is False
assert result.error_message == "TTS出错"
class TestTtsEngineFullVoiceover:
"""TtsEngine.generate_full_voiceover 整段配音测试(mock TTS."""
def _make_engine(self, tmp_path: Path, tts: MagicMock | None = None) -> TtsEngine:
"""创建测试用TtsEngine."""
if tts is None:
tts = MagicMock()
tts.synthesize.return_value = str(tmp_path / "out.wav")
return TtsEngine(tts_service=tts, work_dir=tmp_path)
def test_disabled_returns_failure(self, tmp_path):
"""配音未启用→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=False, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "未启用" in result.error_message
assert len(result.segments) == 0
def test_empty_text_returns_failure(self, tmp_path):
"""文本为空→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text=" ")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "文本为空" in result.error_message
def test_success_creates_segment(self, tmp_path):
"""成功合成返回正确结构."""
mock_tts = MagicMock()
output_file = tmp_path / "voiceover_full.wav"
output_file.write_bytes(b"fake audio")
mock_tts.synthesize.return_value = str(output_file)
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试文本", voice_id="female_warm", speed=1.0)
result = engine.generate_full_voiceover(config)
assert result.success is True
assert len(result.segments) == 1
assert result.segments[0].text == "测试文本"
assert result.segments[0].start_time == 0.0
assert result.total_duration > 0
mock_tts.synthesize.assert_called_once()
def test_tts_error_returns_failure_gracefully(self, tmp_path):
"""TTS抛错→优雅降级返回失败."""
mock_tts = MagicMock()
mock_tts.synthesize.side_effect = TtsError("合成失败")
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "合成失败" in result.error_message
def test_generic_exception_returns_failure(self, tmp_path):
"""其他异常也降级返回失败."""
mock_tts = MagicMock()
mock_tts.synthesize.side_effect = RuntimeError("未知错误")
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "未知错误" in result.error_message
def test_work_dir_created(self, tmp_path):
"""工作目录自动创建."""
new_dir = tmp_path / "nested" / "tts"
mock_tts = MagicMock()
TtsEngine(tts_service=mock_tts, work_dir=new_dir)
assert new_dir.exists()
class TestTtsEngineSubtitleVoiceover:
"""TtsEngine.generate_subtitle_voiceover 字幕配音测试(mock TTS."""
def _make_engine(self, tmp_path: Path, tts: MagicMock | None = None) -> TtsEngine:
if tts is None:
tts = MagicMock()
return TtsEngine(tts_service=tts, work_dir=tmp_path)
def test_disabled_returns_failure(self, tmp_path):
"""配音未启用→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=False, text="")
result = engine.generate_subtitle_voiceover(config, [{"text": "hi", "start_time": 0, "end_time": 1}])
assert result.success is False
assert "未启用" in result.error_message
def test_empty_subtitles_returns_failure(self, tmp_path):
"""字幕列表为空→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text="")
result = engine.generate_subtitle_voiceover(config, [])
assert result.success is False
assert "字幕为空" in result.error_message
def test_none_subtitles_returns_failure(self, tmp_path):
"""None字幕也失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text="")
result = engine.generate_subtitle_voiceover(config, None) # type: ignore
assert result.success is False