"""#1664 随机边缘裁剪降重功能测试""" from __future__ import annotations import os import subprocess import sys from pathlib import Path from unittest.mock import MagicMock, call, patch import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "apps" / "worker")) sys.path.insert(0, str(ROOT / "apps" / "api")) sys.path.insert(0, str(ROOT / "packages")) from video_processing.ffmpeg_utils import random_edge_crop class TestRandomEdgeCropBasic: """基本功能测试""" def test_returns_input_path_when_output_none(self, tmp_path): """output_path=None 时覆盖原文件并返回 input_path""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") fake_info = {"width": 1920, "height": 1080, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg"), ): result = random_edge_crop(input_file) assert result == input_file def test_returns_output_path_when_specified(self, tmp_path): """指定 output_path 时返回该路径""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") output_file = tmp_path / "output.mp4" fake_info = {"width": 1920, "height": 1080, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg"), ): result = random_edge_crop(input_file, output_file) assert result == output_file def test_skip_when_invalid_resolution(self, tmp_path): """无法获取有效分辨率时跳过裁剪""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") fake_info = {"width": 0, "height": 0, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg") as mock_ffmpeg, ): result = random_edge_crop(input_file) assert result == input_file mock_ffmpeg.assert_not_called() class TestRandomEdgeCropFFmpeg: """FFmpeg 调用参数验证""" def test_ffmpeg_crop_and_scale_filter(self, tmp_path): """生成的 ffmpeg 滤镜包含 crop + scale""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") # 固定随机值以便验证 fake_info = {"width": 1000, "height": 1000, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg") as mock_ffmpeg, patch("random.uniform", side_effect=[0.03, 0.03, 0.03, 0.03]), ): random_edge_crop(input_file) mock_ffmpeg.assert_called_once() cmd = mock_ffmpeg.call_args[0][0] # 找到 -vf 参数 vf_idx = cmd.index("-vf") vf_value = cmd[vf_idx + 1] assert "crop=" in vf_value assert "scale=1000:1000" in vf_value def test_crop_amounts_within_range(self, tmp_path): """裁剪量在 2%~5% 范围内""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") fake_info = {"width": 1000, "height": 1000, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg") as mock_ffmpeg, patch("random.uniform", side_effect=[0.02, 0.05, 0.02, 0.05]), ): random_edge_crop(input_file) cmd = mock_ffmpeg.call_args[0][0] vf_idx = cmd.index("-vf") vf_value = cmd[vf_idx + 1] # crop_top=20, crop_bottom=50, crop_left=20, crop_right=50 # new_w = 1000-20-50 = 930, new_h = 1000-20-50 = 930 # x_offset = 20, y_offset = 20 assert "crop=930:930:20:20" in vf_value def test_uses_libx264_codec(self, tmp_path): """使用 libx264 编码""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") fake_info = {"width": 1920, "height": 1080, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg") as mock_ffmpeg, ): random_edge_crop(input_file) cmd = mock_ffmpeg.call_args[0][0] assert "-c:v" in cmd assert cmd[cmd.index("-c:v") + 1] == "libx264" class TestRandomEdgeCropErrorHandling: """错误处理测试""" def test_ffmpeg_failure_raises_exception(self, tmp_path): """ffmpeg 失败时抛出异常""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") fake_info = {"width": 1920, "height": 1080, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch( "video_processing.ffmpeg_utils.run_ffmpeg", side_effect=subprocess.CalledProcessError(1, "ffmpeg"), ), ): with pytest.raises(subprocess.CalledProcessError): random_edge_crop(input_file) def test_probe_failure_propagates(self, tmp_path): """probe_video_info 失败时异常传播""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") with patch( "video_processing.ffmpeg_utils.probe_video_info", side_effect=RuntimeError("probe failed"), ): with pytest.raises(RuntimeError, match="probe failed"): random_edge_crop(input_file) class TestRandomEdgeCropEvenDimensions: """偶数尺寸处理测试""" def test_odd_crop_dimensions_adjusted_to_even(self, tmp_path): """裁剪后尺寸为奇数时自动调整为偶数""" input_file = tmp_path / "input.mp4" input_file.write_bytes(b"fake video data") # 1000 - 3 (top) - 4 (bottom) = 993 → 调整为 992 # 1000 - 3 (left) - 4 (right) = 993 → 调整为 992 fake_info = {"width": 1000, "height": 1000, "duration": 10, "fps": 30} with ( patch("video_processing.ffmpeg_utils.probe_video_info", return_value=fake_info), patch("video_processing.ffmpeg_utils.run_ffmpeg") as mock_ffmpeg, # side_effect 控制 uniform 返回值 # top: 0.003*1000=3, bottom: 0.004*1000=4, left: 0.003*1000=3, right: 0.004*1000=4 ): # 使用自定义 uniform 返回特定值 def fake_uniform(low, high): # 返回特定百分比使得裁剪后尺寸为奇数 # 我们需要 crop_top=3, crop_bottom=4, crop_left=3, crop_right=4 return 0.0035 # 近似值 # 更简单的方式:直接 mock int(H * random.uniform(...)) 的结果 # 但我们直接测试最终 crop 滤镜即可 with patch("random.uniform", side_effect=[0.021, 0.022, 0.021, 0.022]): random_edge_crop(input_file) cmd = mock_ffmpeg.call_args[0][0] vf_idx = cmd.index("-vf") vf_value = cmd[vf_idx + 1] # 提取 crop 参数并验证都是偶数 import re crop_match = re.search(r"crop=(\d+):(\d+)", vf_value) assert crop_match crop_w = int(crop_match.group(1)) crop_h = int(crop_match.group(2)) assert crop_w % 2 == 0, f"crop width {crop_w} should be even" assert crop_h % 2 == 0, f"crop height {crop_h} should be even"