"""水印引擎单元测试 - 配置解析 + 位置计算等纯逻辑.""" from __future__ import annotations import pytest from video_processing.watermark_engine import ( WATERMARK_POSITIONS, WatermarkConfig, WatermarkEngine, ) # ── WatermarkConfig 测试 ────────────────────────────────────────── class TestWatermarkConfigDefaults: """默认值测试.""" def test_default_values(self): """默认配置值正确.""" config = WatermarkConfig() assert config.mode == "text" assert config.position == "bottom_right" assert config.image_path == "" assert config.scale == 0.2 assert config.opacity == 0.8 assert config.text == "" assert config.font_size == 24 assert config.font_color == "white" assert config.font_path == "" assert config.margin_x == 20 assert config.margin_y == 20 assert config.scroll is False assert config.scroll_speed == 50 class TestWatermarkConfigFromDict: """from_dict 配置解析测试.""" def test_none_returns_none(self): """None 返回 None.""" assert WatermarkConfig.from_dict(None) is None def test_empty_dict_returns_none(self): """空字典返回 None.""" assert WatermarkConfig.from_dict({}) is None def test_disabled_returns_none(self): """enabled=False 返回 None.""" assert WatermarkConfig.from_dict({"enabled": False}) is None def test_text_mode_basic(self): """文字水印基本配置.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "测试水印", } ) assert config is not None assert config.mode == "text" assert config.text == "测试水印" assert config.position == "bottom_right" # 默认 def test_text_mode_missing_text_returns_none(self): """文字水印缺少 text 返回 None.""" result = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", } ) assert result is None def test_text_mode_empty_text_returns_none(self): """文字水印 text 为空返回 None.""" result = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "", } ) assert result is None def test_image_mode_basic(self): """图片水印基本配置.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "image", "image_path": "/path/to/logo.png", } ) assert config is not None assert config.mode == "image" assert config.image_path == "/path/to/logo.png" def test_image_mode_missing_image_returns_none(self): """图片水印缺少 image_path 返回 None.""" result = WatermarkConfig.from_dict( { "enabled": True, "mode": "image", } ) assert result is None def test_image_mode_image_alias(self): """image 字段作为 image_path 的别名.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "image", "image": "/path/alias.png", } ) assert config is not None assert config.image_path == "/path/alias.png" def test_invalid_position_falls_back(self): """无效位置 fallback 到 bottom_right.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "test", "position": "invalid_pos", } ) assert config is not None assert config.position == "bottom_right" def test_custom_position_valid(self): """自定义有效位置.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "test", "position": "top_left", } ) assert config is not None assert config.position == "top_left" def test_all_text_fields_parsed(self): """文字水印所有字段正确解析.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "我的水印", "font_size": 32, "font_color": "red", "font_path": "/fonts/msyh.ttf", "position": "top_center", "opacity": 0.5, "margin_x": 30, "margin_y": 40, } ) assert config is not None assert config.text == "我的水印" assert config.font_size == 32 assert config.font_color == "red" assert config.font_path == "/fonts/msyh.ttf" assert config.position == "top_center" assert config.opacity == 0.5 assert config.margin_x == 30 assert config.margin_y == 40 def test_all_image_fields_parsed(self): """图片水印所有字段正确解析.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "image", "image_path": "/img/logo.png", "scale": 0.3, "opacity": 0.9, "position": "bottom_left", "margin_x": 10, "margin_y": 15, } ) assert config is not None assert config.image_path == "/img/logo.png" assert config.scale == 0.3 assert config.opacity == 0.9 assert config.position == "bottom_left" def test_scroll_config_parsed(self): """滚动水印配置解析.""" config = WatermarkConfig.from_dict( { "enabled": True, "mode": "text", "text": "滚动水印", "scroll": True, "scroll_speed": 80, } ) assert config is not None assert config.scroll is True assert config.scroll_speed == 80 def test_default_mode_is_text(self): """不传 mode 默认为 text.""" config = WatermarkConfig.from_dict( { "enabled": True, "text": "默认模式", } ) assert config is not None assert config.mode == "text" class TestWatermarkConfigValidate: """validate 配置校验测试.""" def test_valid_text_config(self): """合法文字水印配置.""" config = WatermarkConfig(mode="text", text="测试", position="bottom_right") ok, msg = config.validate() assert ok is True assert msg == "" def test_valid_image_config(self): """合法图片水印配置.""" config = WatermarkConfig( mode="image", image_path="/a.png", position="top_left", scale=0.3, opacity=0.8, ) ok, msg = config.validate() assert ok is True def test_invalid_position(self): """无效位置.""" config = WatermarkConfig(mode="text", text="test", position="invalid") ok, msg = config.validate() assert ok is False assert "不支持的位置" in msg def test_opacity_too_high(self): """透明度超过1.""" config = WatermarkConfig(mode="text", text="test", opacity=1.5) ok, msg = config.validate() assert ok is False assert "透明度" in msg def test_opacity_negative(self): """透明度为负.""" config = WatermarkConfig(mode="text", text="test", opacity=-0.1) ok, msg = config.validate() assert ok is False assert "透明度" in msg def test_opacity_boundary_zero(self): """透明度边界值0.""" config = WatermarkConfig(mode="text", text="test", opacity=0.0) ok, _ = config.validate() assert ok is True def test_opacity_boundary_one(self): """透明度边界值1.""" config = WatermarkConfig(mode="text", text="test", opacity=1.0) ok, _ = config.validate() assert ok is True def test_image_missing_path(self): """图片水印缺少路径.""" config = WatermarkConfig(mode="image", image_path="") ok, msg = config.validate() assert ok is False assert "图片路径" in msg def test_image_scale_too_small(self): """缩放比例太小.""" config = WatermarkConfig(mode="image", image_path="/a.png", scale=0.001) ok, msg = config.validate() assert ok is False assert "缩放比例" in msg def test_image_scale_too_large(self): """缩放比例太大.""" config = WatermarkConfig(mode="image", image_path="/a.png", scale=2.0) ok, msg = config.validate() assert ok is False assert "缩放比例" in msg def test_image_scale_boundary_low(self): """缩放边界低值.""" config = WatermarkConfig(mode="image", image_path="/a.png", scale=0.01) ok, _ = config.validate() assert ok is True def test_image_scale_boundary_high(self): """缩放边界高值.""" config = WatermarkConfig(mode="image", image_path="/a.png", scale=1.0) ok, _ = config.validate() assert ok is True def test_text_missing_content(self): """文字水印缺少内容.""" config = WatermarkConfig(mode="text", text="") ok, msg = config.validate() assert ok is False assert "文字内容" in msg def test_text_font_size_zero(self): """字体大小为0.""" config = WatermarkConfig(mode="text", text="test", font_size=0) ok, msg = config.validate() assert ok is False assert "字体大小" in msg def test_text_font_size_negative(self): """字体大小为负.""" config = WatermarkConfig(mode="text", text="test", font_size=-5) ok, msg = config.validate() assert ok is False assert "字体大小" in msg def test_unknown_mode(self): """未知模式.""" config = WatermarkConfig(mode="unknown_mode") ok, msg = config.validate() assert ok is False assert "不支持的水印模式" in msg # ── WatermarkEngine 位置计算测试 ──────────────────────────────── class TestCalcPosition: """9宫格位置计算测试.""" # 测试用:输出 1920x1080,水印 200x100,边距 20 W, H = 1920, 1080 WW, WH = 200, 100 MX, MY = 20, 20 def test_top_left(self): """左上角.""" x, y = WatermarkEngine.calc_position("top_left", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert (x, y) == (20, 20) def test_top_center(self): """中上.""" x, y = WatermarkEngine.calc_position("top_center", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == (1920 - 200) // 2 assert y == 20 def test_top_right(self): """右上角.""" x, y = WatermarkEngine.calc_position("top_right", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 1920 - 200 - 20 assert y == 20 def test_center_left(self): """左中.""" x, y = WatermarkEngine.calc_position("center_left", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 20 assert y == (1080 - 100) // 2 def test_center(self): """中心.""" x, y = WatermarkEngine.calc_position("center", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == (1920 - 200) // 2 assert y == (1080 - 100) // 2 def test_center_right(self): """右中.""" x, y = WatermarkEngine.calc_position("center_right", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 1920 - 200 - 20 assert y == (1080 - 100) // 2 def test_bottom_left(self): """左下角.""" x, y = WatermarkEngine.calc_position("bottom_left", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 20 assert y == 1080 - 100 - 20 def test_bottom_center(self): """中下.""" x, y = WatermarkEngine.calc_position("bottom_center", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == (1920 - 200) // 2 assert y == 1080 - 100 - 20 def test_bottom_right(self): """右下角.""" x, y = WatermarkEngine.calc_position("bottom_right", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 1920 - 200 - 20 assert y == 1080 - 100 - 20 def test_unknown_position_defaults_bottom_right(self): """未知位置默认右下角.""" x, y = WatermarkEngine.calc_position("unknown", self.W, self.H, self.WW, self.WH, self.MX, self.MY) assert x == 1920 - 200 - 20 assert y == 1080 - 100 - 20 def test_zero_margin(self): """零边距.""" x, y = WatermarkEngine.calc_position("top_left", 1000, 500, 100, 50, 0, 0) assert (x, y) == (0, 0) def test_small_output(self): """小尺寸输出.""" x, y = WatermarkEngine.calc_position("bottom_right", 320, 240, 50, 30, 5, 5) assert x == 320 - 50 - 5 assert y == 240 - 30 - 5 class TestCalcScrollX: """滚动水印x坐标表达式测试.""" def test_returns_string_expression(self): """返回字符串表达式.""" expr = WatermarkEngine.calc_scroll_x("bottom_right", 1920, 200, 50) assert isinstance(expr, str) assert "1920" in expr assert "200" in expr assert "50" in expr def test_contains_mod_function(self): """包含 mod 函数.""" expr = WatermarkEngine.calc_scroll_x("top_left", 1280, 150, 60) assert "mod(" in expr assert "t" in expr # 时间变量 class TestWatermarkPositions: """位置常量测试.""" def test_nine_positions(self): """共9个位置.""" assert len(WATERMARK_POSITIONS) == 9 def test_all_position_keys_valid(self): """所有位置键名正确.""" expected = { "top_left", "top_center", "top_right", "center_left", "center", "center_right", "bottom_left", "bottom_center", "bottom_right", } assert set(WATERMARK_POSITIONS.keys()) == expected