fix: #1809 补充 对口型接口参数校验返回400而非500 #1818
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user