Files
xiaoxia-saas/tests/unit/test_trim_engine.py
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

286 lines
9.5 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""裁剪引擎单元测试 - 配置解析+推导等纯逻辑."""
from __future__ import annotations
import pytest
from video_processing.trim_engine import MIN_TRIM_DURATION, TrimConfig, TrimSegment
class TestTrimConfigFromDict:
"""TrimConfig.from_dict 解析测试."""
def test_none_returns_none(self):
"""None返回None(不裁剪)."""
assert TrimConfig.from_dict(None) is None
def test_empty_dict_returns_none(self):
"""空dict返回None."""
assert TrimConfig.from_dict({}) is None
def test_all_zero_returns_none(self):
"""全零返回None."""
assert (
TrimConfig.from_dict(
{
"start_time": 0,
"end_time": 0,
"duration": 0,
}
)
is None
)
def test_start_only(self):
"""只有start_time有效."""
config = TrimConfig.from_dict({"start_time": 5.0})
assert config is not None
assert config.start_time == 5.0
assert config.end_time == 0
assert config.duration == 0
def test_duration_only(self):
"""只有duration有效."""
config = TrimConfig.from_dict({"duration": 10.0})
assert config is not None
assert config.start_time == 0
assert config.duration == 10.0
def test_start_and_duration(self):
"""start + duration."""
config = TrimConfig.from_dict({"start_time": 2.0, "duration": 5.0})
assert config is not None
assert config.start_time == 2.0
assert config.duration == 5.0
def test_start_and_end(self):
"""start + end."""
config = TrimConfig.from_dict({"start_time": 1.0, "end_time": 4.0})
assert config is not None
assert config.start_time == 1.0
assert config.end_time == 4.0
def test_end_and_duration(self):
"""end + duration."""
config = TrimConfig.from_dict({"end_time": 10.0, "duration": 3.0})
assert config is not None
assert config.end_time == 10.0
assert config.duration == 3.0
def test_string_values_converted(self):
"""字符串值会被转换."""
config = TrimConfig.from_dict(
{
"start_time": "5.0",
"duration": "10.0",
}
)
assert config is not None
assert config.start_time == 5.0
assert config.duration == 10.0
def test_falsy_start_with_duration(self):
"""start=0 + duration>0有效."""
config = TrimConfig.from_dict({"start_time": 0, "duration": 5.0})
assert config is not None
assert config.start_time == 0.0
assert config.duration == 5.0
class TestValidateAndResolve:
"""validate_and_resolve 推导测试."""
def test_start_plus_end(self):
"""start + end → 推导duration."""
config = TrimConfig(start_time=2.0, end_time=7.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 2.0
assert resolved.end_time == 7.0
assert resolved.duration == 5.0
def test_start_plus_duration(self):
"""start + duration → 推导end."""
config = TrimConfig(start_time=3.0, duration=10.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 3.0
assert resolved.duration == 10.0
assert resolved.end_time == 13.0
def test_end_plus_duration(self):
"""end + duration → 推导start."""
config = TrimConfig(end_time=15.0, duration=5.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.end_time == 15.0
assert resolved.duration == 5.0
assert resolved.start_time == 10.0
def test_start_only_takes_to_end(self):
"""只有start → 取到素材末尾."""
config = TrimConfig(start_time=50.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 50.0
assert resolved.end_time == 60.0
assert resolved.duration == 10.0
def test_end_only_takes_from_start(self):
"""只有end → 从开头取."""
config = TrimConfig(end_time=20.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 0.0
assert resolved.end_time == 20.0
assert resolved.duration == 20.0
def test_end_before_start_invalid(self):
"""end < start → 无效(0时长)."""
config = TrimConfig(start_time=10.0, end_time=5.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.duration == 0.0
assert resolved.is_valid is False
def test_negative_start_clamped(self):
"""负start钳制到0."""
config = TrimConfig(start_time=-5.0, duration=10.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 0.0
assert resolved.duration == 10.0
def test_end_beyond_asset_clamped(self):
"""end超过素材时长钳制."""
config = TrimConfig(start_time=50.0, duration=20.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.end_time == 60.0
assert resolved.duration == 10.0
def test_start_beyond_asset_clamped(self):
"""start超过素材时长 → 钳制到末尾保留MIN_TRIM."""
config = TrimConfig(start_time=100.0, duration=5.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 60.0 - MIN_TRIM_DURATION
assert resolved.end_time == 60.0
def test_zero_asset_duration(self):
"""素材时长为0 → 不裁剪."""
config = TrimConfig(start_time=1.0, duration=5.0)
resolved = config.validate_and_resolve(0.0)
assert resolved.start_time == 0.0
assert resolved.duration == 0.0
def test_end_and_duration_with_negative_start(self):
"""end + duration推导出来负start → 钳制+重算."""
config = TrimConfig(end_time=3.0, duration=10.0)
resolved = config.validate_and_resolve(60.0)
assert resolved.start_time == 0.0
assert resolved.end_time == 3.0
assert resolved.duration == 3.0
def test_all_three_params_uses_start_duration(self):
"""三个都给了,以start+duration为准."""
# 实际代码是先判断 start+end(情况1),如果都>0就用
# 所以这里测试 start+end 都给了且都>0的情况
config = TrimConfig(start_time=2.0, end_time=8.0, duration=10.0)
resolved = config.validate_and_resolve(60.0)
# 走情况1start+end都有)
assert resolved.start_time == 2.0
assert resolved.end_time == 8.0
assert resolved.duration == 6.0
class TestIsValid:
"""is_valid 属性测试."""
def test_valid_duration(self):
"""时长足够有效."""
config = TrimConfig(start_time=0.0, end_time=0.0, duration=5.0)
assert config.is_valid is True
def test_zero_duration_invalid(self):
"""零时长无效."""
config = TrimConfig(duration=0.0)
assert config.is_valid is False
def test_min_duration_valid(self):
"""刚好等于最小值有效."""
config = TrimConfig(duration=MIN_TRIM_DURATION)
assert config.is_valid is True
class TestIsNoop:
"""is_noop 属性测试."""
def test_zero_is_noop(self):
"""全零是noop."""
config = TrimConfig()
assert config.is_noop is True
def test_with_duration_not_noop(self):
"""有时长不是noop."""
config = TrimConfig(duration=10.0)
assert config.is_noop is False
def test_with_start_not_noop(self):
"""有start不是noop."""
config = TrimConfig(start_time=5.0)
assert config.is_noop is False
class TestTrimFromStart:
"""trim_from_start 属性测试."""
def test_zero_start_is_from_start(self):
"""start=0是从开头裁."""
config = TrimConfig(start_time=0.0)
assert config.trim_from_start is True
def test_positive_start_not_from_start(self):
"""有start不是从开头裁."""
config = TrimConfig(start_time=5.0)
assert config.trim_from_start is False
class TestTrimSegment:
"""TrimSegment 测试."""
def test_from_dict_basic(self):
"""基本解析."""
seg = TrimSegment.from_dict(
{
"start_time": 5.0,
"duration": 10.0,
"segment_id": "seg1",
},
default_order=0,
)
assert seg.segment_id == "seg1"
assert seg.trim.start_time == 5.0
assert seg.trim.duration == 10.0
assert seg.order == 0
def test_from_dict_with_order(self):
"""带order的解析."""
seg = TrimSegment.from_dict(
{
"start_time": 1.0,
"end_time": 4.0,
"order": 2,
}
)
assert seg.order == 2
assert seg.trim.start_time == 1.0
assert seg.trim.end_time == 4.0
def test_from_dict_default_segment_id(self):
"""缺省segment_id时用默认值."""
seg = TrimSegment.from_dict({"duration": 5.0}, default_order=3)
assert seg.segment_id == "seg_3"
assert seg.order == 3
def test_from_dict_empty_string_segment_id(self):
"""空字符串segment_id走默认."""
seg = TrimSegment.from_dict(
{
"segment_id": "",
"duration": 5.0,
},
default_order=5,
)
assert seg.segment_id == "seg_5"