Files
xiaoxia-saas/tests/unit/test_cover_url_finalize.py
xiaoxia c3ac621ebd
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m56s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m31s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m20s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m55s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m25s
CI/CD Pipeline / Unit Tests (push) Successful in 11m25s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (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 / Build Staging Worker Image (push) Successful in 13m21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 28m52s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 40s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m0s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m21s
fix: 预览渲染完成后回写 cover_url 到 GenerationTask (#1412)
2026-08-18 00:07:49 +08:00

133 lines
4.7 KiB
Python

"""Tests for cover_url backfill to GenerationTask.
Verifies _finalize_render_success correctly writes cover_url
from cover_candidates to gen_task.cover_url.
"""
from __future__ import annotations
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
# Add worker app to sys.path
_WORKER_ROOT = Path(__file__).resolve().parents[2] / "apps" / "worker"
if str(_WORKER_ROOT) not in sys.path:
sys.path.insert(0, str(_WORKER_ROOT))
class FakeGenTask:
"""Simple stand-in for GenerationTask that tracks attribute assignment."""
def __init__(self):
object.__setattr__(self, "_assigned", {})
self.id = "task-1"
self.status = MagicMock()
self.status.value = "running"
def __setattr__(self, name, value):
if not name.startswith("_"):
self._assigned[name] = value
object.__setattr__(self, name, value)
def append_log(self, **kwargs):
pass
def _make_plan():
plan = MagicMock()
plan.project_id = "proj-1"
plan.created_by_user_id = "user-1"
plan.config = {"batch_id": "batch-1", "mode": "edit_plan", "title": {"text": "test"}}
plan.mark_completed = MagicMock()
return plan
def _call_finalize(cover_candidates=None, gen_task=None, plan=None):
from worker_app.tasks.edit_plan_generation import _finalize_render_success
plan = plan or _make_plan()
gen_task = gen_task or FakeGenTask()
plan_repo = MagicMock()
clip_repo = MagicMock()
gen_task_repo = MagicMock()
gen_task_repo.get.return_value = gen_task
db = MagicMock()
with patch("worker_app.tasks.edit_plan_generation.create_video_record_and_dedup"):
result = _finalize_render_success(
plan=plan,
plan_repo=plan_repo,
clip_repo=clip_repo,
gen_task_repo=gen_task_repo,
db=db,
plan_id="plan-1",
output_url="https://oss.example.com/output.mp4",
storage_key="rendered/plan-1/task-1.mp4",
duration=10.0,
file_size=1024,
width=1280,
height=720,
rendered_clip_ids=["clip-1"],
failed_clip_ids=[],
generation_task_id="task-1",
output_path=Path("/tmp/output.mp4"),
engine="unified",
thumbnail_url="",
cover_candidates=cover_candidates,
)
return result, gen_task, gen_task_repo
class TestFinalizeCoverUrl:
def test_cover_url_set_from_image_url(self):
"""cover_candidates with image_url should set gen_task.cover_url"""
candidates = [
{"image_url": "https://oss.example.com/cover1.jpg", "frame_time": 1.5},
{"image_url": "https://oss.example.com/cover2.jpg", "frame_time": 3.0},
]
_, gen_task, gen_task_repo = _call_finalize(cover_candidates=candidates)
assert gen_task.cover_url == "https://oss.example.com/cover1.jpg"
gen_task_repo.update.assert_called()
def test_cover_url_fallback_to_url_key(self):
"""Should fallback to 'url' key when 'image_url' is absent"""
candidates = [{"url": "https://oss.example.com/cover_url_key.jpg"}]
_, gen_task, _ = _call_finalize(cover_candidates=candidates)
assert gen_task.cover_url == "https://oss.example.com/cover_url_key.jpg"
def test_cover_url_not_set_when_empty_list(self):
"""Empty cover_candidates should not set cover_url"""
_, gen_task, _ = _call_finalize(cover_candidates=[])
assert "cover_url" not in gen_task._assigned
def test_cover_url_not_set_when_none(self):
"""None cover_candidates should not set cover_url"""
_, gen_task, _ = _call_finalize(cover_candidates=None)
assert "cover_url" not in gen_task._assigned
def test_cover_url_not_set_when_url_empty(self):
"""Empty URL strings in candidates should not set cover_url"""
candidates = [{"image_url": "", "url": ""}]
_, gen_task, _ = _call_finalize(cover_candidates=candidates)
assert "cover_url" not in gen_task._assigned
def test_no_generation_task_no_crash(self):
"""Should not crash when gen_task is None"""
candidates = [{"image_url": "https://oss.example.com/cover.jpg"}]
gen_task_repo = MagicMock()
gen_task_repo.get.return_value = None
result, _, _ = _call_finalize(cover_candidates=candidates)
assert result["status"] == "completed"
def test_image_url_priority_over_url(self):
"""image_url should take priority over url key"""
candidates = [{"image_url": "https://a.jpg", "url": "https://b.jpg"}]
_, gen_task, _ = _call_finalize(cover_candidates=candidates)
assert gen_task.cover_url == "https://a.jpg"