""" render_audio 纯工具函数测试. 覆盖 clip_effective_duration / RenderContext 等纯逻辑. 核心混音逻辑依赖 FFmpeg,由集成测试覆盖. """ from __future__ import annotations from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock import pytest from video_processing.render_audio import RenderContext, clip_effective_duration class TestClipEffectiveDuration: """clip_effective_duration 有效时长计算.""" def test_duration_specified_and_actual_longer(self): """指定了 duration,且实际时长更长 → 取 duration""" clip = SimpleNamespace(duration=5.0, actual_duration=10.0) assert clip_effective_duration(clip) == 5.0 def test_duration_specified_and_actual_shorter(self): """指定了 duration,但实际时长更短 → 取实际时长""" clip = SimpleNamespace(duration=10.0, actual_duration=5.0) assert clip_effective_duration(clip) == 5.0 def test_duration_specified_actual_zero(self): """指定了 duration,但实际时长为 0 → 取 duration""" clip = SimpleNamespace(duration=5.0, actual_duration=0.0) assert clip_effective_duration(clip) == 5.0 def test_duration_specified_actual_negative(self): """指定了 duration,但实际时长为负 → 取 duration""" clip = SimpleNamespace(duration=5.0, actual_duration=-1.0) assert clip_effective_duration(clip) == 5.0 def test_no_duration_use_actual(self): """未指定 duration(0),用实际时长""" clip = SimpleNamespace(duration=0.0, actual_duration=8.0) assert clip_effective_duration(clip) == 8.0 def test_no_duration_negative_actual(self): """未指定 duration,实际时长也为负 → 返回 0""" clip = SimpleNamespace(duration=0.0, actual_duration=-5.0) assert clip_effective_duration(clip) == 0.0 def test_no_duration_zero_actual(self): """都为 0 → 返回 0""" clip = SimpleNamespace(duration=0.0, actual_duration=0.0) assert clip_effective_duration(clip) == 0.0 def test_negative_duration_use_actual(self): """duration 为负(视为未指定),用实际时长""" clip = SimpleNamespace(duration=-1.0, actual_duration=5.0) assert clip_effective_duration(clip) == 5.0 def test_both_negative_returns_zero(self): """两者都为负 → 返回 0""" clip = SimpleNamespace(duration=-2.0, actual_duration=-3.0) assert clip_effective_duration(clip) == 0.0 def test_exact_match(self): """duration 与实际时长相等""" clip = SimpleNamespace(duration=7.5, actual_duration=7.5) assert clip_effective_duration(clip) == 7.5 def test_very_short_duration(self): """很短的时长""" clip = SimpleNamespace(duration=0.1, actual_duration=0.2) assert clip_effective_duration(clip) == 0.1 class TestRenderContext: """RenderContext 渲染上下文.""" def test_create_with_work_dir_and_plan_id(self, tmp_path): ctx = RenderContext(work_dir=tmp_path, plan_id="plan_123") assert ctx.work_dir == tmp_path assert ctx.plan_id == "plan_123" assert ctx.noise_reduction_config is None assert ctx._audio_cache == {} def test_noise_reduction_config(self, tmp_path): config = {"enabled": True, "strength": 0.5} ctx = RenderContext( work_dir=tmp_path, plan_id="plan_123", noise_reduction_config=config, ) assert ctx.noise_reduction_config == config assert ctx.noise_reduction_config["enabled"] is True def test_audio_cache_isolation(self, tmp_path): """每个实例有独立的缓存字典.""" ctx1 = RenderContext(work_dir=tmp_path, plan_id="p1") ctx2 = RenderContext(work_dir=tmp_path, plan_id="p2") ctx1._audio_cache["key1"] = True assert "key1" not in ctx2._audio_cache assert len(ctx2._audio_cache) == 0 def test_work_dir_path_type(self, tmp_path): ctx = RenderContext(work_dir=tmp_path, plan_id="test") assert isinstance(ctx.work_dir, Path)