Files
xiaoxia-saas/tests/unit/test_empty_plan_id_marks_failed.py
T
CI Bot ca7fd0bb34
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 / Check if frontend-only change (pull_request) Successful in 48s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m46s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 1m52s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m1s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m2s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 37s
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 / PR Build Worker Image (pull_request) Successful in 37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m57s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m49s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
fix: source_edit_plan_id 为空时标记任务为 failed 而非卡在 running
根因:worker generate_video 在 mark_processing 之后、进入 else 分支
(source_edit_plan_id 为空)时直接 return failed 字典,但没有调用
_update_task_status(task_id, "mark_failed"),导致 DB 状态永远停在
running,前端轮询永远等不到完成(表现为卡在 10%)。

修复:在 return 之前调用 mark_failed 更新 DB 状态。
新增 2 个回归测试验证 else 分支包含 mark_failed 且在 return 之前。
2026-08-24 15:04:57 +08:00

64 lines
2.6 KiB
Python

"""回归测试:source_edit_plan_id 为空时,generate_video 必须调用 mark_failed。
背景 (2026-08-24):确认生成卡在 10% 永不结束。根因是 worker 在
source_edit_plan_id 为空时直接 return failed 字典,但没有调用
_update_task_status(task_id, "mark_failed"),导致 DB 状态永远停在
running,前端轮询永远等不到完成。
"""
from __future__ import annotations
import re
from pathlib import Path
GENERATION_PY = Path(__file__).resolve().parents[2] / "apps" / "worker" / "worker_app" / "tasks" / "generation.py"
def _read_source() -> str:
return GENERATION_PY.read_text(encoding="utf-8")
class TestEmptyPlanIdMarksFailed:
"""验证 generation.py 在 source_edit_plan_id 为空的 else 分支中调用了 mark_failed。"""
def test_else_branch_has_mark_failed(self):
"""else 分支(source_edit_plan_id 为空)必须在 return 之前调用 mark_failed。"""
source = _read_source()
# 找到 else 分支: 包含 source_edit_plan_id 为空 的日志
else_pattern = re.compile(
r"else:\s*\n"
r"\s+logger\.error\("
r"[^)]*source_edit_plan_id",
re.DOTALL,
)
assert else_pattern.search(source), "未找到 source_edit_plan_id 为空的 else 分支"
else_match = else_pattern.search(source)
else_start = else_match.start()
else_block = source[else_start:else_start + 800]
return_match = re.search(r'return\s*\{[^}]*"status"\s*:\s*"failed"', else_block, re.DOTALL)
assert return_match, "else 分支中未找到 return failed"
block_before_return = else_block[:return_match.start()]
assert "mark_failed" in block_before_return, (
"source_edit_plan_id 为空的 else 分支在 return 之前必须调用 mark_failed"
)
def test_mark_failed_before_return_in_else(self):
"""mark_failed 调用必须在 return 之前,而非之后。"""
source = _read_source()
else_idx = source.find("source_edit_plan_id 为空,无法渲染")
assert else_idx != -1, "未找到 source_edit_plan_id 为空的日志"
after_else = source[else_idx:]
mark_failed_idx = after_else.find('"mark_failed"')
return_failed_idx = after_else.find('"status": "failed"')
assert mark_failed_idx != -1, "else 分支中没有 mark_failed 调用"
assert return_failed_idx != -1, "else 分支中没有 return failed"
assert mark_failed_idx < return_failed_idx, (
f"mark_failed (pos {mark_failed_idx}) 必须在 return failed (pos {return_failed_idx}) 之前"
)