"""ReverseEngine 纯逻辑单测 — 配置解析 + 滤镜构建 + 安全限制. 全纯函数测试,不依赖 FFmpeg。 """ from __future__ import annotations from unittest.mock import patch import pytest from video_processing.reverse_engine import ReverseConfig, ReverseEngine # ── ReverseConfig 解析 ──────────────────────────────────────────────────── class TestReverseConfigFromDict: """ReverseConfig.from_dict 配置解析测试.""" def test_none_returns_disabled(self): """None 输入返回 disabled 默认配置.""" config = ReverseConfig.from_dict(None) assert config.enabled is False assert config.reverse_video is True assert config.reverse_audio is True def test_empty_dict_returns_disabled(self): """空 dict 返回 disabled.""" config = ReverseConfig.from_dict({}) assert config.enabled is False def test_enabled_false_returns_disabled(self): """显式 enabled=False.""" config = ReverseConfig.from_dict({"enabled": False}) assert config.enabled is False def test_enabled_default_flags_default_video_audio(self): """只启用时默认视频音频都倒放.""" config = ReverseConfig.from_dict({"enabled": True}) assert config.enabled is True assert config.reverse_video is True assert config.reverse_audio is True def test_video_only(self): """只倒放视频.""" config = ReverseConfig.from_dict( { "enabled": True, "reverse_video": True, "reverse_audio": False, } ) assert config.enabled is True assert config.reverse_video is True assert config.reverse_audio is False def test_audio_only(self): """只倒放音频.""" config = ReverseConfig.from_dict( { "enabled": True, "reverse_video": False, "reverse_audio": True, } ) assert config.enabled is True assert config.reverse_video is False assert config.reverse_audio is True def test_both_disabled_but_enabled_flag_true(self): """enabled=True 但两个子选项都关了(边缘情况).""" config = ReverseConfig.from_dict( { "enabled": True, "reverse_video": False, "reverse_audio": False, } ) assert config.enabled is True assert config.reverse_video is False assert config.reverse_audio is False def test_invalid_type_falls_back(self): """非 dict 类型回退到默认 disabled.""" config = ReverseConfig.from_dict("reverse=true") assert config.enabled is False def test_attribute_error_falls_back(self): """属性错误时回退到默认.""" class WeirdObj: def get(self, key, default=None): raise AttributeError("nope") config = ReverseConfig.from_dict(WeirdObj()) assert config.enabled is False def test_type_error_falls_back(self): """类型错误时回退到默认.""" config = ReverseConfig.from_dict([1, 2, 3]) assert config.enabled is False # ── ReverseEngine 视频滤镜 ────────────────────────────────────────────────── class TestReverseEngineBuildVideoFilter: """ReverseEngine.build_video_filter 视频滤镜测试.""" def test_disabled_returns_empty(self): """disabled 返回空字符串.""" config = ReverseConfig(enabled=False) assert ReverseEngine.build_video_filter(config) == "" def test_enabled_returns_reverse(self): """启用返回 reverse 滤镜.""" config = ReverseConfig(enabled=True, reverse_video=True) assert ReverseEngine.build_video_filter(config) == "reverse" def test_video_disabled_returns_empty(self): """reverse_video=False 返回空.""" config = ReverseConfig(enabled=True, reverse_video=False, reverse_audio=True) assert ReverseEngine.build_video_filter(config) == "" def test_short_duration_ok(self): """短时长正常返回 reverse.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_video_filter(config, duration=10.0) assert result == "reverse" def test_exactly_max_duration_ok(self): """恰好等于安全上限正常.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_video_filter(config, duration=ReverseEngine.MAX_SAFE_DURATION) assert result == "reverse" def test_over_max_duration_skipped(self): """超过安全时长跳过倒放.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_video_filter(config, duration=200.0) assert result == "" def test_zero_duration_ok(self): """零时长正常倒放.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_video_filter(config, duration=0.0) assert result == "reverse" # ── ReverseEngine 音频滤镜 ────────────────────────────────────────────── class TestReverseEngineBuildAudioFilter: """ReverseEngine.build_audio_filter 音频滤镜测试.""" def test_disabled_returns_empty(self): """disabled 返回空字符串.""" config = ReverseConfig(enabled=False) assert ReverseEngine.build_audio_filter(config) == "" def test_enabled_returns_areverse(self): """启用返回 areverse 滤镜.""" config = ReverseConfig(enabled=True, reverse_audio=True) assert ReverseEngine.build_audio_filter(config) == "areverse" def test_audio_disabled_returns_empty(self): """reverse_audio=False 返回空.""" config = ReverseConfig(enabled=True, reverse_video=True, reverse_audio=False) assert ReverseEngine.build_audio_filter(config) == "" def test_short_duration_ok(self): """短时长正常返回 areverse.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_audio_filter(config, duration=30.0) assert result == "areverse" def test_over_max_duration_skipped(self): """超过安全时长跳过音频倒放.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_audio_filter(config, duration=150.0) assert result == "" def test_exactly_max_duration_ok(self): """恰好等于安全上限正常.""" config = ReverseConfig(enabled=True) result = ReverseEngine.build_audio_filter(config, duration=ReverseEngine.MAX_SAFE_DURATION) assert result == "areverse" # ── 组合场景 ──────────────────────────────────────────────────────────── class TestReverseEngineCombined: """组合场景测试.""" def test_both_video_audio_reverse(self): """视频音频都倒放.""" config = ReverseConfig(enabled=True) vf = ReverseEngine.build_video_filter(config) af = ReverseEngine.build_audio_filter(config) assert vf == "reverse" assert af == "areverse" def test_neither_video_nor_audio(self): """都不倒放.""" config = ReverseConfig(enabled=True, reverse_video=False, reverse_audio=False) assert ReverseEngine.build_video_filter(config) == "" assert ReverseEngine.build_audio_filter(config) == "" def test_long_video_both_skipped(self): """超长视频两个都跳过.""" config = ReverseConfig(enabled=True) duration = ReverseEngine.MAX_SAFE_DURATION + 1 assert ReverseEngine.build_video_filter(config, duration=duration) == "" assert ReverseEngine.build_audio_filter(config, duration=duration) == "" def test_from_dict_full_config_flow(self): """从 dict 解析到滤镜构建的完整流程.""" data = {"enabled": True, "reverse_video": True, "reverse_audio": False} config = ReverseConfig.from_dict(data) assert ReverseEngine.build_video_filter(config, duration=10) == "reverse" assert ReverseEngine.build_audio_filter(config, duration=10) == "" def test_from_dict_disabled_flow(self): """disabled 配置完整流程.""" config = ReverseConfig.from_dict({"enabled": False}) assert ReverseEngine.build_video_filter(config) == "" assert ReverseEngine.build_audio_filter(config) == ""