e83a0b86f8
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 58s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m18s
CI/CD Pipeline / Validate - Style (push) Successful in 2m31s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m30s
CI/CD Pipeline / Integration Tests (push) Successful in 2m35s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m30s
CI/CD Pipeline / Validate - Security (push) Successful in 4m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m54s
AI Code Review / AI Code Review (pull_request) Successful in 6m19s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m57s
CI/CD Pipeline / Unit Tests (push) Successful in 8m33s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m48s
CI/CD Pipeline / Canary Release to Production (push) Failing after 221h53m0s
CI/CD Pipeline / CI Gate (push) Failing after 221h53m4s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 221h53m4s
CI/CD Pipeline / Build Production API Image (push) Failing after 221h53m6s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 221h58m26s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 221h59m0s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 221h59m0s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 222h0m54s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 222h0m57s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 221h59m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 222h0m57s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 222h0m55s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 222h1m13s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 222h1m17s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 222h0m55s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 222h1m23s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 222h1m28s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 222h1m39s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 222h1m33s
CI/CD Pipeline / Frontend Lint (push) Failing after 222h1m41s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 222h1m37s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 222h1m43s
CI/CD Pipeline / PR Build API Image (push) Failing after 222h1m43s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 222h1m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 222h1m45s
CI/CD Pipeline / Deploy Production (push) Failing after 222h27m37s
CI/CD Pipeline / Build Production Web Image (push) Failing after 222h27m42s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 222h35m33s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 222h35m50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 222h36m14s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 222h36m15s
CI/CD Pipeline / PR Build Web Image (push) Failing after 222h36m20s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""#1749 POST /api/v1/generation/variant-plans 路由单元测试。
|
||
|
||
直接调用路由函数 + mock 依赖(不使用 TestClient,因 httpx2 冲突)。
|
||
|
||
覆盖:
|
||
- 200: 合法请求返回 items
|
||
- 400: voice_library_ids 长度不符 (schema validator)
|
||
- 400: 缺少 source_edit_plan_id 且无模板兜底 plan
|
||
- 400: 选片失败 ValueError -> HTTPException(400)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
from fastapi import HTTPException
|
||
|
||
|
||
def _call_route(request_obj, db=None):
|
||
"""直接调用 create_variant_plans 路由函数。"""
|
||
from app.api.routes.generation_variant_plans import create_variant_plans
|
||
|
||
user = MagicMock()
|
||
user.user.id = "u1"
|
||
if db is None:
|
||
db = MagicMock()
|
||
return create_variant_plans(request_obj, authenticated_user=user, db=db)
|
||
|
||
|
||
def _base_payload(**kw):
|
||
from app.api.routes.generation_variant_plans import VariantPlanRequest
|
||
d = {
|
||
"template_id": "tpl_1",
|
||
"asset_ids": ["a1", "a2"],
|
||
"count": 1,
|
||
"source_edit_plan_id": "src_plan_1",
|
||
}
|
||
d.update(kw)
|
||
return VariantPlanRequest(**d)
|
||
|
||
|
||
class TestVariantPlansRoute:
|
||
def test_200_happy_path(self):
|
||
"""合法请求:ensure_variant_plans 返回 plan_id,list_clips 返回 clips。"""
|
||
req = _base_payload()
|
||
db = MagicMock()
|
||
|
||
mock_svc_inst = MagicMock()
|
||
mock_svc_inst.ensure_variant_plans.return_value = ["plan_v0"]
|
||
clip = MagicMock()
|
||
clip.id = "c1"
|
||
clip.order = 0
|
||
clip.asset_id = "a1"
|
||
clip.start_time = 0.0
|
||
clip.duration = 10.0
|
||
clip.clip_type = "main"
|
||
clip.transition_effect = "cut"
|
||
clip.transition_duration = 0.0
|
||
clip.playback_speed = 1.0
|
||
clip.text_content = ""
|
||
mock_svc_inst.list_clips.return_value = [clip]
|
||
|
||
mock_svc_cls = MagicMock(return_value=mock_svc_inst)
|
||
|
||
with patch(
|
||
"packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
|
||
return_value=["voice_1"],
|
||
), patch(
|
||
"app.api.routes.generation_tasks._query_voice_durations",
|
||
return_value=[30.0],
|
||
), patch(
|
||
"app.services.edit_plan_service.EditPlanService",
|
||
mock_svc_cls,
|
||
):
|
||
resp = _call_route(req, db)
|
||
|
||
assert resp.total == 1
|
||
assert resp.items[0].plan_id == "plan_v0"
|
||
assert len(resp.items[0].clips) == 1
|
||
|
||
def test_400_voice_library_ids_length_mismatch(self):
|
||
"""voice_library_ids 长度 != count -> pydantic model_validator 抛异常。"""
|
||
from app.api.routes.generation_variant_plans import VariantPlanRequest
|
||
with pytest.raises(Exception): # noqa: B017
|
||
VariantPlanRequest(
|
||
template_id="tpl_1",
|
||
asset_ids=["a1"],
|
||
count=3,
|
||
source_edit_plan_id="src_plan_1",
|
||
voice_library_ids=["v1", "v2"], # len=2 != count=3
|
||
)
|
||
|
||
def test_400_no_source_plan_no_template_fallback(self):
|
||
"""无 source_edit_plan_id 且 DB 查不到模板 plan -> HTTPException(400)。"""
|
||
from app.api.routes.generation_variant_plans import VariantPlanRequest
|
||
|
||
req = VariantPlanRequest(
|
||
template_id="tpl_nonexist",
|
||
asset_ids=[],
|
||
count=1,
|
||
source_edit_plan_id="",
|
||
)
|
||
|
||
db = MagicMock()
|
||
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
|
||
|
||
with patch(
|
||
"packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
|
||
return_value=[""],
|
||
):
|
||
with pytest.raises(HTTPException) as exc_info:
|
||
_call_route(req, db)
|
||
assert exc_info.value.status_code == 400
|
||
|
||
def test_400_selection_failure_value_error(self):
|
||
"""ensure_variant_plans 抛 ValueError -> HTTPException(400)。"""
|
||
req = _base_payload()
|
||
db = MagicMock()
|
||
|
||
mock_svc_inst = MagicMock()
|
||
mock_svc_inst.ensure_variant_plans.side_effect = ValueError("素材池为空")
|
||
mock_svc_cls = MagicMock(return_value=mock_svc_inst)
|
||
|
||
with patch(
|
||
"packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
|
||
return_value=["voice_1"],
|
||
), patch(
|
||
"app.api.routes.generation_tasks._query_voice_durations",
|
||
return_value=[30.0],
|
||
), patch(
|
||
"app.services.edit_plan_service.EditPlanService",
|
||
mock_svc_cls,
|
||
):
|
||
with pytest.raises(HTTPException) as exc_info:
|
||
_call_route(req, db)
|
||
assert exc_info.value.status_code == 400
|