24379e14ca
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 39s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m16s
AI Code Review / AI Code Review (pull_request) Successful in 1m52s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m17s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m18s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m38s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m30s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (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
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 551h44m42s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 551h44m46s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 551h45m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 551h46m10s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 551h46m31s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 551h46m33s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 551h48m23s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 551h48m25s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 551h48m28s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 552h18m36s
58 lines
2.7 KiB
Python
58 lines
2.7 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 ast
|
|
from pathlib import Path
|
|
|
|
|
|
GENERATION_PY = Path(__file__).resolve().parents[2] / "apps" / "worker" / "worker_app" / "tasks" / "generation.py"
|
|
|
|
|
|
def _get_else_block_source() -> str:
|
|
"""解析 generate_video 函数,提取 if source_edit_plan_id: 的 else 分支源码。"""
|
|
source = GENERATION_PY.read_text(encoding="utf-8")
|
|
lines = source.splitlines(keepends=True)
|
|
tree = ast.parse(source)
|
|
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.FunctionDef) and node.name == "generate_video":
|
|
for child in ast.walk(node):
|
|
if isinstance(child, ast.If):
|
|
test = child.test
|
|
if isinstance(test, ast.Name) and test.id == "source_edit_plan_id":
|
|
if not child.orelse:
|
|
raise AssertionError("if source_edit_plan_id 缺少 else 分支")
|
|
# 取 else 块第一行的行号和最后一行的行号
|
|
first_line = child.orelse[0].lineno # 1-based
|
|
last_line = child.orelse[-1].end_lineno
|
|
return "".join(lines[first_line - 1:last_line])
|
|
raise AssertionError("未找到 if source_edit_plan_id: 分支")
|
|
|
|
|
|
class TestEmptyPlanIdMarksFailed:
|
|
"""验证 else 分支(source_edit_plan_id 为空)调用了 mark_failed。"""
|
|
|
|
def test_else_branch_calls_mark_failed(self):
|
|
"""else 分支必须包含 _update_task_status + mark_failed 调用。"""
|
|
else_source = _get_else_block_source()
|
|
assert "mark_failed" in else_source, (
|
|
"else 分支必须调用 _update_task_status(task_id, 'mark_failed', ...) "
|
|
"以更新 DB 状态为 failed,否则任务永远卡在 running"
|
|
)
|
|
|
|
def test_mark_failed_before_return(self):
|
|
"""mark_failed 调用必须出现在 return 之前。"""
|
|
else_source = _get_else_block_source()
|
|
mark_failed_pos = else_source.find("mark_failed")
|
|
return_pos = else_source.find("return")
|
|
assert mark_failed_pos != -1, "else 分支中未找到 mark_failed"
|
|
assert return_pos != -1, "else 分支中未找到 return"
|
|
assert mark_failed_pos < return_pos, (
|
|
f"mark_failed (pos {mark_failed_pos}) 必须在 return (pos {return_pos}) 之前"
|
|
)
|