Files
xiaoxia-saas/tests/unit/test_pip_engine.py
T
CI Bot f1541fd0f3
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 39s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m13s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m18s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m59s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m7s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 50s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 29s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m54s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m1s
AI Code Review / AI Code Review (pull_request) Successful in 5m35s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 7m14s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m25s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m35s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m11s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1286h13m25s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1286h21m35s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1286h21m37s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1286h21m39s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1286h21m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1286h32m47s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1286h33m6s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1286h33m10s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1286h33m12s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1286h45m0s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1286h45m2s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1286h45m4s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1287h5m38s
test(unit): 第65波 - intro_outro + transition + pip 引擎配置
- test_intro_outro_engine: 片头片尾引擎,30个用例
  - from_dict 解析(text/video/别名/样式/转场)
  - has_intro / has_outro 属性
  - validate 配置校验

- test_transition_engine: 转场引擎,27个用例
  - TransitionConfig.parse 解析+降级+钳制
  - is_cut / ffmpeg_transition 属性
  - TransitionType 枚举

- test_pip_engine: 画中画引擎,25个用例
  - PiPLayerConfig.validate 全字段校验
  - PiPConfig.from_dict 解析+z_index排序+无效跳过

+82
2026-07-25 00:27:32 +08:00

263 lines
8.2 KiB
Python
Executable File

