f5100b7b5d
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 / Deploy Staging (Watchtower auto-deploy) (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 / Check if frontend-only change (pull_request) Successful in 52s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 55s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m53s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m9s
AI Code Review / AI Code Review (pull_request) Failing after 2m19s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m34s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m8s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 41s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m29s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m3s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m18s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 3m27s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 8m34s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 17m7s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m12s
CI/CD Pipeline / CI Gate (pull_request) Failing after 20s
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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 56s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Waiting to run
- 配音预览逻辑优化:优先检查 selectedVoice 是否为已上传素材 - 如果是上传素材,直接使用其 file_url 作为预览音频 - 仅对预设音色或克隆音色调用 previewTts 接口合成 - 切换配音或标题时自动重新生成预览音频 解决用户反馈:Step3 选择上传的配音素材后预览无声音的问题
152 lines
5.8 KiB
Python
152 lines
5.8 KiB
Python
"""Tests for voice_ids fallback in _download_all_assets.
|
|
|
|
When voice_library_id is empty but voice_ids is non-empty, the Worker
|
|
should fallback to voice_ids[0] as the audio asset_id.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestDownloadAllAssetsVoiceIdsFallback:
|
|
"""_download_all_assets 配音下载 fallback 逻辑测试。"""
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_voice_library_id_takes_priority(self, mock_download_videos, mock_download_voice, tmp_path):
|
|
"""voice_library_id 存在时优先使用,不 fallback 到 voice_ids。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
mock_download_voice.return_value = True
|
|
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="voice-lib-123",
|
|
task_id="task-1",
|
|
voice_ids=["voice-ids-456"],
|
|
)
|
|
|
|
assert audio is not None
|
|
mock_download_voice.assert_called_once()
|
|
call_args = mock_download_voice.call_args
|
|
assert call_args[0][0] == "voice-lib-123" # first positional arg
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_fallback_to_voice_ids_when_voice_library_id_empty(
|
|
self, mock_download_videos, mock_download_voice, tmp_path
|
|
):
|
|
"""voice_library_id 为空时 fallback 到 voice_ids[0]。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
mock_download_voice.return_value = True
|
|
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="", # 空字符串
|
|
task_id="task-2",
|
|
voice_ids=["voice-asset-789"],
|
|
)
|
|
|
|
assert audio is not None
|
|
mock_download_voice.assert_called_once()
|
|
call_args = mock_download_voice.call_args
|
|
assert call_args[0][0] == "voice-asset-789"
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_no_audio_when_both_empty(self, mock_download_videos, mock_download_voice, tmp_path):
|
|
"""voice_library_id 和 voice_ids 都为空时,不下载音频。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="",
|
|
task_id="task-3",
|
|
voice_ids=[],
|
|
)
|
|
|
|
assert audio is None
|
|
mock_download_voice.assert_not_called()
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_no_audio_when_voice_ids_none(self, mock_download_videos, mock_download_voice, tmp_path):
|
|
"""voice_ids 为 None 时,不触发 fallback。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="",
|
|
task_id="task-4",
|
|
voice_ids=None,
|
|
)
|
|
|
|
assert audio is None
|
|
mock_download_voice.assert_not_called()
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_voice_library_id_empty_string_fallback(self, mock_download_videos, mock_download_voice, tmp_path):
|
|
"""voice_library_id 为空字符串且 voice_ids 有多个元素时,取第一个。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
mock_download_voice.return_value = True
|
|
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="",
|
|
task_id="task-5",
|
|
voice_ids=["first-id", "second-id", "third-id"],
|
|
)
|
|
|
|
assert audio is not None
|
|
call_args = mock_download_voice.call_args
|
|
assert call_args[0][0] == "first-id"
|
|
|
|
@patch("worker_app.tasks.generation._download_voice_asset")
|
|
@patch("worker_app.tasks.generation._download_library_assets")
|
|
def test_backward_compat_no_voice_ids_param(self, mock_download_videos, mock_download_voice, tmp_path):
|
|
"""不传 voice_ids 参数时,行为与之前一致(向后兼容)。"""
|
|
from worker_app.tasks.generation import _download_all_assets
|
|
|
|
mock_download_videos.return_value = [tmp_path / "v1.mp4"]
|
|
mock_download_voice.return_value = True
|
|
|
|
# 不传 voice_ids
|
|
videos, audio = _download_all_assets(
|
|
temp_path=tmp_path,
|
|
asset_library_id="lib-1",
|
|
project_id="proj-1",
|
|
task_asset_ids=["a1"],
|
|
voice_library_id="voice-lib-999",
|
|
task_id="task-6",
|
|
)
|
|
|
|
assert audio is not None
|
|
mock_download_voice.assert_called_once_with("voice-lib-999", tmp_path / "voice.mp3")
|