@@ -0,0 +1,350 @@
""" filter_presets 单元测试 - wave164
覆盖:
- FilterPreset 数据类(frozen/默认值/字段)
- FILTER_PRESET_LIBRARY 预设库(数量/分类/ID唯一性)
- get_filter_preset 按ID获取
- list_filter_presets 筛选列表(分类/关键词)
- build_ffmpeg_filter 滤镜生成(强度/参数插值/边界/空值)
"""
import dataclasses
import pytest
from packages . domain . filter_presets import (
FILTER_PRESET_LIBRARY ,
FilterPreset ,
build_ffmpeg_filter ,
get_filter_preset ,
list_filter_presets ,
)
# ============================================================
# FilterPreset 数据类
# ============================================================
class TestFilterPresetDataclass :
def test_minimal_creation ( self ) :
p = FilterPreset ( id = " test " , name = " Test " , category = " basic " )
assert p . id == " test "
assert p . name == " Test "
assert p . category == " basic "
def test_default_values ( self ) :
p = FilterPreset ( id = " t " , name = " T " , category = " basic " )
assert p . description == " "
assert p . tags == [ ]
assert p . brightness == 0.0
assert p . contrast == 1.0
assert p . saturation == 1.0
assert p . gamma == 1.0
assert p . gamma_r == 1.0
assert p . gamma_g == 1.0
assert p . gamma_b == 1.0
assert p . hue == 0.0
assert p . lut_url == " "
def test_full_creation ( self ) :
p = FilterPreset (
id = " full " ,
name = " Full " ,
category = " cinematic " ,
description = " test desc " ,
tags = [ " tag1 " , " tag2 " ] ,
brightness = 0.5 ,
contrast = 1.5 ,
saturation = 2.0 ,
gamma = 1.2 ,
gamma_r = 1.3 ,
gamma_g = 0.9 ,
gamma_b = 0.8 ,
hue = 30.0 ,
lut_url = " http://lut.png " ,
)
assert p . description == " test desc "
assert p . tags == [ " tag1 " , " tag2 " ]
assert p . brightness == 0.5
assert p . contrast == 1.5
assert p . gamma == 1.2
assert p . hue == 30.0
assert p . lut_url == " http://lut.png "
def test_frozen_immutable ( self ) :
p = FilterPreset ( id = " t " , name = " T " , category = " basic " )
with pytest . raises ( ( AttributeError , dataclasses . FrozenInstanceError ) ) :
p . brightness = 0.5 # frozen=True,不能修改
def test_tags_default_new_list ( self ) :
# 每个实例有独立的列表
p1 = FilterPreset ( id = " t1 " , name = " T1 " , category = " basic " )
p2 = FilterPreset ( id = " t2 " , name = " T2 " , category = " basic " )
assert p1 . tags is not p2 . tags
# ============================================================
# FILTER_PRESET_LIBRARY 预设库
# ============================================================
class TestFilterPresetLibrary :
def test_library_not_empty ( self ) :
assert len ( FILTER_PRESET_LIBRARY ) > 0
def test_all_ids_unique ( self ) :
ids = [ p . id for p in FILTER_PRESET_LIBRARY ]
assert len ( ids ) == len ( set ( ids ) )
def test_all_have_required_fields ( self ) :
for p in FILTER_PRESET_LIBRARY :
assert p . id , f " 预设缺少id: { p } "
assert p . name , f " 预设缺少name: { p . id } "
assert p . category , f " 预设缺少category: { p . id } "
def test_categories_are_valid ( self ) :
valid_categories = { " basic " , " cinematic " , " vintage " , " bw " , " style " }
for p in FILTER_PRESET_LIBRARY :
assert p . category in valid_categories , f " 无效分类: { p . id } -> { p . category } "
def test_basic_category_exists ( self ) :
basics = [ p for p in FILTER_PRESET_LIBRARY if p . category == " basic " ]
assert len ( basics ) > = 3
def test_cinematic_category_exists ( self ) :
cinematic = [ p for p in FILTER_PRESET_LIBRARY if p . category == " cinematic " ]
assert len ( cinematic ) > = 1
def test_vintage_category_exists ( self ) :
vintage = [ p for p in FILTER_PRESET_LIBRARY if p . category == " vintage " ]
assert len ( vintage ) > = 1
def test_bw_category_exists ( self ) :
bw = [ p for p in FILTER_PRESET_LIBRARY if p . category == " bw " ]
assert len ( bw ) > = 1
# 所有黑白预设饱和度为0
for p in bw :
assert p . saturation == 0.0
def test_style_category_exists ( self ) :
style = [ p for p in FILTER_PRESET_LIBRARY if p . category == " style " ]
assert len ( style ) > = 1
def test_none_filter_has_no_effect ( self ) :
none_preset = get_filter_preset ( " filter_none " )
assert none_preset is not None
assert none_preset . brightness == 0.0
assert none_preset . contrast == 1.0
assert none_preset . saturation == 1.0
assert none_preset . gamma == 1.0
def test_total_count ( self ) :
# 至少有15个预设
assert len ( FILTER_PRESET_LIBRARY ) > = 15
# ============================================================
# get_filter_preset
# ============================================================
class TestGetFilterPreset :
def test_existing_id ( self ) :
p = get_filter_preset ( " filter_brighten " )
assert p is not None
assert p . id == " filter_brighten "
assert p . name == " 明亮 "
def test_nonexistent_id ( self ) :
assert get_filter_preset ( " nonexistent " ) is None
def test_empty_id ( self ) :
assert get_filter_preset ( " " ) is None
def test_case_sensitive ( self ) :
# 大小写敏感
assert get_filter_preset ( " Filter_Brighten " ) is None
def test_returns_preset_object ( self ) :
p = get_filter_preset ( " filter_cinematic " )
assert isinstance ( p , FilterPreset )
# ============================================================
# list_filter_presets
# ============================================================
class TestListFilterPresets :
def test_no_filters_returns_all ( self ) :
result = list_filter_presets ( )
assert len ( result ) == len ( FILTER_PRESET_LIBRARY )
def test_filter_by_category_basic ( self ) :
result = list_filter_presets ( category = " basic " )
assert len ( result ) > 0
for p in result :
assert p . category == " basic "
def test_filter_by_category_cinematic ( self ) :
result = list_filter_presets ( category = " cinematic " )
for p in result :
assert p . category == " cinematic "
def test_filter_by_category_bw ( self ) :
result = list_filter_presets ( category = " bw " )
for p in result :
assert p . category == " bw "
def test_filter_by_category_vintage ( self ) :
result = list_filter_presets ( category = " vintage " )
for p in result :
assert p . category == " vintage "
def test_filter_by_category_style ( self ) :
result = list_filter_presets ( category = " style " )
for p in result :
assert p . category == " style "
def test_invalid_category_returns_empty ( self ) :
result = list_filter_presets ( category = " nonexistent " )
assert result == [ ]
def test_keyword_search_name ( self ) :
result = list_filter_presets ( keyword = " 电影 " )
assert len ( result ) > = 1
names = [ p . name for p in result ]
assert any ( " 电影 " in n for n in names )
def test_keyword_search_tags ( self ) :
result = list_filter_presets ( keyword = " 复古 " )
assert len ( result ) > = 1
# 至少有一个的标签或名称包含复古
found = any ( any ( " 复古 " in t for t in p . tags ) or " 复古 " in p . name for p in result )
assert found
def test_keyword_search_description ( self ) :
result = list_filter_presets ( keyword = " 青橙 " )
assert len ( result ) > = 1
def test_keyword_case_insensitive ( self ) :
# 中文不区分大小写,用英文标签/名称试
result = list_filter_presets ( keyword = " FILTER " )
# 搜索 filter 应该能匹配到很多(id里有但搜索name/desc/tags)
# 用确定的中文关键词更可靠
assert isinstance ( result , list )
def test_keyword_nonexistent_returns_empty ( self ) :
result = list_filter_presets ( keyword = " zzz不存在的关键词zzz " )
assert result == [ ]
def test_category_and_keyword_combined ( self ) :
result = list_filter_presets ( category = " basic " , keyword = " 亮 " )
for p in result :
assert p . category == " basic "
assert " 亮 " in p . name or " 亮 " in p . description or any ( " 亮 " in t for t in p . tags )
def test_keyword_empty_returns_all ( self ) :
result = list_filter_presets ( keyword = " " )
assert len ( result ) == len ( FILTER_PRESET_LIBRARY )
def test_returns_list_of_presets ( self ) :
result = list_filter_presets ( )
for p in result :
assert isinstance ( p , FilterPreset )
# ============================================================
# build_ffmpeg_filter
# ============================================================
class TestBuildFfmpegFilter :
def test_valid_preset_full_intensity ( self ) :
result = build_ffmpeg_filter ( " filter_brighten " , 100 )
assert result . startswith ( " eq= " )
assert " brightness= " in result
assert " contrast= " in result
def test_invalid_preset_returns_empty ( self ) :
assert build_ffmpeg_filter ( " nonexistent " , 100 ) == " "
def test_zero_intensity_returns_empty ( self ) :
assert build_ffmpeg_filter ( " filter_brighten " , 0 ) == " "
def test_negative_intensity_returns_empty ( self ) :
assert build_ffmpeg_filter ( " filter_brighten " , - 10 ) == " "
def test_intensity_over_100_clamped ( self ) :
# >100 按100算
r100 = build_ffmpeg_filter ( " filter_saturate " , 100 )
r200 = build_ffmpeg_filter ( " filter_saturate " , 200 )
assert r100 == r200
def test_half_intensity_half_effect ( self ) :
full = build_ffmpeg_filter ( " filter_brighten " , 100 )
half = build_ffmpeg_filter ( " filter_brighten " , 50 )
# 半强度的brightness应该小一些
assert full != half
# 半强度仍然有效果
assert half . startswith ( " eq= " )
def test_none_filter_returns_empty ( self ) :
# filter_none所有参数都是默认值,应该返回空
result = build_ffmpeg_filter ( " filter_none " , 100 )
assert result == " "
def test_format_has_eq_prefix ( self ) :
result = build_ffmpeg_filter ( " filter_saturate " , 100 )
assert result . startswith ( " eq= " )
def test_parameters_separated_by_colon ( self ) :
result = build_ffmpeg_filter ( " filter_cinematic " , 100 )
parts = result [ len ( " eq= " ) : ] . split ( " : " )
assert len ( parts ) > = 3 # 至少有3个参数
def test_brightness_value_format ( self ) :
result = build_ffmpeg_filter ( " filter_brighten " , 100 )
# brightness参数应该存在且是数值
assert " brightness=0. " in result or " brightness=-0. " in result
def test_saturation_value_format ( self ) :
result = build_ffmpeg_filter ( " filter_saturate " , 100 )
assert " saturation= " in result
def test_contrast_value_format ( self ) :
result = build_ffmpeg_filter ( " filter_contrast " , 100 )
assert " contrast= " in result
def test_gamma_parameters_present ( self ) :
result = build_ffmpeg_filter ( " filter_warm " , 100 )
# 暖色预设应该有gamma_r/gamma_b变化
assert " gamma_r= " in result or " gamma_b= " in result
def test_very_low_intensity_near_zero ( self ) :
# 强度1%,效果接近0,但因为有brightness等可能仍然在阈值以上
result = build_ffmpeg_filter ( " filter_brighten " , 1 )
# 至少应该有一个参数(brightness * 0.01 = 0.0012 > 0.001)
assert isinstance ( result , str )
def test_all_presets_generate_valid_output ( self ) :
# 每个预设在100强度下都能生成合法结果
for preset in FILTER_PRESET_LIBRARY :
result = build_ffmpeg_filter ( preset . id , 100 )
assert isinstance ( result , str )
if result : # filter_none 可能为空
assert result . startswith ( " eq= " )
def test_intensity_50_between_0_and_100 ( self ) :
# 验证50%强度确实在0和100之间插值
r0 = build_ffmpeg_filter ( " filter_saturate " , 0 )
r50 = build_ffmpeg_filter ( " filter_saturate " , 50 )
r100 = build_ffmpeg_filter ( " filter_saturate " , 100 )
assert r0 == " "
assert r50 != r100
assert r50 != " "
def test_hue_not_included_in_eq ( self ) :
# hue 不在 eq 滤镜参数中(当前实现没有hue)
result = build_ffmpeg_filter ( " filter_cinematic " , 100 )
assert " hue= " not in result