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
286 lines
9.5 KiB
Python
Executable File
286 lines
9.5 KiB
Python
Executable File
"""裁剪引擎单元测试 - 配置解析+推导等纯逻辑."""
|
||
|
||
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)
|
||
# 走情况1(start+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"
|