Files
xiaoxia-saas/tests/unit/test_cover_frame_pre_extract.py
T
xiaoxia 05b35f2596
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 40s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m32s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m32s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m42s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 790h5m38s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 790h5m39s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 790h6m14s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 790h6m16s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 790h6m18s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 790h6m20s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 790h6m20s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 790h39m9s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 790h39m47s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 790h39m51s
fix: resolve CI code quality issues (unused import, broad exception)
2026-08-14 17:20:26 +08:00

141 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for unified cover frame extraction pipeline.
统一封面管道测试:
- extract_first_frame: 从已渲染视频抽取封面帧
- 封面天然带标题(ASS 字幕已烧录到视频中)
"""
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
class TestExtractFirstFrame(unittest.TestCase):
"""extract_first_frame 单元测试."""
@patch("video_processing.thumbnail_generator.run_ffmpeg")
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_extracts_frame_at_default_ratio(self, mock_probe, mock_run):
"""默认在视频 15% 处抽帧."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
result = extract_first_frame(video.name)
self.assertTrue(Path(result).exists())
# 验证 ffmpeg 被调用
mock_run.assert_called()
cmd = mock_run.call_args[0][0]
self.assertIn("-vframes", cmd)
self.assertIn("1", cmd)
Path(result).unlink(missing_ok=True)
@patch("video_processing.thumbnail_generator.run_ffmpeg")
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_custom_seek_ratio(self, mock_probe, mock_run):
"""自定义抽帧位置."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
result = extract_first_frame(video.name, seek_ratio=0.5)
self.assertTrue(Path(result).exists())
# 50% of 10s = 5s
cmd = mock_run.call_args[0][0]
ss_idx = cmd.index("-ss") + 1
seek_val = cmd[ss_idx]
# Should be around 5 seconds
self.assertIn("05", seek_val)
Path(result).unlink(missing_ok=True)
@patch("video_processing.thumbnail_generator.run_ffmpeg")
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_output_path_parameter(self, mock_probe, mock_run):
"""指定输出路径."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as out:
pass # just get a path
# Create the file so ffmpeg "succeeds"
mock_run.side_effect = lambda *a, **k: Path(out.name).write_bytes(b"fake image")
result = extract_first_frame(video.name, output_path=out.name)
self.assertEqual(result, out.name)
Path(out.name).unlink(missing_ok=True)
@patch("video_processing.thumbnail_generator.run_ffmpeg")
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_keeps_original_resolution_by_default(self, mock_probe, mock_run):
"""默认保持原始分辨率(width=-1, height=-1)."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
result = extract_first_frame(video.name)
cmd = mock_run.call_args[0][0]
vf_idx = cmd.index("-vf") + 1
vf_filter = cmd[vf_idx]
# Should NOT have scale filter (only format)
self.assertNotIn("scale", vf_filter)
self.assertIn("format", vf_filter)
Path(result).unlink(missing_ok=True)
@patch("video_processing.thumbnail_generator.run_ffmpeg")
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_custom_width_triggers_scale(self, mock_probe, mock_run):
"""指定宽度时添加 scale 滤镜."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
result = extract_first_frame(video.name, width=640)
cmd = mock_run.call_args[0][0]
vf_idx = cmd.index("-vf") + 1
vf_filter = cmd[vf_idx]
self.assertIn("scale=640", vf_filter)
Path(result).unlink(missing_ok=True)
@patch("video_processing.thumbnail_generator.run_ffmpeg", side_effect=Exception("fail"))
@patch("video_processing.thumbnail_generator.probe_duration", return_value=10.0)
def test_cleanup_temp_file_on_failure(self, mock_probe, mock_run):
"""失败时清理临时文件."""
from video_processing.thumbnail_generator import extract_first_frame
with tempfile.NamedTemporaryFile(suffix=".mp4") as video:
Path(video.name).write_bytes(b"fake video")
with self.assertRaises(RuntimeError):
extract_first_frame(video.name)
class TestRenderAdapterCoverUrl(unittest.TestCase):
"""RenderAdapterResult.cover_url 字段测试."""
def test_result_has_cover_url_field(self):
"""RenderAdapterResult 包含 cover_url 字段."""
from video_processing.render_adapter import RenderAdapterResult
result = RenderAdapterResult(success=True, cover_url="https://example.com/cover.jpg")
self.assertEqual(result.cover_url, "https://example.com/cover.jpg")
def test_result_cover_url_defaults_empty(self):
"""cover_url 默认为空字符串."""
from video_processing.render_adapter import RenderAdapterResult
result = RenderAdapterResult(success=True)
self.assertEqual(result.cover_url, "")
if __name__ == "__main__":
unittest.main()