Files
xiaoxia-saas/tests/unit/test_1280_preview_speedup.py
xiaoxia c873bb635f
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 Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 55s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m13s
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 / Validate - Migration (alembic) (push) Successful in 2m43s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m46s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 3m6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m14s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
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
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m53s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m6s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m6s
CI/CD Pipeline / Build Staging API Image (push) Successful in 7m12s
AI Code Review / AI Code Review (pull_request) Failing after 6m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 39s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m57s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m2s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 8m55s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Integration Tests (push) Successful in 3m12s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m13s
CI/CD Pipeline / Unit Tests (push) Successful in 14m24s
CI/CD Pipeline / CI Gate (push) Has been skipped
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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m30s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 8s
refactor: 统一渲染链路,预览即所得(-3370行) (#1479)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-08-24 14:16:00 +08:00

165 lines
5.5 KiB
Python

"""统一渲染管线 — 预览与确认生成使用相同品质参数。
验证点:
1. UnifiedRenderService 不再有 is_preview 参数
2. 所有渲染统一使用 medium preset + CRF 23
3. RenderAdapter 统一执行校验和缩略图生成
4. generation.py 并行下载逻辑(保留)
"""
from __future__ import annotations
import os
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
# ── 1. UnifiedRenderService 无 is_preview 参数 ──
class TestUnifiedRenderServiceNoPreviewParam:
"""UnifiedRenderService 构造函数不再接受 is_preview 参数。"""
def test_constructor_has_no_is_preview(self):
import inspect
from video_processing.unified_render_service import UnifiedRenderService
sig = inspect.signature(UnifiedRenderService.__init__)
param_names = list(sig.parameters.keys())
assert (
"is_preview" not in param_names
), f"is_preview should be removed from UnifiedRenderService.__init__, found params: {param_names}"
def test_no_is_preview_attribute(self):
from video_processing.unified_render_service import UnifiedRenderService
svc = UnifiedRenderService(
plan=MagicMock(id="test"),
clips=[],
asset_path_map={},
work_dir=Path(tempfile.mkdtemp()),
)
assert not hasattr(
svc, "is_preview"
), "UnifiedRenderService should not have is_preview attribute after unification"
# ── 2. FFmpeg 参数统一为 medium + CRF 23 ──
class TestUnifiedFFmpegPreset:
"""所有渲染统一使用 medium preset + CRF 23。"""
def _make_clip(self):
from video_processing.unified_render_service import ResolvedClip
return ResolvedClip(
clip_id="c1",
asset_id="a1",
local_path=Path("/tmp/fake.mp4"),
clip_type="main",
order=0,
start_time=0,
duration=10.0,
playback_speed=1.0,
transition_effect="cut",
transition_duration=0.0,
config={},
)
@patch("video_processing.unified_render_service.run_ffmpeg")
def test_execute_ffmpeg_uses_medium_crf23(self, mock_run):
from video_processing.unified_render_service import (
RenderLayer,
UnifiedRenderService,
)
plan = MagicMock()
plan.id = "test_plan"
plan.config = {}
clip = self._make_clip()
svc = UnifiedRenderService(
plan=plan,
clips=[clip],
asset_path_map={"a1": Path("/tmp/fake.mp4")},
work_dir=Path(tempfile.mkdtemp()),
output_width=1920,
output_height=1080,
)
layers = [RenderLayer(role="main", clips=[clip])]
filter_complex, input_args = svc._build_filter_complex(layers)
output_path = Path(tempfile.mkdtemp()) / "out.mp4"
svc._execute_ffmpeg(filter_complex, input_args, output_path)
mock_run.assert_called_once()
cmd = mock_run.call_args[0][0]
# Check preset is medium (no conditional)
preset_idx = cmd.index("-preset")
assert cmd[preset_idx + 1] == "medium", f"Expected medium, got {cmd[preset_idx + 1]}"
# Check crf is 23 (no conditional)
crf_idx = cmd.index("-crf")
assert cmd[crf_idx + 1] == "23", f"Expected crf 23, got {cmd[crf_idx + 1]}"
# ── 3. RenderAdapter 统一执行校验和缩略图 ──
class TestRenderAdapterUnifiedPostProcess:
"""RenderAdapter 不再跳过校验和缩略图。"""
def test_render_adapter_no_is_preview_param(self):
import inspect
from video_processing.render_adapter import RenderAdapter
# Check render_from_memory signature
sig = inspect.signature(RenderAdapter.render_from_memory)
param_names = list(sig.parameters.keys())
assert (
"is_preview" not in param_names
), f"is_preview should be removed from render_from_memory, found params: {param_names}"
def test_no_preview_skip_validation_in_source(self):
"""渲染适配器源码中不再包含预览跳过校验的逻辑。"""
with open("apps/worker/video_processing/render_adapter.py") as f:
source = f.read()
assert "预览模式:跳过输出校验" not in source, "Should not skip validation in any mode"
assert "if not is_preview:" not in source, "Thumbnail should always be generated"
# ── 4. generation.py 不再有 is_preview 覆盖逻辑 ──
class TestWorkerGenerationNoPreviewOverride:
"""Worker generation.py 不再覆盖预览分辨率为 480p。"""
def test_no_480p_override(self):
with open("apps/worker/worker_app/tasks/generation.py") as f:
source = f.read()
assert 'resolution = "854x480"' not in source, "Should not override resolution to 480p in preview mode"
assert 'bitrate = "1M"' not in source, "Should not override bitrate to 1M in preview mode"
# ── 5. generation_preview.py 不再有 PREVIEW_RESOLUTION ──
class TestPreviewNoLowQualityConstants:
"""预览 API 不再定义低质量常量。"""
def test_no_preview_resolution_constant(self):
with open("apps/api/app/api/routes/generation_preview.py") as f:
source = f.read()
assert "PREVIEW_RESOLUTION" not in source, "PREVIEW_RESOLUTION constant should be removed"
assert "_calc_preview_resolution" not in source, "_calc_preview_resolution function should be removed"