Files
xiaoxia-saas/tests/unit/test_render_audio_pure.py
T
CI Bot 7a72cfd709
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 / Validate - Migration (alembic) (push) Successful in 2m9s
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 / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Successful in 8m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 27m2s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 30m15s
style: auto-format with black + isort + prettier
2026-07-25 00:16:10 +00:00

156 lines
5.1 KiB
Python
Executable File

"""渲染音频纯逻辑测试 — clip_effective_duration + clip_has_audio缓存."""
from __future__ import annotations
from dataclasses import field
from pathlib import Path
from unittest.mock import patch
import pytest
from video_processing.render_audio import (
RenderContext,
clip_effective_duration,
clip_has_audio,
)
from video_processing.unified_render_service import ResolvedClip
def _make_ctx(work_dir: str = "/tmp") -> RenderContext:
"""创建测试用RenderContext."""
return RenderContext(work_dir=Path(work_dir), plan_id="test_plan")
def _make_clip(
*,
duration: float = 0.0,
actual_duration: float = 0.0,
local_path: str = "/tmp/test.mp4",
clip_type: str = "main",
clip_id: str = "c1",
asset_id: str = "a1",
order: int = 0,
) -> ResolvedClip:
"""快速创建测试用ResolvedClip."""
return ResolvedClip(
clip_id=clip_id,
asset_id=asset_id,
local_path=Path(local_path),
clip_type=clip_type,
order=order,
duration=duration,
actual_duration=actual_duration,
)
class TestClipEffectiveDuration:
"""clip_effective_duration 有效时长计算测试."""
def test_both_zero_returns_zero(self):
"""duration和actual_duration都是0 → 0."""
clip = _make_clip(duration=0, actual_duration=0)
assert clip_effective_duration(clip) == 0.0
def test_only_actual_duration(self):
"""只有actual_duration → 返回actual_duration."""
clip = _make_clip(duration=0, actual_duration=30.0)
assert clip_effective_duration(clip) == pytest.approx(30.0)
def test_duration_less_than_actual(self):
"""duration < actual → 返回duration(剪辑后的时长)."""
clip = _make_clip(duration=10.0, actual_duration=30.0)
assert clip_effective_duration(clip) == pytest.approx(10.0)
def test_duration_greater_than_actual(self):
"""duration > actual → 返回actual(不能超过素材时长)."""
clip = _make_clip(duration=50.0, actual_duration=30.0)
assert clip_effective_duration(clip) == pytest.approx(30.0)
def test_duration_equals_actual(self):
"""duration == actual → 返回该值."""
clip = _make_clip(duration=20.0, actual_duration=20.0)
assert clip_effective_duration(clip) == pytest.approx(20.0)
def test_no_actual_with_positive_duration(self):
"""actual_duration=0但duration>0 → 返回duration(还没probe时)."""
clip = _make_clip(duration=15.0, actual_duration=0.0)
assert clip_effective_duration(clip) == pytest.approx(15.0)
class TestClipHasAudio:
"""clip_has_audio 音频探测+缓存测试."""
def test_has_audio_true(self):
"""有音频时返回True."""
ctx = _make_ctx()
clip = _make_clip(local_path="/tmp/video1.mp4")
with patch("video_processing.render_audio.probe_has_audio", return_value=True):
result = clip_has_audio(ctx, clip)
assert result is True
def test_has_audio_false(self):
"""无音频时返回False."""
ctx = _make_ctx()
clip = _make_clip(local_path="/tmp/video2.mp4")
with patch("video_processing.render_audio.probe_has_audio", return_value=False):
result = clip_has_audio(ctx, clip)
assert result is False
def test_cache_avoids_reprobe(self):
"""同一个clip多次调用只probe一次(缓存生效)."""
ctx = _make_ctx()
clip = _make_clip(local_path="/tmp/cached.mp4")
call_count = 0
def fake_probe(path):
nonlocal call_count
call_count += 1
return True
with patch("video_processing.render_audio.probe_has_audio", side_effect=fake_probe):
result1 = clip_has_audio(ctx, clip)
result2 = clip_has_audio(ctx, clip)
result3 = clip_has_audio(ctx, clip)
assert result1 is True
assert result2 is True
assert result3 is True
assert call_count == 1 # 只调用了一次
def test_different_clips_both_probed(self):
"""不同clip各自probe一次."""
ctx = _make_ctx()
clip1 = _make_clip(clip_id="c1", local_path="/tmp/v1.mp4")
clip2 = _make_clip(clip_id="c2", local_path="/tmp/v2.mp4")
probe_call_count = 0
def fake_probe(path):
nonlocal probe_call_count
probe_call_count += 1
return "v1" in str(path)
with patch("video_processing.render_audio.probe_has_audio", side_effect=fake_probe):
r1 = clip_has_audio(ctx, clip1)
r2 = clip_has_audio(ctx, clip2)
assert r1 is True
assert r2 is False
assert probe_call_count == 2
class TestRenderContext:
"""RenderContext 渲染上下文测试."""
def test_default_noise_reduction_none(self):
"""默认无降噪配置."""
ctx = _make_ctx()
assert ctx.noise_reduction_config is None
def test_cache_starts_empty(self):
"""音频缓存初始为空."""
ctx = _make_ctx()
assert ctx._audio_cache == {}