c575a4b835
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m55s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m52s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m27s
CI/CD Pipeline / Unit Tests (push) Failing after 6m39s
CI/CD Pipeline / Integration Tests (push) Successful in 2m41s
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m0s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m49s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m11s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 5s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m11s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m28s
108 lines
4.1 KiB
Python
108 lines
4.1 KiB
Python
"""
|
||
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)
|