456718ad84
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Failing after 13m7s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 13m2s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Failing after 12m57s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m27s
CI/CD Pipeline / Unit Tests (push) Failing after 4m22s
CI/CD Pipeline / Integration Tests (push) Successful in 2m29s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m0s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 1m33s
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 / Build Staging API Image (push) Successful in 11m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m29s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m8s
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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 24s
CI/CD Pipeline / CI Gate (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
781 lines
24 KiB
Python
Executable File
781 lines
24 KiB
Python
Executable File
"""贴纸引擎纯逻辑单元测试."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from video_processing.sticker_engine_pure import (
|
|
build_drawtext_alpha_expr,
|
|
build_enable_expr,
|
|
build_image_fade_filters,
|
|
build_opacity_filter,
|
|
build_overlay_position,
|
|
build_pre_filter_label,
|
|
build_scale_filter,
|
|
build_shadow_params,
|
|
build_stroke_params,
|
|
calculate_end_time,
|
|
calculate_fade_out_start,
|
|
count_sticker_types,
|
|
escape_drawtext_text,
|
|
estimate_sticker_size,
|
|
estimate_text_size,
|
|
filter_enabled_stickers,
|
|
has_time_range,
|
|
safe_bool,
|
|
safe_float,
|
|
safe_int,
|
|
sort_stickers_by_z_index,
|
|
validate_image_sticker,
|
|
validate_text_sticker,
|
|
)
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 安全类型转换测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestSafeFloat:
|
|
"""safe_float 测试."""
|
|
|
|
def test_int_input(self):
|
|
"""整数输入."""
|
|
assert safe_float(42) == 42.0
|
|
|
|
def test_float_input(self):
|
|
"""浮点数输入."""
|
|
assert safe_float(3.14) == 3.14
|
|
|
|
def test_string_number(self):
|
|
"""字符串数字."""
|
|
assert safe_float("3.14") == 3.14
|
|
|
|
def test_string_int(self):
|
|
"""字符串整数."""
|
|
assert safe_float("100") == 100.0
|
|
|
|
def test_none_input(self):
|
|
"""None 输入."""
|
|
assert safe_float(None) is None
|
|
|
|
def test_invalid_string(self):
|
|
"""无效字符串."""
|
|
assert safe_float("abc") is None
|
|
|
|
def test_empty_string(self):
|
|
"""空字符串."""
|
|
assert safe_float("") is None
|
|
|
|
def test_zero(self):
|
|
"""零值."""
|
|
assert safe_float(0) == 0.0
|
|
|
|
def test_negative(self):
|
|
"""负值."""
|
|
assert safe_float(-5.5) == -5.5
|
|
|
|
|
|
class TestSafeInt:
|
|
"""safe_int 测试."""
|
|
|
|
def test_int_input(self):
|
|
"""整数输入."""
|
|
assert safe_int(42) == 42
|
|
|
|
def test_float_input(self):
|
|
"""浮点数输入(截断)."""
|
|
assert safe_int(3.7) == 3
|
|
|
|
def test_string_number(self):
|
|
"""字符串数字."""
|
|
assert safe_int("42") == 42
|
|
|
|
def test_none_input(self):
|
|
"""None 输入用默认值."""
|
|
assert safe_int(None) == 0
|
|
|
|
def test_none_custom_default(self):
|
|
"""None 输入自定义默认值."""
|
|
assert safe_int(None, default=10) == 10
|
|
|
|
def test_invalid_string(self):
|
|
"""无效字符串."""
|
|
assert safe_int("abc") == 0
|
|
|
|
def test_negative(self):
|
|
"""负值."""
|
|
assert safe_int(-5) == -5
|
|
|
|
def test_zero(self):
|
|
"""零值."""
|
|
assert safe_int(0) == 0
|
|
|
|
|
|
class TestSafeBool:
|
|
"""safe_bool 测试."""
|
|
|
|
def test_true_bool(self):
|
|
"""True."""
|
|
assert safe_bool(True) is True
|
|
|
|
def test_false_bool(self):
|
|
"""False."""
|
|
assert safe_bool(False) is False
|
|
|
|
def test_none(self):
|
|
"""None -> False."""
|
|
assert safe_bool(None) is False
|
|
|
|
def test_string_true(self):
|
|
"""字符串 true."""
|
|
assert safe_bool("true") is True
|
|
|
|
def test_string_yes(self):
|
|
"""字符串 yes."""
|
|
assert safe_bool("yes") is True
|
|
|
|
def test_string_one(self):
|
|
"""字符串 1."""
|
|
assert safe_bool("1") is True
|
|
|
|
def test_string_false(self):
|
|
"""字符串 false."""
|
|
assert safe_bool("false") is False
|
|
|
|
def test_int_one(self):
|
|
"""整数 1 -> True."""
|
|
assert safe_bool(1) is True
|
|
|
|
def test_int_zero(self):
|
|
"""整数 0 -> False."""
|
|
assert safe_bool(0) is False
|
|
|
|
def test_empty_list(self):
|
|
"""空列表 -> False."""
|
|
assert safe_bool([]) is False
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 尺寸估算测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestEstimateStickerSize:
|
|
"""贴纸尺寸估算测试."""
|
|
|
|
def test_default_scale(self):
|
|
"""默认 scale=1.0."""
|
|
w, h = estimate_sticker_size(1000, 1000)
|
|
assert w == 300 # 1000 * 0.3 * 1.0
|
|
assert h == 300
|
|
|
|
def test_custom_scale(self):
|
|
"""自定义缩放."""
|
|
w, h = estimate_sticker_size(1000, 1000, scale=0.5)
|
|
assert w == 150
|
|
assert h == 150
|
|
|
|
def test_fixed_width_height(self):
|
|
"""固定宽高."""
|
|
w, h = estimate_sticker_size(1000, 1000, fixed_width=200, fixed_height=100)
|
|
assert w == 200
|
|
assert h == 100
|
|
|
|
def test_scale_2x(self):
|
|
"""2倍缩放."""
|
|
w, h = estimate_sticker_size(800, 600, scale=2.0)
|
|
assert w == 480 # 800 * 0.3 * 2
|
|
assert h == 360 # 600 * 0.3 * 2
|
|
|
|
def test_zero_canvas(self):
|
|
"""零画布尺寸,返回最小 1."""
|
|
w, h = estimate_sticker_size(0, 0)
|
|
assert w >= 1
|
|
assert h >= 1
|
|
|
|
|
|
class TestEstimateTextSize:
|
|
"""文字尺寸估算测试."""
|
|
|
|
def test_normal_text(self):
|
|
"""普通文字."""
|
|
w, h = estimate_text_size("Hello", 36)
|
|
assert w == int(5 * 36 * 0.6)
|
|
assert h == int(36 * 1.4)
|
|
|
|
def test_empty_text(self):
|
|
"""空文字."""
|
|
w, h = estimate_text_size("", 36)
|
|
assert w == 0
|
|
assert h == 0
|
|
|
|
def test_large_font(self):
|
|
"""大字号."""
|
|
w, h = estimate_text_size("A", 72)
|
|
assert w == int(1 * 72 * 0.6)
|
|
assert h == int(72 * 1.4)
|
|
|
|
def test_chinese_chars(self):
|
|
"""中文字符."""
|
|
w, h = estimate_text_size("你好世界", 48)
|
|
assert w == int(4 * 48 * 0.6)
|
|
assert h == int(48 * 1.4)
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 时间计算测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCalculateFadeOutStart:
|
|
"""淡出开始时间计算测试."""
|
|
|
|
def test_normal_case(self):
|
|
"""正常情况."""
|
|
assert calculate_fade_out_start(10, 30, 2) == pytest.approx(38.0)
|
|
|
|
def test_no_fade_out(self):
|
|
"""无淡出."""
|
|
assert calculate_fade_out_start(10, 30, 0) == 0.0
|
|
|
|
def test_negative_fade_out(self):
|
|
"""负淡出."""
|
|
assert calculate_fade_out_start(10, 30, -1) == 0.0
|
|
|
|
def test_zero_duration(self):
|
|
"""零时长."""
|
|
assert calculate_fade_out_start(10, 0, 2) == 0.0
|
|
|
|
def test_fade_out_longer_than_duration(self):
|
|
"""淡出超过时长,返回 0."""
|
|
# start=10, dur=5, fade=10 -> 10+5-10 = 5 > 0
|
|
assert calculate_fade_out_start(10, 5, 10) == pytest.approx(5.0)
|
|
|
|
def test_fade_out_starts_before_zero(self):
|
|
"""淡出开始时间在 0 之前,钳制到 0."""
|
|
# start=0, dur=3, fade=5 -> 0+3-5 = -2 -> 0
|
|
assert calculate_fade_out_start(0, 3, 5) == 0.0
|
|
|
|
|
|
class TestCalculateEndTime:
|
|
"""结束时间计算测试."""
|
|
|
|
def test_normal_case(self):
|
|
"""正常情况."""
|
|
assert calculate_end_time(10, 30) == 40.0
|
|
|
|
def test_zero_duration(self):
|
|
"""零时长."""
|
|
assert calculate_end_time(10, 0) == 10.0
|
|
|
|
def test_negative_duration(self):
|
|
"""负时长."""
|
|
assert calculate_end_time(10, -5) == 10.0
|
|
|
|
def test_zero_start(self):
|
|
"""零开始."""
|
|
assert calculate_end_time(0, 100) == 100.0
|
|
|
|
|
|
class TestHasTimeRange:
|
|
"""时间范围判断测试."""
|
|
|
|
def test_positive_duration(self):
|
|
"""正时长."""
|
|
assert has_time_range(30) is True
|
|
|
|
def test_zero_duration(self):
|
|
"""零时长."""
|
|
assert has_time_range(0) is False
|
|
|
|
def test_negative_duration(self):
|
|
"""负时长."""
|
|
assert has_time_range(-5) is False
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 滤镜构建测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestBuildScaleFilter:
|
|
"""缩放滤镜构建测试."""
|
|
|
|
def test_fixed_width_height(self):
|
|
"""固定宽高."""
|
|
result = build_scale_filter(width=200, height=100)
|
|
assert result == "scale=200:100"
|
|
|
|
def test_scale_only(self):
|
|
"""仅缩放."""
|
|
result = build_scale_filter(scale=0.5)
|
|
assert result == "scale=iw*0.5:ih*0.5"
|
|
|
|
def test_no_scaling_needed(self):
|
|
"""无需缩放."""
|
|
result = build_scale_filter(scale=1.0)
|
|
assert result is None
|
|
|
|
def test_scale_2x(self):
|
|
"""2倍缩放."""
|
|
result = build_scale_filter(scale=2.0)
|
|
assert result == "scale=iw*2.0:ih*2.0"
|
|
|
|
def test_fixed_overrides_scale(self):
|
|
"""固定宽高优先于 scale."""
|
|
result = build_scale_filter(width=100, height=50, scale=0.5)
|
|
assert result == "scale=100:50"
|
|
|
|
|
|
class TestBuildOpacityFilter:
|
|
"""透明度滤镜构建测试."""
|
|
|
|
def test_partial_opacity(self):
|
|
"""部分透明."""
|
|
result = build_opacity_filter(0.5)
|
|
assert result == "colorchannelmixer=aa=0.5"
|
|
|
|
def test_fully_opaque(self):
|
|
"""完全不透明."""
|
|
result = build_opacity_filter(1.0)
|
|
assert result is None
|
|
|
|
def test_fully_transparent(self):
|
|
"""完全透明."""
|
|
result = build_opacity_filter(0.0)
|
|
assert result == "colorchannelmixer=aa=0.0"
|
|
|
|
def test_opacity_above_1_clamped(self):
|
|
"""超过 1 被钳制."""
|
|
result = build_opacity_filter(1.5)
|
|
assert result is None
|
|
|
|
def test_opacity_below_0_clamped(self):
|
|
"""低于 0 被钳制."""
|
|
result = build_opacity_filter(-0.5)
|
|
assert result == "colorchannelmixer=aa=0.0"
|
|
|
|
|
|
class TestBuildImageFadeFilters:
|
|
"""图片淡入淡出滤镜测试."""
|
|
|
|
def test_fade_in_only(self):
|
|
"""仅淡入."""
|
|
result = build_image_fade_filters(10, 30, fade_in=1.0)
|
|
assert len(result) == 1
|
|
assert "fade=in:st=10:d=1.0:alpha=1" in result[0]
|
|
|
|
def test_fade_out_only(self):
|
|
"""仅淡出."""
|
|
result = build_image_fade_filters(10, 30, fade_out=2.0)
|
|
assert len(result) == 1
|
|
assert "fade=out" in result[0]
|
|
assert "st=38.0" in result[0] # 10 + 30 - 2 = 38
|
|
|
|
def test_fade_in_and_out(self):
|
|
"""淡入+淡出."""
|
|
result = build_image_fade_filters(0, 10, fade_in=1.0, fade_out=1.0)
|
|
assert len(result) == 2
|
|
assert "fade=in" in result[0]
|
|
assert "fade=out" in result[1]
|
|
|
|
def test_no_fade(self):
|
|
"""无淡入淡出."""
|
|
result = build_image_fade_filters(10, 30)
|
|
assert len(result) == 0
|
|
|
|
def test_zero_duration_no_fade_out(self):
|
|
"""零时长不生成淡出."""
|
|
result = build_image_fade_filters(10, 0, fade_out=1.0)
|
|
assert len(result) == 0
|
|
|
|
|
|
class TestBuildEnableExpr:
|
|
"""enable 表达式构建测试."""
|
|
|
|
def test_normal_duration(self):
|
|
"""正常时长."""
|
|
result = build_enable_expr(10, 30)
|
|
assert "between(t,10,40" in result
|
|
assert "enable" in result
|
|
|
|
def test_zero_duration(self):
|
|
"""零时长返回空."""
|
|
result = build_enable_expr(10, 0)
|
|
assert result == ""
|
|
|
|
def test_negative_duration(self):
|
|
"""负时长返回空."""
|
|
result = build_enable_expr(10, -5)
|
|
assert result == ""
|
|
|
|
def test_zero_start(self):
|
|
"""从零开始."""
|
|
result = build_enable_expr(0, 100)
|
|
assert "t,0,100" in result
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# drawtext 相关测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestEscapeDrawtextText:
|
|
"""文字转义测试."""
|
|
|
|
def test_no_special_chars(self):
|
|
"""无特殊字符."""
|
|
assert escape_drawtext_text("Hello") == "Hello"
|
|
|
|
def test_colon_escaped(self):
|
|
"""冒号转义."""
|
|
assert escape_drawtext_text("a:b") == "a\\:b"
|
|
|
|
def test_quote_escaped(self):
|
|
"""单引号转义."""
|
|
assert escape_drawtext_text("it's") == "it\\'s"
|
|
|
|
def test_multiple_special_chars(self):
|
|
"""多个特殊字符."""
|
|
assert escape_drawtext_text("a:b:c'd") == "a\\:b\\:c\\'d"
|
|
|
|
def test_empty_string(self):
|
|
"""空字符串."""
|
|
assert escape_drawtext_text("") == ""
|
|
|
|
|
|
class TestBuildDrawtextAlphaExpr:
|
|
"""drawtext alpha 表达式测试."""
|
|
|
|
def test_no_fade(self):
|
|
"""无淡入淡出."""
|
|
assert build_drawtext_alpha_expr(10, 30) == "1"
|
|
|
|
def test_fade_in_only(self):
|
|
"""仅淡入."""
|
|
result = build_drawtext_alpha_expr(10, 30, fade_in=2.0)
|
|
assert "if(lt(t,12.0)" in result
|
|
assert "(t-10)/2.0" in result
|
|
|
|
def test_fade_out_only(self):
|
|
"""仅淡出."""
|
|
result = build_drawtext_alpha_expr(10, 30, fade_out=3.0)
|
|
assert "if(gt(t,37" in result
|
|
assert "-t)/3.0" in result
|
|
|
|
def test_fade_in_and_out(self):
|
|
"""淡入+淡出(相乘)."""
|
|
result = build_drawtext_alpha_expr(0, 10, fade_in=1.0, fade_out=1.0)
|
|
assert "*" in result
|
|
assert result.count("if(") == 2
|
|
|
|
def test_zero_duration_no_fade_out(self):
|
|
"""零时长不生成淡出."""
|
|
result = build_drawtext_alpha_expr(10, 0, fade_out=1.0)
|
|
assert result == "1"
|
|
|
|
|
|
class TestBuildStrokeParams:
|
|
"""描边参数测试."""
|
|
|
|
def test_no_stroke(self):
|
|
"""无描边."""
|
|
result = build_stroke_params(0)
|
|
assert len(result) == 0
|
|
|
|
def test_with_stroke(self):
|
|
"""有描边."""
|
|
result = build_stroke_params(2, "red")
|
|
assert len(result) == 2
|
|
assert "borderw=2" in result
|
|
assert "bordercolor=red" in result
|
|
|
|
def test_negative_width(self):
|
|
"""负宽度."""
|
|
result = build_stroke_params(-1)
|
|
assert len(result) == 0
|
|
|
|
|
|
class TestBuildShadowParams:
|
|
"""阴影参数测试."""
|
|
|
|
def test_no_shadow(self):
|
|
"""无阴影."""
|
|
result = build_shadow_params(0)
|
|
assert len(result) == 0
|
|
|
|
def test_with_shadow(self):
|
|
"""有阴影."""
|
|
result = build_shadow_params(0.5, 3, 4, "black")
|
|
assert len(result) == 3
|
|
assert "shadowx=3" in result
|
|
assert "shadowy=4" in result
|
|
assert "shadowcolor=black@0.5" in result
|
|
|
|
def test_shadow_alpha_clamped(self):
|
|
"""透明度钳制."""
|
|
result = build_shadow_params(1.5)
|
|
assert "shadowcolor=black@1.0" in result[2]
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 贴纸排序与过滤测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestSortStickersByZIndex:
|
|
"""贴纸排序测试."""
|
|
|
|
def test_sorted_by_z_index(self):
|
|
"""按 z_index 排序."""
|
|
stickers = [
|
|
{"z_index": 20, "name": "top"},
|
|
{"z_index": 5, "name": "bottom"},
|
|
{"z_index": 10, "name": "middle"},
|
|
]
|
|
result = sort_stickers_by_z_index(stickers)
|
|
assert result[0]["name"] == "bottom"
|
|
assert result[1]["name"] == "middle"
|
|
assert result[2]["name"] == "top"
|
|
|
|
def test_same_z_index_preserves_order(self):
|
|
"""相同 z_index 保持原顺序."""
|
|
stickers = [
|
|
{"z_index": 10, "name": "first"},
|
|
{"z_index": 10, "name": "second"},
|
|
]
|
|
result = sort_stickers_by_z_index(stickers)
|
|
assert result[0]["name"] == "first"
|
|
assert result[1]["name"] == "second"
|
|
|
|
def test_empty_list(self):
|
|
"""空列表."""
|
|
assert sort_stickers_by_z_index([]) == []
|
|
|
|
def test_default_z_index_10(self):
|
|
"""无 z_index 默认 10."""
|
|
stickers = [
|
|
{"z_index": 5, "name": "low"},
|
|
{"name": "default"},
|
|
]
|
|
result = sort_stickers_by_z_index(stickers)
|
|
assert result[0]["name"] == "low"
|
|
assert result[1]["name"] == "default"
|
|
|
|
|
|
class TestFilterEnabledStickers:
|
|
"""启用贴纸过滤测试."""
|
|
|
|
def test_all_enabled(self):
|
|
"""全部启用."""
|
|
stickers = [{"enabled": True}, {"enabled": True}]
|
|
assert len(filter_enabled_stickers(stickers)) == 2
|
|
|
|
def test_mixed(self):
|
|
"""混合."""
|
|
stickers = [
|
|
{"enabled": True, "name": "a"},
|
|
{"enabled": False, "name": "b"},
|
|
{"enabled": True, "name": "c"},
|
|
]
|
|
result = filter_enabled_stickers(stickers)
|
|
assert len(result) == 2
|
|
assert result[0]["name"] == "a"
|
|
|
|
def test_default_enabled(self):
|
|
"""默认启用."""
|
|
stickers = [{"name": "a"}]
|
|
result = filter_enabled_stickers(stickers)
|
|
assert len(result) == 1
|
|
|
|
def test_empty_list(self):
|
|
"""空列表."""
|
|
assert filter_enabled_stickers([]) == []
|
|
|
|
|
|
class TestCountStickerTypes:
|
|
"""贴纸类型统计测试."""
|
|
|
|
def test_mixed_types(self):
|
|
"""混合类型."""
|
|
stickers = [
|
|
{"type": "image"},
|
|
{"type": "text"},
|
|
{"type": "image"},
|
|
]
|
|
counts = count_sticker_types(stickers)
|
|
assert counts["image"] == 2
|
|
assert counts["text"] == 1
|
|
|
|
def test_default_type(self):
|
|
"""默认 image."""
|
|
stickers = [{}]
|
|
counts = count_sticker_types(stickers)
|
|
assert counts["image"] == 1
|
|
|
|
def test_empty_list(self):
|
|
"""空列表."""
|
|
assert count_sticker_types([]) == {}
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# overlay 相关测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestBuildOverlayPosition:
|
|
"""overlay 位置构建测试."""
|
|
|
|
def test_integer_position(self):
|
|
"""整数位置."""
|
|
assert build_overlay_position(100, 200) == "100:200"
|
|
|
|
def test_float_position_rounded(self):
|
|
"""浮点取整."""
|
|
assert build_overlay_position(100.6, 200.4) == "101:200"
|
|
|
|
def test_zero_position(self):
|
|
"""零位置."""
|
|
assert build_overlay_position(0, 0) == "0:0"
|
|
|
|
def test_negative_position(self):
|
|
"""负位置."""
|
|
assert build_overlay_position(-10, -20) == "-10:-20"
|
|
|
|
|
|
class TestBuildPreFilterLabel:
|
|
"""预处理标签构建测试."""
|
|
|
|
def test_normal_idx(self):
|
|
"""正常索引."""
|
|
assert build_pre_filter_label(3) == "sticker_3_scaled"
|
|
|
|
def test_zero_idx(self):
|
|
"""零索引."""
|
|
assert build_pre_filter_label(0) == "sticker_0_scaled"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 验证函数测试
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestValidateImageSticker:
|
|
"""图片贴纸验证测试."""
|
|
|
|
def test_valid_with_image_path(self):
|
|
"""有 image_path,合法."""
|
|
ok, errors = validate_image_sticker({"image_path": "/a.png"})
|
|
assert ok is True
|
|
assert len(errors) == 0
|
|
|
|
def test_valid_with_asset_id(self):
|
|
"""有 asset_id,合法."""
|
|
ok, errors = validate_image_sticker({"asset_id": "123"})
|
|
assert ok is True
|
|
|
|
def test_missing_image_source(self):
|
|
"""缺图片来源."""
|
|
ok, errors = validate_image_sticker({})
|
|
assert ok is False
|
|
assert any("image_path" in e or "asset_id" in e for e in errors)
|
|
|
|
def test_opacity_out_of_range(self):
|
|
"""透明度超范围."""
|
|
ok, errors = validate_image_sticker(
|
|
{
|
|
"image_path": "/a.png",
|
|
"opacity": 1.5,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("opacity" in e for e in errors)
|
|
|
|
def test_negative_scale(self):
|
|
"""负缩放."""
|
|
ok, errors = validate_image_sticker(
|
|
{
|
|
"image_path": "/a.png",
|
|
"scale": -0.5,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("scale" in e for e in errors)
|
|
|
|
def test_negative_duration(self):
|
|
"""负时长."""
|
|
ok, errors = validate_image_sticker(
|
|
{
|
|
"image_path": "/a.png",
|
|
"duration": -10,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("duration" in e for e in errors)
|
|
|
|
def test_multiple_errors(self):
|
|
"""多个错误."""
|
|
ok, errors = validate_image_sticker(
|
|
{
|
|
"opacity": 1.5,
|
|
"duration": -1,
|
|
"start_time": -5,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert len(errors) >= 3
|
|
|
|
|
|
class TestValidateTextSticker:
|
|
"""文字贴纸验证测试."""
|
|
|
|
def test_valid(self):
|
|
"""合法配置."""
|
|
ok, errors = validate_text_sticker(
|
|
{
|
|
"text": "Hello",
|
|
"font_size": 36,
|
|
"font_color": "white",
|
|
}
|
|
)
|
|
assert ok is True
|
|
assert len(errors) == 0
|
|
|
|
def test_empty_text(self):
|
|
"""空文字."""
|
|
ok, errors = validate_text_sticker({"text": ""})
|
|
assert ok is False
|
|
assert any("text" in e for e in errors)
|
|
|
|
def test_zero_font_size(self):
|
|
"""零字号."""
|
|
ok, errors = validate_text_sticker(
|
|
{
|
|
"text": "Hi",
|
|
"font_size": 0,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("font_size" in e for e in errors)
|
|
|
|
def test_empty_font_color(self):
|
|
"""空颜色."""
|
|
ok, errors = validate_text_sticker(
|
|
{
|
|
"text": "Hi",
|
|
"font_color": "",
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("font_color" in e for e in errors)
|
|
|
|
def test_negative_duration(self):
|
|
"""负时长."""
|
|
ok, errors = validate_text_sticker(
|
|
{
|
|
"text": "Hi",
|
|
"duration": -5,
|
|
}
|
|
)
|
|
assert ok is False
|
|
assert any("duration" in e for e in errors)
|