1a57878f76
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m12s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m17s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1m33s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m14s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
1. 未使用依赖清理:
- 从 requirements-base.txt 移除 cryptography 和 pyOpenSSL
2. pyflakes 警告清零 (apps/ + packages/ + tests/):
- 移除 17 处未使用的 import (F401)
- 修复 26 处未使用的局部变量 (F841):
* 有副作用的赋值转为裸调用
* 无副作用的赋值直接删除
- 修复 1 处未使用的异常变量 (F841)
- 修复 1 处空 except 块
3. 测试文件冗余清理:
- 删除 tests/integration/test_project_management.py (模块级 skip,测试不存在的模块)
- 删除 tests/integration/fixtures/duplication_routes_fixed.py (未被引用)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
235 lines
7.2 KiB
Python
235 lines
7.2 KiB
Python
"""全链路集成测试.
|
|
|
|
验证 PlanGeneratorService → UnifiedRenderService → 查重 的端到端流程。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from video_processing.unified_render_service import (
|
|
UnifiedRenderService,
|
|
)
|
|
from worker_app.tasks.generation import (
|
|
_build_plan_and_clips_from_task,
|
|
_create_fallback_clip,
|
|
_mux_audio_track,
|
|
)
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not shutil.which("ffmpeg"),
|
|
reason="ffmpeg not available",
|
|
)
|
|
|
|
|
|
def _generate_test_video(path: Path, duration: float = 3.0) -> None:
|
|
"""生成一个测试视频。"""
|
|
cmd = [
|
|
"ffmpeg",
|
|
"-y",
|
|
"-f",
|
|
"lavfi",
|
|
"-i",
|
|
f"color=c=blue:s=640x360:d={duration}:r=25",
|
|
"-c:v",
|
|
"libx264",
|
|
"-pix_fmt",
|
|
"yuv420p",
|
|
str(path),
|
|
]
|
|
subprocess.run(cmd, check=True, capture_output=True, timeout=30)
|
|
|
|
|
|
def _generate_test_audio(path: Path, duration: float = 5.0) -> None:
|
|
"""生成一个测试音频文件。"""
|
|
cmd = [
|
|
"ffmpeg",
|
|
"-y",
|
|
"-f",
|
|
"lavfi",
|
|
"-i",
|
|
f"sine=frequency=440:duration={duration}",
|
|
"-c:a",
|
|
"aac",
|
|
"-b:a",
|
|
"128k",
|
|
str(path),
|
|
]
|
|
subprocess.run(cmd, check=True, capture_output=True, timeout=30)
|
|
|
|
|
|
# ── 测试 _create_fallback_clip ────────────────────────────────────────────────
|
|
|
|
|
|
class TestFallbackClip:
|
|
"""测试 fallback 视频生成。"""
|
|
|
|
def test_fallback_clip_creates_video(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
output = Path(tmpdir) / "fallback.mp4"
|
|
_create_fallback_clip(output, "Test Fallback")
|
|
|
|
assert output.exists()
|
|
assert output.stat().st_size > 0
|
|
|
|
|
|
# ── 测试 _mux_audio_track ────────────────────────────────────────────────────
|
|
|
|
|
|
class TestMuxAudioTrack:
|
|
"""测试视频+音频混合。"""
|
|
|
|
def test_mux_audio_into_video(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
video_path = Path(tmpdir) / "video.mp4"
|
|
audio_path = Path(tmpdir) / "audio.aac"
|
|
output_path = Path(tmpdir) / "output.mp4"
|
|
|
|
_generate_test_video(video_path, duration=3.0)
|
|
_generate_test_audio(audio_path, duration=5.0)
|
|
|
|
_mux_audio_track(video_path, str(audio_path), output_path)
|
|
|
|
assert output_path.exists()
|
|
assert output_path.stat().st_size > 0
|
|
|
|
# 验证输出文件包含音频轨
|
|
probe_cmd = [
|
|
"ffprobe",
|
|
"-v",
|
|
"quiet",
|
|
"-show_streams",
|
|
"-select_streams",
|
|
"a",
|
|
"-of",
|
|
"csv=p=0",
|
|
str(output_path),
|
|
]
|
|
result = subprocess.run(probe_cmd, capture_output=True, text=True, timeout=10)
|
|
# 如果有音频流,输出非空
|
|
assert result.stdout.strip() != "" or result.returncode == 0
|
|
|
|
|
|
# ── 测试 PlanGenerator → UnifiedRenderService 全链路 ─────────────────────────
|
|
|
|
|
|
class TestFullPipeline:
|
|
"""验证从虚拟 plan 构建到渲染输出的完整流程。"""
|
|
|
|
def test_one_take_pipeline(self):
|
|
"""ONE_TAKE 模式完整流程。"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
work_dir = Path(tmpdir)
|
|
|
|
# 生成测试素材
|
|
paths = []
|
|
for i in range(3):
|
|
p = work_dir / f"clip_{i}.mp4"
|
|
_generate_test_video(p, duration=2.0)
|
|
paths.append(p)
|
|
|
|
# 构建虚拟 plan
|
|
plan, clips, asset_map = _build_plan_and_clips_from_task("pipeline_test", paths, "one_take")
|
|
|
|
# 渲染
|
|
service = UnifiedRenderService(
|
|
plan=plan,
|
|
clips=clips,
|
|
asset_path_map=asset_map,
|
|
work_dir=work_dir,
|
|
output_width=640,
|
|
output_height=360,
|
|
)
|
|
result = service.render()
|
|
|
|
assert result.output_path.exists()
|
|
assert result.duration > 0
|
|
assert result.file_size > 0
|
|
assert result.width == 640
|
|
assert result.height == 360
|
|
|
|
def test_pipeline_with_audio_mux(self):
|
|
"""渲染 + 混音后处理。"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
work_dir = Path(tmpdir)
|
|
|
|
# 生成测试素材
|
|
video_path = work_dir / "clip_0.mp4"
|
|
_generate_test_video(video_path, duration=3.0)
|
|
|
|
# 构建虚拟 plan
|
|
plan, clips, asset_map = _build_plan_and_clips_from_task("audio_test", [video_path], "one_take")
|
|
|
|
# 渲染
|
|
service = UnifiedRenderService(
|
|
plan=plan,
|
|
clips=clips,
|
|
asset_path_map=asset_map,
|
|
work_dir=work_dir,
|
|
output_width=640,
|
|
output_height=360,
|
|
)
|
|
render_result = service.render()
|
|
|
|
# 混音
|
|
audio_path = work_dir / "voice.aac"
|
|
_generate_test_audio(audio_path, duration=5.0)
|
|
|
|
final_path = work_dir / "final.mp4"
|
|
_mux_audio_track(render_result.output_path, str(audio_path), final_path)
|
|
|
|
assert final_path.exists()
|
|
assert final_path.stat().st_size > 0
|
|
|
|
def test_single_clip_pipeline(self):
|
|
"""单 clip 渲染(无转场)。"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
work_dir = Path(tmpdir)
|
|
|
|
video_path = work_dir / "single.mp4"
|
|
_generate_test_video(video_path, duration=5.0)
|
|
|
|
plan, clips, asset_map = _build_plan_and_clips_from_task("single_test", [video_path], "one_take")
|
|
|
|
service = UnifiedRenderService(
|
|
plan=plan,
|
|
clips=clips,
|
|
asset_path_map=asset_map,
|
|
work_dir=work_dir,
|
|
output_width=640,
|
|
output_height=360,
|
|
)
|
|
result = service.render()
|
|
|
|
assert result.output_path.exists()
|
|
assert result.duration > 0
|
|
|
|
def test_dedup_helper_integration(self):
|
|
"""验证 dedup_helpers.create_video_record_and_dedup 的导入和签名。"""
|
|
# 只验证函数存在且签名正确(不实际调用,需要数据库)
|
|
import inspect
|
|
|
|
from video_processing.dedup_helpers import create_video_record_and_dedup
|
|
|
|
sig = inspect.signature(create_video_record_and_dedup)
|
|
params = set(sig.parameters.keys())
|
|
expected = {
|
|
"generation_task_id",
|
|
"project_id",
|
|
"batch_id",
|
|
"file_url",
|
|
"file_size",
|
|
"duration",
|
|
"video_path",
|
|
"mode",
|
|
"session",
|
|
"width",
|
|
"height",
|
|
"fps",
|
|
}
|
|
assert expected.issubset(params), f"Missing params: {expected - params}"
|