""" 水印引擎配置与纯逻辑测试. 覆盖 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