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
312 lines
9.0 KiB
Python
Executable File
312 lines
9.0 KiB
Python
Executable File
"""拼接引擎单元测试 - 配置解析等纯逻辑."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from video_processing.concat_engine import ConcatConfig, ConcatSegment
|
|
|
|
|
|
class TestConcatSegmentDefaults:
|
|
"""ConcatSegment 默认值测试."""
|
|
|
|
def test_default_values(self):
|
|
"""默认值正确."""
|
|
seg = ConcatSegment(video_path="/a.mp4")
|
|
assert seg.video_path == "/a.mp4"
|
|
assert seg.start_time == 0.0
|
|
assert seg.duration == 0.0
|
|
assert seg.has_audio is True
|
|
|
|
|
|
class TestConcatSegmentFromDict:
|
|
"""ConcatSegment.from_dict 测试."""
|
|
|
|
def test_basic_path(self):
|
|
"""基本路径."""
|
|
seg = ConcatSegment.from_dict({"video_path": "/a.mp4"})
|
|
assert seg.video_path == "/a.mp4"
|
|
assert seg.start_time == 0.0
|
|
assert seg.duration == 0.0
|
|
|
|
def test_custom_start_time(self):
|
|
"""自定义开始时间."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"start_time": 5.0,
|
|
}
|
|
)
|
|
assert seg.start_time == 5.0
|
|
|
|
def test_custom_duration(self):
|
|
"""自定义时长."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"duration": 10.0,
|
|
}
|
|
)
|
|
assert seg.duration == 10.0
|
|
|
|
def test_start_time_negative_clamped(self):
|
|
"""负开始时间钳制到0."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"start_time": -5.0,
|
|
}
|
|
)
|
|
assert seg.start_time == 0.0
|
|
|
|
def test_duration_negative_clamped(self):
|
|
"""负时长钳制到0."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"duration": -3.0,
|
|
}
|
|
)
|
|
assert seg.duration == 0.0
|
|
|
|
def test_invalid_start_time_falls_back(self):
|
|
"""无效start_time回退到0."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"start_time": "invalid",
|
|
}
|
|
)
|
|
assert seg.start_time == 0.0
|
|
|
|
def test_invalid_duration_falls_back(self):
|
|
"""无效duration回退到0."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"duration": "not_a_number",
|
|
}
|
|
)
|
|
assert seg.duration == 0.0
|
|
|
|
def test_no_audio(self):
|
|
"""无音频."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/a.mp4",
|
|
"has_audio": False,
|
|
}
|
|
)
|
|
assert seg.has_audio is False
|
|
|
|
def test_full_config(self):
|
|
"""完整配置."""
|
|
seg = ConcatSegment.from_dict(
|
|
{
|
|
"video_path": "/video.mp4",
|
|
"start_time": 2.5,
|
|
"duration": 15.0,
|
|
"has_audio": False,
|
|
}
|
|
)
|
|
assert seg.video_path == "/video.mp4"
|
|
assert seg.start_time == 2.5
|
|
assert seg.duration == 15.0
|
|
assert seg.has_audio is False
|
|
|
|
|
|
class TestConcatConfigDefaults:
|
|
"""ConcatConfig 默认值测试."""
|
|
|
|
def test_default_values(self):
|
|
"""默认值正确."""
|
|
config = ConcatConfig()
|
|
assert config.segments == []
|
|
assert config.output_width == 0
|
|
assert config.output_height == 0
|
|
assert config.output_fps == 0.0
|
|
assert config.force_reencode is False
|
|
assert config.transition == "none"
|
|
assert config.transition_duration == 0.3
|
|
|
|
|
|
class TestConcatConfigFromConfigDict:
|
|
"""ConcatConfig.from_config_dict 测试."""
|
|
|
|
def test_none_returns_default(self):
|
|
"""None返回默认配置."""
|
|
config = ConcatConfig.from_config_dict(None)
|
|
assert config.segments == []
|
|
|
|
def test_empty_dict_returns_default(self):
|
|
"""空dict返回默认."""
|
|
config = ConcatConfig.from_config_dict({})
|
|
assert config.segments == []
|
|
|
|
def test_single_segment(self):
|
|
"""单片段."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
}
|
|
)
|
|
assert len(config.segments) == 1
|
|
assert config.segments[0].video_path == "/a.mp4"
|
|
|
|
def test_multiple_segments(self):
|
|
"""多片段."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [
|
|
{"video_path": "/a.mp4", "start_time": 1.0},
|
|
{"video_path": "/b.mp4", "duration": 5.0},
|
|
{"video_path": "/c.mp4"},
|
|
],
|
|
}
|
|
)
|
|
assert len(config.segments) == 3
|
|
assert config.segments[0].start_time == 1.0
|
|
assert config.segments[1].duration == 5.0
|
|
|
|
def test_skips_no_path(self):
|
|
"""跳过无video_path的片段."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [
|
|
{"video_path": "/a.mp4"},
|
|
{"other": "value"},
|
|
{"video_path": ""},
|
|
],
|
|
}
|
|
)
|
|
assert len(config.segments) == 1
|
|
|
|
def test_segments_not_list_ignored(self):
|
|
"""segments不是列表忽略."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": "not_a_list",
|
|
}
|
|
)
|
|
assert config.segments == []
|
|
|
|
def test_output_size(self):
|
|
"""输出尺寸."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
"output_width": 1920,
|
|
"output_height": 1080,
|
|
}
|
|
)
|
|
assert config.output_width == 1920
|
|
assert config.output_height == 1080
|
|
|
|
def test_negative_output_size_clamped(self):
|
|
"""负输出尺寸钳制到0."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
"output_width": -100,
|
|
"output_height": -50,
|
|
}
|
|
)
|
|
assert config.output_width == 0
|
|
assert config.output_height == 0
|
|
|
|
def test_invalid_output_size_falls_back(self):
|
|
"""无效输出尺寸回退."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
"output_width": "wide",
|
|
"output_fps": "sixty",
|
|
}
|
|
)
|
|
assert config.output_width == 0
|
|
assert config.output_fps == 0.0
|
|
|
|
def test_output_fps(self):
|
|
"""输出帧率."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
"output_fps": 60.0,
|
|
}
|
|
)
|
|
assert config.output_fps == 60.0
|
|
|
|
def test_force_reencode(self):
|
|
"""强制重新编码."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}],
|
|
"force_reencode": True,
|
|
}
|
|
)
|
|
assert config.force_reencode is True
|
|
|
|
def test_transition_config(self):
|
|
"""转场配置."""
|
|
config = ConcatConfig.from_config_dict(
|
|
{
|
|
"segments": [{"video_path": "/a.mp4"}, {"video_path": "/b.mp4"}],
|
|
"transition": "crossfade",
|
|
"transition_duration": 1.0,
|
|
}
|
|
)
|
|
assert config.transition == "crossfade"
|
|
assert config.transition_duration == 1.0
|
|
|
|
def test_non_dict_config_returns_default(self):
|
|
"""非dict配置返回默认."""
|
|
config = ConcatConfig.from_config_dict("not_a_dict")
|
|
assert config.segments == []
|
|
|
|
|
|
class TestHasEffect:
|
|
"""has_effect 属性测试."""
|
|
|
|
def test_no_segments_no_effect(self):
|
|
"""无片段无效果."""
|
|
config = ConcatConfig()
|
|
assert config.has_effect is False
|
|
|
|
def test_one_segment_no_effect(self):
|
|
"""单片段无效果(拼接至少需要2段)."""
|
|
config = ConcatConfig(
|
|
segments=[
|
|
ConcatSegment(video_path="/a.mp4"),
|
|
]
|
|
)
|
|
assert config.has_effect is False
|
|
|
|
def test_two_segments_has_effect(self):
|
|
"""两段及以上有效果."""
|
|
config = ConcatConfig(
|
|
segments=[
|
|
ConcatSegment(video_path="/a.mp4"),
|
|
ConcatSegment(video_path="/b.mp4"),
|
|
]
|
|
)
|
|
assert config.has_effect is True
|
|
|
|
|
|
class TestTotalSegments:
|
|
"""total_segments 属性测试."""
|
|
|
|
def test_no_segments(self):
|
|
"""零片段."""
|
|
config = ConcatConfig()
|
|
assert config.total_segments == 0
|
|
|
|
def test_three_segments(self):
|
|
"""三个片段."""
|
|
config = ConcatConfig(
|
|
segments=[
|
|
ConcatSegment(video_path="/a.mp4"),
|
|
ConcatSegment(video_path="/b.mp4"),
|
|
ConcatSegment(video_path="/c.mp4"),
|
|
]
|
|
)
|
|
assert config.total_segments == 3
|