Files
xiaoxia-saas/tests/unit/test_batch_no_plan_guard.py
xiaoxia 0469272bd6
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 22s
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 / Build Staging API Image (push) Successful in 38s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m59s
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 / Build Staging Worker Image (push) Successful in 33s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m23s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m24s
CI/CD Pipeline / Integration Tests (push) Successful in 2m40s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m31s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m16s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (push) Successful in 3m6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m42s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m48s
CI/CD Pipeline / Validate - Security (push) Successful in 5m28s
AI Code Review / AI Code Review (pull_request) Failing after 5m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m5s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m6s
CI/CD Pipeline / Unit Tests (push) Successful in 8m54s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
fix(#1743): 批量变体独立选片——完整重跑单视频选片+批次20%重叠避让+查重超阈重渲+封面独立 (#1745)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-06 18:07:24 +08:00

140 lines
5.2 KiB
Python
Executable File
Raw Permalink 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.
"""#1743 批量生成无可用 plan 守卫。
新规则(P0 降重):count>1 批量生成时必须存在源 plan(前端传入或按模板兜底
解析到最新 plan),为每个变体独立选片;**无任何可用 plan 时直接 4xx 中断、
不创建任务**,严禁 N 个任务兜底共用同一 plan 产出同源成片。
N=1 单视频不受影响(无 plan 时走原有单任务流程)。
"""
import sys
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
from fastapi import HTTPException
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
def _make_user():
return SimpleNamespace(user=SimpleNamespace(id="user-1"))
def _make_request(count, **overrides):
from app.schemas.generation_task import CreateGenerationTaskRequest
fields = dict(
project_id="proj-1",
asset_library_id="lib-1",
strategy_id="one_take",
asset_ids=["a1"],
count=count,
source_edit_plan_id="",
)
fields.update(overrides)
return CreateGenerationTaskRequest(**fields)
def _common_patches(latest_plan=None):
"""构造通用 patch 上下文(repo/usecase/enqueue 等)。latest_plan 为模板兜底 plan 或 None。"""
repo = MagicMock()
repo.count_pending_by_user.return_value = 0
repo.count_pending_total.return_value = 0
repo.create.side_effect = lambda t: t
repo.update.side_effect = lambda t: t
created = []
def _fake_execute(cmd):
task = MagicMock()
task.id = f"task-{len(created) + 1}"
task.source_edit_plan_id = cmd.source_edit_plan_id
task.status = "pending"
task.progress = 0.0
task.strategy_id = "one_take"
task.error_message = ""
task.cover_url = ""
task.title_config = {}
task.created_at = None
task.batch_id = "batch-1"
created.append(task)
return task
db = MagicMock()
# 模板兜底查最新 plan:返回 latest_planNone 表示查不到)
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = latest_plan
mock_uc = patch("app.api.routes.generation_tasks.CreateGenerationTaskUseCase")
other_patches = [
patch("app.api.routes.generation_tasks.safe_enqueue_generation_task", return_value=True),
patch("app.api.routes.generation_tasks._writeback_edit_plan_config"),
patch(
"app.api.routes.generation_tasks._resolve_project_and_library",
return_value=("proj-1", ""),
),
]
return repo, db, created, mock_uc, other_patches, _fake_execute
class TestBatchNoSourcePlanGuard:
def test_count3_without_any_plan_rejects_4xx_and_creates_nothing(self):
"""count=3 且无源 plan、模板兜底也查不到 → 400 中断,零任务创建(严禁同源成片)。"""
from app.api.routes.generation_tasks import create_generation_task
repo, db, created, mock_uc, other_patches, fake_exec = _common_patches(latest_plan=None)
MockUC = mock_uc.start()
MockUC.return_value.execute.side_effect = fake_exec
for p in other_patches:
p.start()
all_patches = [mock_uc] + other_patches
try:
with pytest.raises(HTTPException) as exc_info:
create_generation_task(
_make_request(3),
authenticated_user=_make_user(),
generation_task_repository=repo,
project_repository=MagicMock(),
asset_repository=MagicMock(),
asset_library_repository=MagicMock(),
db=db,
)
assert exc_info.value.status_code == 400
finally:
for p in reversed(all_patches):
p.stop()
assert len(created) == 0, "无 plan 批量必须零任务创建"
def test_count1_without_plan_still_works(self):
"""N=1 单视频无 plan 不触发批量守卫(向后兼容,不 4xx)。"""
from app.api.routes.generation_tasks import create_generation_task
repo, db, created, mock_uc, other_patches, fake_exec = _common_patches(latest_plan=None)
MockUC = mock_uc.start()
MockUC.return_value.execute.side_effect = fake_exec
for p in other_patches:
p.start()
all_patches = [mock_uc] + other_patches
try:
create_generation_task(
_make_request(1),
authenticated_user=_make_user(),
generation_task_repository=repo,
project_repository=MagicMock(),
asset_repository=MagicMock(),
asset_library_repository=MagicMock(),
db=db,
)
except HTTPException as e:
assert e.status_code != 400, f"N=1 不应被批量守卫拦截: {e.detail}"
except Exception:
# MagicMock 任务对象下游响应序列化可能抛 ValidationError 等,与批量守卫无关;
# 任务已在 usecase.execute 中创建,下方断言 created==1 即证明守卫未拦截。
pass
finally:
for p in reversed(all_patches):
p.stop()
assert len(created) == 1