9eca947f88
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
314 lines
9.9 KiB
Python
314 lines
9.9 KiB
Python
"""speed_config 领域模型单测."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from packages.domain.speed_config import (
|
|
DEFAULT_SPEED,
|
|
MAX_SPEED,
|
|
MIN_SPEED,
|
|
SpeedConfig,
|
|
adjust_duration,
|
|
build_audio_filter,
|
|
build_clip_speed_filter,
|
|
build_video_filter,
|
|
resolve_clip_speed,
|
|
)
|
|
|
|
# ── 常量测试 ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestConstants:
|
|
def test_min_speed(self):
|
|
assert MIN_SPEED == 0.25
|
|
|
|
def test_max_speed(self):
|
|
assert MAX_SPEED == 4.0
|
|
|
|
def test_default_speed(self):
|
|
assert DEFAULT_SPEED == 1.0
|
|
|
|
|
|
# ── SpeedConfig.parse 测试 ────────────────────────────────────────────────
|
|
|
|
|
|
class TestSpeedConfigParse:
|
|
def test_none_returns_default(self):
|
|
cfg = SpeedConfig.parse(None)
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
assert cfg.pitch_correct is True
|
|
|
|
def test_empty_dict_returns_default(self):
|
|
cfg = SpeedConfig.parse({})
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_invalid_type_returns_default(self):
|
|
cfg = SpeedConfig.parse("not_a_dict")
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_valid_speed(self):
|
|
cfg = SpeedConfig.parse({"speed": 2.0})
|
|
assert cfg.speed == 2.0
|
|
|
|
def test_speed_clamped_low(self):
|
|
cfg = SpeedConfig.parse({"speed": 0.1})
|
|
assert cfg.speed == MIN_SPEED
|
|
|
|
def test_speed_clamped_high(self):
|
|
cfg = SpeedConfig.parse({"speed": 5.0})
|
|
assert cfg.speed == MAX_SPEED
|
|
|
|
def test_zero_speed_returns_default(self):
|
|
cfg = SpeedConfig.parse({"speed": 0})
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_negative_speed_returns_default(self):
|
|
cfg = SpeedConfig.parse({"speed": -1.0})
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_pitch_correct_false(self):
|
|
cfg = SpeedConfig.parse({"pitch_correct": False})
|
|
assert cfg.pitch_correct is False
|
|
|
|
def test_pitch_correct_invalid_type_defaults_true(self):
|
|
cfg = SpeedConfig.parse({"pitch_correct": "yes"})
|
|
assert cfg.pitch_correct is True
|
|
|
|
def test_string_speed_invalid_uses_default(self):
|
|
cfg = SpeedConfig.parse({"speed": "fast"})
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
|
|
# ── SpeedConfig.clamp 测试 ────────────────────────────────────────────────
|
|
|
|
|
|
class TestClamp:
|
|
def test_already_valid_unchanged(self):
|
|
cfg = SpeedConfig(speed=1.5)
|
|
cfg.clamp()
|
|
assert cfg.speed == 1.5
|
|
|
|
def test_below_min_clamped(self):
|
|
cfg = SpeedConfig(speed=0.1)
|
|
cfg.clamp()
|
|
assert cfg.speed == MIN_SPEED
|
|
|
|
def test_above_max_clamped(self):
|
|
cfg = SpeedConfig(speed=10.0)
|
|
cfg.clamp()
|
|
assert cfg.speed == MAX_SPEED
|
|
|
|
def test_zero_defaults(self):
|
|
cfg = SpeedConfig(speed=0.0)
|
|
cfg.clamp()
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_negative_defaults(self):
|
|
cfg = SpeedConfig(speed=-2.0)
|
|
cfg.clamp()
|
|
assert cfg.speed == DEFAULT_SPEED
|
|
|
|
def test_exact_min_stays(self):
|
|
cfg = SpeedConfig(speed=MIN_SPEED)
|
|
cfg.clamp()
|
|
assert cfg.speed == MIN_SPEED
|
|
|
|
def test_exact_max_stays(self):
|
|
cfg = SpeedConfig(speed=MAX_SPEED)
|
|
cfg.clamp()
|
|
assert cfg.speed == MAX_SPEED
|
|
|
|
|
|
# ── is_original / is_fast / is_slow 测试 ─────────────────────────────────
|
|
|
|
|
|
class TestSpeedProperties:
|
|
def test_is_original_true(self):
|
|
cfg = SpeedConfig(speed=1.0)
|
|
assert cfg.is_original is True
|
|
|
|
def test_is_original_false_fast(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert cfg.is_original is False
|
|
|
|
def test_is_original_false_slow(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
assert cfg.is_original is False
|
|
|
|
def test_is_original_near_one(self):
|
|
cfg = SpeedConfig(speed=1.0000001)
|
|
assert cfg.is_original is True
|
|
|
|
def test_is_fast_true(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert cfg.is_fast is True
|
|
|
|
def test_is_fast_false(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
assert cfg.is_fast is False
|
|
|
|
def test_is_false_for_original(self):
|
|
cfg = SpeedConfig(speed=1.0)
|
|
assert cfg.is_fast is False
|
|
assert cfg.is_slow is False
|
|
|
|
def test_is_slow_true(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
assert cfg.is_slow is True
|
|
|
|
def test_is_slow_false(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert cfg.is_slow is False
|
|
|
|
|
|
# ── build_video_filter 测试 ───────────────────────────────────────────────
|
|
|
|
|
|
class TestBuildVideoFilter:
|
|
def test_original_speed_empty(self):
|
|
cfg = SpeedConfig(speed=1.0)
|
|
assert build_video_filter(cfg) == ""
|
|
|
|
def test_fast_speed_setpts(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
result = build_video_filter(cfg)
|
|
assert "setpts=PTS/2.0000" in result
|
|
|
|
def test_slow_speed_setpts(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
result = build_video_filter(cfg)
|
|
assert "setpts=PTS/0.5000" in result
|
|
|
|
def test_format_four_decimals(self):
|
|
cfg = SpeedConfig(speed=1.5)
|
|
result = build_video_filter(cfg)
|
|
assert "1.5000" in result
|
|
|
|
|
|
# ── build_audio_filter 测试 ───────────────────────────────────────────────
|
|
|
|
|
|
class TestBuildAudioFilter:
|
|
def test_original_speed_empty(self):
|
|
cfg = SpeedConfig(speed=1.0)
|
|
assert build_audio_filter(cfg) == ""
|
|
|
|
def test_single_stage_within_range(self):
|
|
cfg = SpeedConfig(speed=1.5)
|
|
result = build_audio_filter(cfg)
|
|
assert result == "atempo=1.5000"
|
|
assert result.count("atempo") == 1
|
|
|
|
def test_fast_two_stages(self):
|
|
cfg = SpeedConfig(speed=3.0)
|
|
result = build_audio_filter(cfg)
|
|
assert result.count("atempo") == 2
|
|
# 2.0 * 1.5 = 3.0
|
|
assert "atempo=2.0000" in result
|
|
assert "atempo=1.5000" in result
|
|
|
|
def test_max_speed_two_stages(self):
|
|
cfg = SpeedConfig(speed=4.0)
|
|
result = build_audio_filter(cfg)
|
|
assert result.count("atempo") == 2
|
|
# 2.0 * 2.0 = 4.0
|
|
assert result == "atempo=2.0000,atempo=2.0000"
|
|
|
|
def test_slow_two_stages(self):
|
|
cfg = SpeedConfig(speed=0.25)
|
|
result = build_audio_filter(cfg)
|
|
assert result.count("atempo") == 2
|
|
# 0.5 * 0.5 = 0.25
|
|
assert result == "atempo=0.5000,atempo=0.5000"
|
|
|
|
def test_slow_single_stage(self):
|
|
cfg = SpeedConfig(speed=0.8)
|
|
result = build_audio_filter(cfg)
|
|
assert result == "atempo=0.8000"
|
|
assert result.count("atempo") == 1
|
|
|
|
def test_exactly_two_point_zero_single(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
result = build_audio_filter(cfg)
|
|
assert result.count("atempo") == 1
|
|
assert "atempo=2.0000" in result
|
|
|
|
def test_exactly_half_single(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
result = build_audio_filter(cfg)
|
|
assert result.count("atempo") == 1
|
|
assert "atempo=0.5000" in result
|
|
|
|
|
|
# ── adjust_duration 测试 ──────────────────────────────────────────────────
|
|
|
|
|
|
class TestAdjustDuration:
|
|
def test_original_speed_unchanged(self):
|
|
cfg = SpeedConfig(speed=1.0)
|
|
assert adjust_duration(10.0, cfg) == 10.0
|
|
|
|
def test_double_speed_halved(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert adjust_duration(10.0, cfg) == 5.0
|
|
|
|
def test_half_speed_doubled(self):
|
|
cfg = SpeedConfig(speed=0.5)
|
|
assert adjust_duration(10.0, cfg) == 20.0
|
|
|
|
def test_zero_duration_unchanged(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert adjust_duration(0.0, cfg) == 0.0
|
|
|
|
def test_negative_duration_unchanged(self):
|
|
cfg = SpeedConfig(speed=2.0)
|
|
assert adjust_duration(-5.0, cfg) == -5.0
|
|
|
|
|
|
# ── build_clip_speed_filter 测试 ─────────────────────────────────────────
|
|
|
|
|
|
class TestBuildClipSpeedFilter:
|
|
def test_normal_speed(self):
|
|
vf, af, cfg = build_clip_speed_filter(2.0)
|
|
assert vf == "setpts=PTS/2.0000"
|
|
assert "atempo=2.0000" in af
|
|
assert cfg.speed == 2.0
|
|
|
|
def test_clamped_speed(self):
|
|
vf, af, cfg = build_clip_speed_filter(10.0)
|
|
assert cfg.speed == MAX_SPEED
|
|
|
|
def test_pitch_correct_param(self):
|
|
vf, af, cfg = build_clip_speed_filter(1.5, pitch_correct=False)
|
|
assert cfg.pitch_correct is False
|
|
|
|
def test_original_speed_empty_filters(self):
|
|
vf, af, cfg = build_clip_speed_filter(1.0)
|
|
assert vf == ""
|
|
assert af == ""
|
|
|
|
|
|
# ── resolve_clip_speed 测试 ──────────────────────────────────────────────
|
|
|
|
|
|
class TestResolveClipSpeed:
|
|
def test_none_config_uses_global(self):
|
|
assert resolve_clip_speed(None, 1.5) == 1.5
|
|
|
|
def test_no_playback_speed_uses_global(self):
|
|
assert resolve_clip_speed({}, 1.5) == 1.5
|
|
|
|
def test_zero_speed_uses_global(self):
|
|
assert resolve_clip_speed({"playback_speed": 0}, 1.5) == 1.5
|
|
|
|
def test_valid_speed_returns_speed(self):
|
|
assert resolve_clip_speed({"playback_speed": 2.0}, 1.0) == 2.0
|
|
|
|
def test_invalid_type_uses_global(self):
|
|
assert resolve_clip_speed({"playback_speed": "fast"}, 1.0) == 1.0
|
|
|
|
def test_default_global_speed(self):
|
|
assert resolve_clip_speed({}) == DEFAULT_SPEED
|