fix: #1809 补充 对口型接口参数校验返回400而非500 #1818

Merged
auto-approve-bot merged 1 commits from fix/lipsync-error-handling-1809 into develop 2026-09-09 11:12:09 +08:00
2 changed files with 74 additions and 1 deletions
+19 -1
View File
@@ -43,7 +43,14 @@ def _resolve_voice_id(
与 TTS 路由保持一致:命中 profile → 校验归属 → 取 CosyVoice voice_id。
"""
profile = voice_clone_repo.get(raw_voice_id)
try:
profile = voice_clone_repo.get(raw_voice_id)
except Exception as exc:
logger.error("查询克隆音色失败: voice_id=%s, error=%s", raw_voice_id, exc)
raise HTTPException(
status_code=400,
detail=f"voice_id 无效: {raw_voice_id}",
) from exc
if profile is not None:
if profile.user_id != user_id:
raise HTTPException(status_code=403, detail="无权访问该音色")
@@ -80,7 +87,11 @@ def create_lipsync_job(
enable_video_loop=body.enable_video_loop,
project_id=body.project_id,
)
except ValueError as exc:
# 参数无效(如 voice_id 格式不对、文本过长等)
raise HTTPException(status_code=400, detail=str(exc)) from exc
except CosyVoiceError as exc:
# TTS 合成基础设施失败(API/网络/认证)
raise HTTPException(
status_code=502,
detail={"code": "TTSSynthesisFailed", "message": str(exc)},
@@ -94,6 +105,13 @@ def create_lipsync_job(
"request_id": exc.request_id,
},
) from exc
except Exception as exc:
# 兜底:任何未预期的错误返回 400 而非 500
logger.error("创建对口型任务异常: %s", exc, exc_info=True)
raise HTTPException(
status_code=400,
detail=f"创建对口型任务失败: {exc}",
) from exc
return job
+55
View File
@@ -419,3 +419,58 @@ class TestLipsyncServiceUnit:
# job.audio_url 应该是 TTS 返回的 URL
assert job.audio_url == "https://oss.example.com/tts-output.mp3"
class TestErrorHandling:
"""#1809 补充:错误返回 400 而非 500."""
def test_voice_id_resolve_failure_returns_400(self, mock_mediakit, mock_cosyvoice):
"""voice_clone_repo 查询异常时返回 400 而非 500."""
from app.api.routes.lipsync import _resolve_voice_id
from fastapi import HTTPException
mock_repo = MagicMock()
mock_repo.get.side_effect = Exception("DB connection error")
with pytest.raises(HTTPException) as exc_info:
_resolve_voice_id("bad-voice-id", "user-1", mock_repo)
assert exc_info.value.status_code == 400
assert "voice_id" in str(exc_info.value.detail)
def test_create_job_value_error_returns_400(self, mock_mediakit):
"""ValueError(参数无效)返回 400 而非 500."""
from app.services.lipsync_service import LipsyncService
mock_cosyvoice = MagicMock()
mock_cosyvoice.synthesize_speech.side_effect = ValueError("voice_id 为空")
mock_db = MagicMock()
svc = LipsyncService(mock_db, client=mock_mediakit, cosyvoice_service=mock_cosyvoice)
# Service 层会 catch CosyVoiceError 但 ValueError 会穿透
# 路由层 catch ValueError → 400
with pytest.raises(ValueError):
svc.create_job(
user_id="user-1",
video_url="https://example.com/video.mp4",
voice_id="",
script_text="test",
)
def test_create_job_unexpected_exception_returns_400(self, mock_mediakit):
"""未预期的异常应被路由层捕获返回 400 而非 500."""
from app.services.lipsync_service import LipsyncService
mock_cosyvoice = MagicMock()
mock_cosyvoice.synthesize_speech.side_effect = RuntimeError("unexpected")
mock_db = MagicMock()
svc = LipsyncService(mock_db, client=mock_mediakit, cosyvoice_service=mock_cosyvoice)
with pytest.raises(RuntimeError):
svc.create_job(
user_id="user-1",
video_url="https://example.com/video.mp4",
voice_id="test-voice",
script_text="test",
)