efb7fa5729
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 (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
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 (pull_request) Has been skipped
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 / 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 / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
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 API Image (pull_request) Successful in 47s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m54s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m6s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 8m43s
AI Code Review / AI Code Review (pull_request) Successful in 10m46s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m49s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 15m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 24m52s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 4m25s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 5m1s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 1h3m21s
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 / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
89 lines
3.6 KiB
Python
89 lines
3.6 KiB
Python
"""Regression test: ensure worker.generate_video Celery task is bound to the
|
||
real generate_video function, not a helper introduced above it.
|
||
|
||
Context (P0 incident 2026-08-23): a refactor inserted helper function
|
||
_sync_task_config_to_plan directly under the @celery_app.task decorator,
|
||
so Celery registered the helper as "worker.generate_video". Calling the
|
||
task with a single task_id raised TypeError and every generation job
|
||
failed immediately. This test pins the decorator target.
|
||
|
||
NOTE: CI conftest may mock Celery so that @celery_app.task does NOT return
|
||
a fully functional Task/PromiseProxy object. Tests therefore use multiple
|
||
defensive strategies: source-code inspection, __wrapped__.__func__ chain
|
||
traversal, and direct attribute checks.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import inspect
|
||
import re
|
||
|
||
|
||
def _get_original_function(generate_video):
|
||
"""Walk the __wrapped__ chain to find the original function object."""
|
||
obj = generate_video
|
||
seen = set()
|
||
while hasattr(obj, "__wrapped__"):
|
||
obj_id = id(obj)
|
||
if obj_id in seen:
|
||
break
|
||
seen.add(obj_id)
|
||
obj = obj.__wrapped__
|
||
# __wrapped__ may be a bound method — unwrap to the underlying function
|
||
if hasattr(obj, "__func__"):
|
||
return obj.__func__
|
||
return obj
|
||
|
||
|
||
def test_generate_video_task_has_bind_true():
|
||
"""The decorator must use bind=True — verified via the original function's
|
||
first parameter being 'self' (bind=True convention)."""
|
||
from worker_app.tasks.generation import generate_video
|
||
|
||
original = _get_original_function(generate_video)
|
||
sig = inspect.signature(original)
|
||
params = list(sig.parameters)
|
||
assert params[0] == "self", f"bind=True requires 'self' as first param, got {params}"
|
||
|
||
|
||
def test_generate_video_task_signature_has_task_id():
|
||
"""The original generate_video function must accept task_id as a parameter."""
|
||
from worker_app.tasks.generation import generate_video
|
||
|
||
original = _get_original_function(generate_video)
|
||
sig = inspect.signature(original)
|
||
params = list(sig.parameters)
|
||
assert "task_id" in params, f"expected 'task_id' in params, got {params}"
|
||
|
||
|
||
def test_generate_video_preserves_original_function():
|
||
"""The original function wrapped by @celery_app.task must be named
|
||
'generate_video' — not '_sync_task_config_to_plan'."""
|
||
from worker_app.tasks.generation import generate_video
|
||
|
||
original = _get_original_function(generate_video)
|
||
assert original.__name__ == "generate_video", f"expected __name__='generate_video', got '{original.__name__}'"
|
||
|
||
|
||
def test_build_task_config_override_is_plain_function():
|
||
"""#2098: 原 _sync_task_config_to_plan 已拆分为 _build_task_config_override + _download_voice_for_task,
|
||
均为普通函数,不应被注册为 Celery task。"""
|
||
from worker_app.tasks.generation import _build_task_config_override, _download_voice_for_task
|
||
|
||
for fn in (_build_task_config_override, _download_voice_for_task):
|
||
assert not hasattr(fn, "run"), f"{fn.__name__} must be a plain function, not a Celery task"
|
||
|
||
# Bug A: override 对 title_config 做 key 归一化 (font_size→size, font_color→color)
|
||
override = _build_task_config_override(
|
||
{
|
||
"title_config": {"font_size": 48, "font_color": "#ff0000", "text": "hi"},
|
||
"bgm_config": {"url": "http://x/bgm.mp3"},
|
||
"output_width": 1080,
|
||
"output_height": 1920,
|
||
}
|
||
)
|
||
assert override["title"]["size"] == 48
|
||
assert override["title"]["color"] == "#ff0000"
|
||
assert override["bgm"]["url"] == "http://x/bgm.mp3"
|
||
assert override["export"]["resolution"] == "1080x1920"
|