Files
xiaoxia-saas/tests/unit/test_speed_config.py
CI Bot 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
style: auto-format with black + isort + prettier
2026-07-27 13:33:18 +00:00

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