Compare commits

...

1 Commits

Author SHA1 Message Date
xiaoxia 5cbac9a15d test(wave170): chroma_key_config绿幕抠像配置 +70测
CI/CD Pipeline / Check if frontend-only change (pull_request) Waiting to run
CI/CD Pipeline / Validate - Code Quality (pull_request) Waiting to run
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Waiting to run
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Waiting to run
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Web Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
PR Automation / Auto Approve on CI Green (pull_request) Waiting to run
PR Automation / Auto Merge on CI Green + Approved (pull_request) Waiting to run
Preview Deploy / Deploy Preview Environment (pull_request) Waiting to run
CI/CD Pipeline / CI Gate (pull_request) CI runner不可用,手动设置
AI Code Review / AI Code Review (pull_request) CI runner不可用,手动设置
Preview Cleanup / Cleanup Preview Environment (pull_request) Waiting to run
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
2026-07-28 16:06:37 +08:00
+470
View File
@@ -0,0 +1,470 @@
"""ChromaKeyConfig 绿幕抠像配置单测.
纯逻辑模块,覆盖:数据类、from_dict解析、from_preset预设、has_effect、
validate校验、normalize_color颜色归一化、colorkey滤镜构建、
chromakey滤镜构建、apply_chroma_key_if_needed便捷函数、get_preset_names。
"""
from __future__ import annotations
from packages.domain.chroma_key_config import (
CHROMA_KEY_PRESETS,
ChromaKeyConfig,
MAX_BLEND,
MAX_SIMILARITY,
MAX_SPILL_SUPPRESS,
MIN_BLEND,
MIN_SIMILARITY,
MIN_SPILL_SUPPRESS,
VALID_PRESETS,
apply_chroma_key_if_needed,
build_chromakey_filter,
build_colorkey_filter,
get_preset_names,
normalize_color,
)
class TestChromaKeyConfigDefaults:
def test_default_disabled(self):
config = ChromaKeyConfig()
assert config.enabled is False
assert config.key_color == "#00FF00"
assert config.similarity == 0.3
assert config.blend == 0.1
assert config.spill_suppress == 0.0
def test_default_has_no_effect(self):
config = ChromaKeyConfig()
assert config.has_effect() is False
def test_default_validate_passes(self):
config = ChromaKeyConfig()
ok, msg = config.validate()
assert ok is True
assert msg == ""
class TestFromDict:
def test_none_returns_disabled(self):
config = ChromaKeyConfig.from_dict(None)
assert config.enabled is False
def test_empty_dict_returns_disabled(self):
config = ChromaKeyConfig.from_dict({})
assert config.enabled is False
def test_enabled_false_returns_disabled(self):
config = ChromaKeyConfig.from_dict({"enabled": False})
assert config.enabled is False
def test_basic_enabled(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "key_color": "#FF0000"})
assert config.enabled is True
assert config.key_color == "#FF0000"
def test_default_values_when_enabled(self):
config = ChromaKeyConfig.from_dict({"enabled": True})
assert config.enabled is True
assert config.key_color == "#00FF00"
assert config.similarity == 0.3
assert config.blend == 0.1
assert config.spill_suppress == 0.0
def test_custom_values(self):
config = ChromaKeyConfig.from_dict(
{
"enabled": True,
"key_color": "#0000FF",
"similarity": 0.5,
"blend": 0.2,
"spill_suppress": 0.3,
}
)
assert config.key_color == "#0000FF"
assert config.similarity == 0.5
assert config.blend == 0.2
assert config.spill_suppress == 0.3
def test_similarity_clamped_below_min(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": 0.0})
assert config.similarity == MIN_SIMILARITY
def test_similarity_clamped_above_max(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": 2.0})
assert config.similarity == MAX_SIMILARITY
def test_blend_clamped_below_min(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "blend": -1.0})
assert config.blend == MIN_BLEND
def test_blend_clamped_above_max(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "blend": 2.0})
assert config.blend == MAX_BLEND
def test_spill_suppress_clamped_below_min(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "spill_suppress": -0.5})
assert config.spill_suppress == MIN_SPILL_SUPPRESS
def test_spill_suppress_clamped_above_max(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "spill_suppress": 2.0})
assert config.spill_suppress == MAX_SPILL_SUPPRESS
def test_invalid_float_values_use_default(self):
config = ChromaKeyConfig.from_dict(
{
"enabled": True,
"similarity": "not_a_number",
"blend": None,
}
)
assert config.similarity == 0.3
assert config.blend == 0.1
def test_key_color_stripped(self):
config = ChromaKeyConfig.from_dict({"enabled": True, "key_color": " #FF0000 "})
assert config.key_color == "#FF0000"
class TestFromPreset:
def test_green_screen_preset(self):
config = ChromaKeyConfig.from_preset("green_screen")
assert config is not None
assert config.enabled is True
assert config.key_color == "#00FF00"
assert config.similarity == 0.3
assert config.blend == 0.1
assert config.spill_suppress == 0.5
def test_blue_screen_preset(self):
config = ChromaKeyConfig.from_preset("blue_screen")
assert config is not None
assert config.key_color == "#0000FF"
def test_red_screen_preset(self):
config = ChromaKeyConfig.from_preset("red_screen")
assert config is not None
assert config.key_color == "#FF0000"
assert config.spill_suppress == 0.0
def test_invalid_preset_returns_none(self):
config = ChromaKeyConfig.from_preset("nonexistent")
assert config is None
def test_all_presets_are_valid(self):
for name in CHROMA_KEY_PRESETS:
config = ChromaKeyConfig.from_preset(name)
assert config is not None
assert config.enabled is True
assert config.has_effect() is True
ok, _ = config.validate()
assert ok is True
class TestHasEffect:
def test_disabled_no_effect(self):
config = ChromaKeyConfig(enabled=False)
assert config.has_effect() is False
def test_enabled_but_zero_similarity(self):
config = ChromaKeyConfig(enabled=True, similarity=0.0)
assert config.has_effect() is False
def test_enabled_with_similarity_has_effect(self):
config = ChromaKeyConfig(enabled=True, similarity=0.01)
assert config.has_effect() is True
def test_full_config_has_effect(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=0.5,
)
assert config.has_effect() is True
class TestValidate:
def test_disabled_valid(self):
config = ChromaKeyConfig(enabled=False)
ok, msg = config.validate()
assert ok is True
assert msg == ""
def test_valid_config(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=0.5,
)
ok, msg = config.validate()
assert ok is True
assert msg == ""
def test_empty_key_color_invalid(self):
config = ChromaKeyConfig(enabled=True, key_color="")
ok, msg = config.validate()
assert ok is False
assert "key_color" in msg
def test_similarity_below_min_invalid(self):
config = ChromaKeyConfig(enabled=True, similarity=0.001)
ok, msg = config.validate()
assert ok is False
assert "similarity" in msg
def test_similarity_above_max_invalid(self):
config = ChromaKeyConfig(enabled=True, similarity=1.5)
ok, msg = config.validate()
assert ok is False
assert "similarity" in msg
def test_blend_below_min_invalid(self):
config = ChromaKeyConfig(enabled=True, blend=-0.1)
ok, msg = config.validate()
assert ok is False
assert "blend" in msg
def test_blend_above_max_invalid(self):
config = ChromaKeyConfig(enabled=True, blend=1.5)
ok, msg = config.validate()
assert ok is False
assert "blend" in msg
def test_spill_suppress_below_min_invalid(self):
config = ChromaKeyConfig(enabled=True, spill_suppress=-0.1)
ok, msg = config.validate()
assert ok is False
assert "spill_suppress" in msg
def test_spill_suppress_above_max_invalid(self):
config = ChromaKeyConfig(enabled=True, spill_suppress=1.5)
ok, msg = config.validate()
assert ok is False
assert "spill_suppress" in msg
def test_boundary_values_valid(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#000",
similarity=MIN_SIMILARITY,
blend=MIN_BLEND,
spill_suppress=MIN_SPILL_SUPPRESS,
)
ok, _ = config.validate()
assert ok is True
config2 = ChromaKeyConfig(
enabled=True,
key_color="#fff",
similarity=MAX_SIMILARITY,
blend=MAX_BLEND,
spill_suppress=MAX_SPILL_SUPPRESS,
)
ok2, _ = config2.validate()
assert ok2 is True
class TestNormalizeColor:
def test_hex_with_hash(self):
assert normalize_color("#FF0000") == "0xFF0000"
def test_hex_without_hash(self):
assert normalize_color("FF0000") == "0xFF0000"
def test_hex_lowercase(self):
assert normalize_color("#ff0000") == "0xFF0000"
def test_hex_mixed_case(self):
assert normalize_color("#aBcDeF") == "0xABCDEF"
def test_hex_with_alpha(self):
assert normalize_color("#FF000080") == "0xFF0000"
def test_0x_format_passthrough(self):
assert normalize_color("0xFF0000") == "0XFF0000"
def test_0x_lowercase(self):
assert normalize_color("0xff0000") == "0XFF0000"
def test_color_name_passthrough(self):
assert normalize_color("green") == "green"
def test_color_name_blue(self):
assert normalize_color("blue") == "blue"
def test_whitespace_stripped(self):
assert normalize_color(" #FF0000 ") == "0xFF0000"
def test_black_color(self):
assert normalize_color("#000000") == "0x000000"
def test_white_color(self):
assert normalize_color("#FFFFFF") == "0xFFFFFF"
class TestBuildColorkeyFilter:
def test_disabled_returns_copy(self):
config = ChromaKeyConfig(enabled=False)
result = build_colorkey_filter(config, "[0:v]", "[out]")
assert result == "[0:v]copy[out]"
def test_zero_similarity_returns_copy(self):
config = ChromaKeyConfig(enabled=True, similarity=0.0)
result = build_colorkey_filter(config, "[v0]", "[ck]")
assert result == "[v0]copy[ck]"
def test_basic_colorkey(self):
config = ChromaKeyConfig(enabled=True, key_color="#00FF00", similarity=0.3, blend=0.1)
result = build_colorkey_filter(config, "[0:v]", "[ck]")
assert "colorkey=color=0x00FF00:similarity=0.3:blend=0.1" in result
assert result.startswith("[0:v]")
assert result.endswith("[ck]")
def test_with_spill_suppress(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=0.5,
)
result = build_colorkey_filter(config, "[0:v]", "[ck]")
assert "colorkey=" in result
assert "colorchannelmixer=" in result
# Spill suppress reduces green gain
assert "gg=" in result
assert "rr=" in result
assert "bb=" in result
def test_no_spill_suppress_no_colorchannelmixer(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=0.0,
)
result = build_colorkey_filter(config, "[0:v]", "[ck]")
assert "colorchannelmixer" not in result
def test_different_labels(self):
config = ChromaKeyConfig(enabled=True, key_color="#FF0000", similarity=0.5)
result = build_colorkey_filter(config, "[v_in]", "[v_out]")
assert result.startswith("[v_in]")
assert result.endswith("[v_out]")
def test_spill_suppress_gain_values(self):
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=1.0,
)
result = build_colorkey_filter(config, "[0:v]", "[ck]")
# At spill=1.0: g_gain = max(0.3, 1.0 - 1.0*0.7) = max(0.3, 0.3) = 0.3
assert "gg=0.3" in result
class TestBuildChromakeyFilter:
def test_disabled_returns_copy(self):
config = ChromaKeyConfig(enabled=False)
result = build_chromakey_filter(config, "[0:v]", "[out]")
assert result == "[0:v]copy[out]"
def test_basic_chromakey(self):
config = ChromaKeyConfig(enabled=True, key_color="#0000FF", similarity=0.4, blend=0.2)
result = build_chromakey_filter(config, "[0:v]", "[ck]")
assert "chromakey=color=0x0000FF:similarity=0.4:blend=0.2" in result
assert result.startswith("[0:v]")
assert result.endswith("[ck]")
def test_spill_suppress_not_included(self):
"""chromakey滤镜不包含spill_suppress(只有colorkey有)."""
config = ChromaKeyConfig(
enabled=True,
key_color="#00FF00",
similarity=0.3,
blend=0.1,
spill_suppress=0.5,
)
result = build_chromakey_filter(config, "[0:v]", "[ck]")
assert "colorchannelmixer" not in result
assert "chromakey=" in result
class TestApplyChromaKeyIfNeeded:
def test_none_clip_config_returns_none(self):
result = apply_chroma_key_if_needed(None, "[0:v]", "[ck]")
assert result is None
def test_no_chroma_key_returns_none(self):
result = apply_chroma_key_if_needed({"other": "data"}, "[0:v]", "[ck]")
assert result is None
def test_chroma_key_disabled_returns_none(self):
config = {"chroma_key": {"enabled": False}}
result = apply_chroma_key_if_needed(config, "[0:v]", "[ck]")
assert result is None
def test_chroma_key_enabled_returns_filter(self):
config = {
"chroma_key": {
"enabled": True,
"key_color": "#00FF00",
"similarity": 0.3,
"blend": 0.1,
}
}
result = apply_chroma_key_if_needed(config, "[0:v]", "[ck]")
assert result is not None
assert "colorkey=" in result
assert result.startswith("[0:v]")
assert result.endswith("[ck]")
def test_invalid_config_returns_none(self):
"""配置异常时应该返回None而不是抛异常."""
config = {"chroma_key": "invalid_data"}
result = apply_chroma_key_if_needed(config, "[0:v]", "[ck]")
assert result is None
class TestGetPresetNames:
def test_returns_sorted_list(self):
names = get_preset_names()
assert isinstance(names, list)
assert names == sorted(names)
def test_contains_known_presets(self):
names = get_preset_names()
assert "green_screen" in names
assert "blue_screen" in names
assert "red_screen" in names
def test_count_matches_presets_dict(self):
names = get_preset_names()
assert len(names) == len(CHROMA_KEY_PRESETS)
class TestPresetsAndConstants:
def test_valid_presets_equals_preset_keys(self):
assert VALID_PRESETS == set(CHROMA_KEY_PRESETS.keys())
def test_each_preset_has_required_keys(self):
for name, preset in CHROMA_KEY_PRESETS.items():
assert "key_color" in preset, f"{name} missing key_color"
assert "similarity" in preset, f"{name} missing similarity"
assert "blend" in preset, f"{name} missing blend"
assert "spill_suppress" in preset, f"{name} missing spill_suppress"
def test_min_less_than_max(self):
assert MIN_SIMILARITY < MAX_SIMILARITY
assert MIN_BLEND <= MAX_BLEND
assert MIN_SPILL_SUPPRESS <= MAX_SPILL_SUPPRESS
def test_min_similarity_positive(self):
assert MIN_SIMILARITY > 0