"""画中画引擎单元测试 - 配置解析+校验等纯逻辑."""
from __future__ import annotations
import pytest
from video_processing.pip_engine import PiPConfig, PiPLayerConfig
class TestPiPLayerConfigDefaults:
"""PiPLayerConfig 默认配置测试."""
def test_default_values(self):
"""默认值正确."""
layer = PiPLayerConfig()
assert layer.source == ""
assert layer.source_type == "asset_id"
assert layer.position == "bottom_right"
assert layer.margin == 20
assert layer.width == "25%"
assert layer.height == ""
assert layer.opacity == 1.0
assert layer.corner_radius == 0
assert layer.border_width == 0
assert layer.border_color == "white"
assert layer.start_time == 0.0
assert layer.duration == 0.0
assert layer.animation_in == ""
assert layer.animation_out == ""
assert layer.animation_duration == 0.5
assert layer.z_index == 1
class TestPiPLayerConfigValidate:
"""PiPLayerConfig.validate 校验测试."""
def test_valid_config(self):
"""合法配置."""
layer = PiPLayerConfig(source="asset_123")
ok, msg = layer.validate()
assert ok is True
assert msg == ""
def test_empty_source_invalid(self):
"""空source非法."""
layer = PiPLayerConfig(source="")
ok, msg = layer.validate()
assert ok is False
assert "source" in msg
def test_invalid_position(self):
"""无效position."""
layer = PiPLayerConfig(source="asset_123", position="invalid_pos")
ok, msg = layer.validate()
assert ok is False
assert "position" in msg
def test_custom_position_valid(self):
"""custom位置合法."""
layer = PiPLayerConfig(
source="asset_123",
position="custom",
x=100,
y=100,
)
ok, _ = layer.validate()
assert ok is True
def test_opacity_too_high(self):
"""透明度超过1."""
layer = PiPLayerConfig(source="a", opacity=1.5)
ok, msg = layer.validate()
assert ok is False
assert "opacity" in msg
def test_opacity_negative(self):
"""透明度为负."""
layer = PiPLayerConfig(source="a", opacity=-0.1)
ok, msg = layer.validate()
assert ok is False
assert "opacity" in msg
def test_opacity_boundary_zero(self):
"""透明度边界值0."""
layer = PiPLayerConfig(source="a", opacity=0.0)
ok, _ = layer.validate()
assert ok is True
def test_opacity_boundary_one(self):
"""透明度边界值1."""
layer = PiPLayerConfig(source="a", opacity=1.0)
ok, _ = layer.validate()
assert ok is True
def test_negative_corner_radius(self):
"""负圆角."""
layer = PiPLayerConfig(source="a", corner_radius=-5)
ok, msg = layer.validate()
assert ok is False
assert "corner_radius" in msg
def test_negative_start_time(self):
"""负开始时间."""
layer = PiPLayerConfig(source="a", start_time=-1.0)
ok, msg = layer.validate()
assert ok is False
assert "start_time" in msg
def test_negative_duration(self):
"""负时长."""
layer = PiPLayerConfig(source="a", duration=-2.0)
ok, msg = layer.validate()
assert ok is False
assert "duration" in msg
def test_zero_duration_valid(self):
"""零时长(全程显示)合法."""
layer = PiPLayerConfig(source="a", duration=0.0)
ok, _ = layer.validate()
assert ok is True
def test_invalid_animation_in(self):
"""无效入场动画."""
layer = PiPLayerConfig(source="a", animation_in="invalid_anim")
ok, msg = layer.validate()
assert ok is False
assert "入场动画" in msg
def test_invalid_animation_out(self):
"""无效出场动画."""
layer = PiPLayerConfig(source="a", animation_out="invalid_anim")
ok, msg = layer.validate()
assert ok is False
assert "出场动画" in msg
def test_negative_animation_duration(self):
"""负动画时长."""
layer = PiPLayerConfig(source="a", animation_duration=-0.5)
ok, msg = layer.validate()
assert ok is False
assert "animation_duration" in msg
class TestPiPConfigDefaults:
"""PiPConfig 默认配置测试."""
def test_default_values(self):
"""默认值正确."""
config = PiPConfig()
assert config.enabled is False
assert config.layers == []
class TestPiPConfigFromDict:
"""PiPConfig.from_dict 解析测试."""
def test_none_returns_disabled(self):
"""None 返回禁用配置."""
config = PiPConfig.from_dict(None)
assert config.enabled is False
assert config.layers == []
def test_empty_dict_returns_disabled(self):
"""空 dict 返回禁用."""
config = PiPConfig.from_dict({})
assert config.enabled is False
def test_disabled_returns_disabled(self):
"""enabled=False 返回禁用."""
config = PiPConfig.from_dict({"enabled": False})
assert config.enabled is False
def test_enabled_no_layers(self):
"""启用但无图层,disabled."""
config = PiPConfig.from_dict({"enabled": True, "layers": []})
assert config.enabled is False
assert config.layers == []
def test_single_layer(self):
"""单个图层."""
config = PiPConfig.from_dict({
"enabled": True,
"layers": [
{"source": "asset_001", "position": "top_left"},
],
})
assert config.enabled is True
assert len(config.layers) == 1
assert config.layers[0].source == "asset_001"
assert config.layers[0].position == "top_left"
def test_multiple_layers_sorted_by_z_index(self):
"""多个图层按z_index排序."""
config = PiPConfig.from_dict({
"enabled": True,
"layers": [
{"source": "a", "z_index": 3},
{"source": "b", "z_index": 1},
{"source": "c", "z_index": 2},
],
})
assert len(config.layers) == 3
assert config.layers[0].z_index == 1
assert config.layers[1].z_index == 2
assert config.layers[2].z_index == 3
def test_invalid_layer_skipped(self):
"""无效图层跳过."""
config = PiPConfig.from_dict({
"enabled": True,
"layers": [
{"source": "valid_asset"},
{"source": ""}, # 无效,空source
],
})
assert len(config.layers) == 1
assert config.layers[0].source == "valid_asset"
def test_all_invalid_layers_disabled(self):
"""全部无效则disabled."""
config = PiPConfig.from_dict({
"enabled": True,
"layers": [
{"source": ""},
{"source": ""},
],
})
assert config.enabled is False
assert config.layers == []
def test_layer_full_config(self):
"""完整图层配置."""
config = PiPConfig.from_dict({
"enabled": True,
"layers": [
{
"source": "https://example.com/video.mp4",
"source_type": "url",
"position": "bottom_right",
"width": "30%",
"opacity": 0.8,
"corner_radius": 10,
"border_width": 2,
"border_color": "red",
"start_time": 5.0,
"duration": 10.0,
"z_index": 5,
},
],
})
assert len(config.layers) == 1
layer = config.layers[0]
assert layer.source == "https://example.com/video.mp4"
assert layer.source_type == "url"
assert layer.width == "30%"
assert layer.opacity == 0.8
assert layer.corner_radius == 10
assert layer.border_width == 2
assert layer.border_color == "red"
assert layer.start_time == 5.0
assert layer.duration == 10.0
assert layer.z_index == 5