Files
xiaoxia-saas/tests/unit/test_watermark_engine.py
T
CI Bot 2df7bc9dc8
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 1m41s
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 / Build Staging Web Image (push) Successful in 1m45s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m34s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 5m49s
CI/CD Pipeline / Frontend Lint (push) Successful in 6m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m27s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
CI/CD Pipeline / Unit Tests (push) Failing after 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m10s
style: auto-format with black + isort + prettier
2026-07-24 13:35:05 +00:00

276 lines
8.4 KiB
Python
Executable File

"""
水印引擎配置与纯逻辑测试.
覆盖 WatermarkConfig.from_dict / validate / 位置枚举等纯逻辑.
引擎核心 render 方法依赖 FFmpeg,由集成测试覆盖.
"""
import pytest
from video_processing.watermark_engine import WATERMARK_POSITIONS, WatermarkConfig
class TestWatermarkPositions:
"""水印位置枚举."""
def test_nine_positions_exist(self):
assert len(WATERMARK_POSITIONS) == 9
assert "top_left" in WATERMARK_POSITIONS
assert "top_center" in WATERMARK_POSITIONS
assert "top_right" in WATERMARK_POSITIONS
assert "center_left" in WATERMARK_POSITIONS
assert "center" in WATERMARK_POSITIONS
assert "center_right" in WATERMARK_POSITIONS
assert "bottom_left" in WATERMARK_POSITIONS
assert "bottom_center" in WATERMARK_POSITIONS
assert "bottom_right" in WATERMARK_POSITIONS
def test_position_values_are_chinese_labels(self):
for key, label in WATERMARK_POSITIONS.items():
assert isinstance(label, str)
assert len(label) >= 2
class TestWatermarkConfigFromDict:
"""from_dict 构造逻辑."""
def test_none_returns_none(self):
assert WatermarkConfig.from_dict(None) is None
def test_empty_dict_returns_none(self):
assert WatermarkConfig.from_dict({}) is None
def test_enabled_false_returns_none(self):
assert WatermarkConfig.from_dict({"enabled": False}) is None
def test_image_mode_without_path_returns_none(self):
result = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "image",
}
)
assert result is None
def test_image_mode_with_empty_path_returns_none(self):
result = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "image",
"image_path": "",
}
)
assert result is None
def test_text_mode_without_text_returns_none(self):
result = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "text",
}
)
assert result is None
def test_text_mode_with_empty_text_returns_none(self):
result = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "text",
"text": "",
}
)
assert result is None
def test_image_mode_success(self):
cfg = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "image",
"image_path": "/tmp/logo.png",
"scale": 0.3,
"opacity": 0.9,
"position": "top_left",
"margin_x": 30,
"margin_y": 30,
}
)
assert cfg is not None
assert cfg.mode == "image"
assert cfg.image_path == "/tmp/logo.png"
assert cfg.scale == 0.3
assert cfg.opacity == 0.9
assert cfg.position == "top_left"
assert cfg.margin_x == 30
assert cfg.margin_y == 30
def test_image_mode_image_key_fallback(self):
"""image 字段作为 image_path 的 fallback."""
cfg = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "image",
"image": "/tmp/fallback.png",
}
)
assert cfg is not None
assert cfg.image_path == "/tmp/fallback.png"
def test_text_mode_success(self):
cfg = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "text",
"text": "hello world",
"font_size": 32,
"font_color": "red",
"position": "bottom_left",
"scroll": True,
"scroll_speed": 100,
}
)
assert cfg is not None
assert cfg.mode == "text"
assert cfg.text == "hello world"
assert cfg.font_size == 32
assert cfg.font_color == "red"
assert cfg.position == "bottom_left"
assert cfg.scroll is True
assert cfg.scroll_speed == 100
def test_invalid_position_falls_back_to_bottom_right(self):
cfg = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "text",
"text": "test",
"position": "invalid_position",
}
)
assert cfg is not None
assert cfg.position == "bottom_right"
def test_default_values_applied(self):
cfg = WatermarkConfig.from_dict(
{
"enabled": True,
"mode": "text",
"text": "test",
}
)
assert cfg is not None
assert cfg.position == "bottom_right"
assert cfg.opacity == 0.8
assert cfg.scale == 0.2
assert cfg.font_size == 24
assert cfg.font_color == "white"
assert cfg.margin_x == 20
assert cfg.margin_y == 20
assert cfg.scroll is False
assert cfg.scroll_speed == 50
class TestWatermarkConfigValidate:
"""validate 校验逻辑."""
def test_valid_image_config(self):
cfg = WatermarkConfig(
mode="image",
image_path="/tmp/logo.png",
position="top_right",
opacity=0.5,
scale=0.5,
)
ok, msg = cfg.validate()
assert ok is True
assert msg == ""
def test_valid_text_config(self):
cfg = WatermarkConfig(
mode="text",
text="hello",
position="center",
opacity=1.0,
font_size=48,
)
ok, msg = cfg.validate()
assert ok is True
assert msg == ""
def test_invalid_position(self):
cfg = WatermarkConfig(mode="text", text="test", position="nowhere")
ok, msg = cfg.validate()
assert ok is False
assert "不支持的位置" in msg
def test_opacity_below_zero(self):
cfg = WatermarkConfig(mode="text", text="test", opacity=-0.1)
ok, msg = cfg.validate()
assert ok is False
assert "透明度" in msg
def test_opacity_above_one(self):
cfg = WatermarkConfig(mode="text", text="test", opacity=1.5)
ok, msg = cfg.validate()
assert ok is False
assert "透明度" in msg
def test_opacity_zero_is_valid(self):
cfg = WatermarkConfig(mode="text", text="test", opacity=0.0)
ok, _ = cfg.validate()
assert ok is True
def test_opacity_one_is_valid(self):
cfg = WatermarkConfig(mode="text", text="test", opacity=1.0)
ok, _ = cfg.validate()
assert ok is True
def test_image_missing_path(self):
cfg = WatermarkConfig(mode="image", image_path="")
ok, msg = cfg.validate()
assert ok is False
assert "图片路径" in msg
def test_image_scale_too_small(self):
cfg = WatermarkConfig(mode="image", image_path="/tmp/a.png", scale=0.001)
ok, msg = cfg.validate()
assert ok is False
assert "缩放比例" in msg
def test_image_scale_too_large(self):
cfg = WatermarkConfig(mode="image", image_path="/tmp/a.png", scale=2.0)
ok, msg = cfg.validate()
assert ok is False
assert "缩放比例" in msg
def test_image_scale_boundary_valid(self):
cfg = WatermarkConfig(mode="image", image_path="/tmp/a.png", scale=0.01)
ok, _ = cfg.validate()
assert ok is True
cfg2 = WatermarkConfig(mode="image", image_path="/tmp/a.png", scale=1.0)
ok2, _ = cfg2.validate()
assert ok2 is True
def test_text_missing_text(self):
cfg = WatermarkConfig(mode="text", text="")
ok, msg = cfg.validate()
assert ok is False
assert "文字内容" in msg
def test_text_font_size_zero(self):
cfg = WatermarkConfig(mode="text", text="test", font_size=0)
ok, msg = cfg.validate()
assert ok is False
assert "字体大小" in msg
def test_text_font_size_negative(self):
cfg = WatermarkConfig(mode="text", text="test", font_size=-5)
ok, msg = cfg.validate()
assert ok is False
assert "字体大小" in msg
def test_unsupported_mode(self):
cfg = WatermarkConfig(mode="video", text="test")
ok, msg = cfg.validate()
assert ok is False
assert "不支持的水印模式" in msg