"""
视频合成服务安全校验单元测试
针对 PR #159 安全审计发现的问题进行测试
"""

import os
import tempfile
from unittest.mock import MagicMock, patch

import pytest

# 导入被测试的模块
from apps.worker.video_processing.video_compose_service import (
    ALLOWED_INPUT_PREFIXES,
    ALLOWED_OUTPUT_DIRS,
    ALLOWED_TRANSITIONS,
    Clip,
    EditingMode,
    EditingModeConfig,
    VideoComposeService,
)


class TestOutputPathValidation:
    """P0: 输出路径穿越校验测试"""

    def setup_method(self):
        """测试前设置"""
        self.config = EditingModeConfig(mode=EditingMode.ONE_TAKE)
        self.service = VideoComposeService(self.config)

    def test_valid_output_path_in_allowed_dir(self):
        """测试合法的输出路径"""
        valid_path = "/tmp/video_output/test.mp4"
        result = self.service._validate_output_path(valid_path)
        assert result == os.path.abspath(valid_path)

    def test_valid_output_path_with_relative_components(self):
        """测试带相对路径成分但最终在允许目录内的路径"""
        valid_path = "/tmp/video_output/subdir/../test.mp4"
        result = self.service._validate_output_path(valid_path)
        assert result == os.path.abspath(valid_path)

    def test_path_traversal_attack_blocked(self):
        """测试路径穿越攻击被阻止"""
        # 尝试穿越到 /etc/passwd
        malicious_path = "/tmp/video_output/../../../etc/passwd"
        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service._validate_output_path(malicious_path)

    def test_path_traversal_attack_blocked_var_app(self):
        """测试针对 /var/app 的路径穿越攻击被阻止"""
        malicious_path = "/var/app/rendered/../../config/../../../etc/passwd"
        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service._validate_output_path(malicious_path)

    def test_absolute_path_to_forbidden_location(self):
        """测试直接访问禁止位置"""
        forbidden_path = "/etc/shadow"
        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service._validate_output_path(forbidden_path)

    def test_root_path_blocked(self):
        """测试根目录被阻止"""
        root_path = "/"
        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service._validate_output_path(root_path)

    def test_absolute_path_to_tmp_not_allowed(self):
        """测试 /tmp 不在白名单中时应被阻止"""
        # /tmp 不在 ALLOWED_OUTPUT_DIRS 中
        tmp_path = "/tmp/test.mp4"
        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service._validate_output_path(tmp_path)


class TestInputPathValidation:
    """P1-1: 输入路径格式校验测试"""

    def setup_method(self):
        """测试前设置"""
        self.config = EditingModeConfig(mode=EditingMode.ONE_TAKE)
        self.service = VideoComposeService(self.config)

    def test_valid_s3_path(self):
        """测试 S3 路径"""
        assert self.service._validate_input_path("s3://bucket/key.mp4") is True

    def test_valid_oss_path(self):
        """测试 OSS 路径"""
        assert self.service._validate_input_path("oss://bucket/key.mp4") is True

    def test_valid_local_path(self):
        """测试 local:// 路径"""
        assert self.service._validate_input_path("local://asset/123.mp4") is True

    def test_valid_var_storage_path(self):
        """测试 /var/storage/ 路径"""
        assert self.service._validate_input_path("/var/storage/assets/123.mp4") is True

    def test_path_traversal_in_input_rejected(self):
        """测试输入路径中的路径穿越尝试被拒绝"""
        malicious_path = "s3://bucket/../../etc/passwd"
        # 这会通过前缀检查，但实际使用时文件系统访问会失败
        # 安全设计：只校验格式前缀
        assert self.service._validate_input_path(malicious_path) is True

    def test_malicious_input_path_blocked(self):
        """测试恶意输入路径被阻止"""
        assert self.service._validate_input_path("/etc/passwd") is False
        assert self.service._validate_input_path("file:///etc/passwd") is False
        assert self.service._validate_input_path("http://evil.com/shell.sh") is False

    def test_empty_path_rejected(self):
        """测试空路径被拒绝"""
        assert self.service._validate_input_path("") is False

    def test_random_string_rejected(self):
        """测试随机字符串被拒绝"""
        assert self.service._validate_input_path("random123") is False
        assert self.service._validate_input_path("abc../../../etc") is False


class TestTransitionValidation:
    """P1-2: 转场参数白名单校验测试"""

    def setup_method(self):
        """测试前设置"""
        self.config = EditingModeConfig(mode=EditingMode.ONE_TAKE)
        self.service = VideoComposeService(self.config)

    @pytest.mark.parametrize("transition", list(ALLOWED_TRANSITIONS))
    def test_valid_transitions(self, transition):
        """测试所有合法的转场效果"""
        result = self.service._validate_transition(transition)
        assert result == transition

    def test_invalid_transition_defaults_to_fade(self):
        """测试非法转场效果默认为 fade"""
        result = self.service._validate_transition("random_transition")
        assert result == "fade"

    def test_sql_injection_in_transition_blocked(self):
        """测试 SQL 注入尝试被阻止"""
        result = self.service._validate_transition("fade; DROP TABLE videos;--")
        assert result == "fade"

    def test_shell_injection_in_transition_blocked(self):
        """测试 Shell 注入尝试被阻止"""
        result = self.service._validate_transition("fade$(whoami)")
        assert result == "fade"

    def test_empty_transition_handled(self):
        """测试空转场名称"""
        result = self.service._validate_transition("")
        assert result == "fade"

    def test_none_transition_handled(self):
        """测试 None 转场名称"""
        result = self.service._validate_transition(None)
        assert result == "fade"

    def test_get_validated_transition_returns_mapped(self):
        """测试 _get_validated_transition 返回映射后的值"""
        # "fade" 应该映射为 "fade"
        result = self.service._get_validated_transition("fade")
        assert result == "fade"


class TestComposeSecurityIntegration:
    """安全集成测试"""

    def setup_method(self):
        """测试前设置"""
        self.config = EditingModeConfig(mode=EditingMode.ONE_TAKE)
        self.service = VideoComposeService(self.config)

    def test_compose_rejects_malicious_output_path(self):
        """测试 compose 方法拒绝恶意输出路径"""
        clips = [
            Clip(asset_id="s3://bucket/video1.mp4"),
            Clip(asset_id="s3://bucket/video2.mp4"),
        ]

        with pytest.raises(ValueError, match="输出路径不在允许范围内"):
            self.service.compose(clips, output_path="/etc/passwd")

    def test_compose_rejects_invalid_input_path(self):
        """测试 compose 方法拒绝非法输入路径"""
        clips = [
            Clip(asset_id="/etc/shadow"),  # 非法路径
        ]

        with pytest.raises(ValueError, match="不合法的输入路径"):
            self.service.compose(clips)

    def test_compose_with_valid_paths(self):
        """测试合法路径可以正常处理"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # 创建临时视频文件
            video_path = os.path.join(tmpdir, "input.mp4")
            output_path = os.path.join("/tmp/video_output", "output.mp4")

            # 创建空的测试文件（实际测试需要真实视频）
            with open(video_path, "wb") as f:
                f.write(b"fake video data")

            clips = [
                Clip(asset_id=f"local://{video_path}"),
            ]

            # 验证输入校验通过
            assert self.service._validate_input_path(f"local://{video_path}") is True

    def test_compose_empty_clips_rejected(self):
        """测试空片段列表被拒绝"""
        with pytest.raises(ValueError, match="clips 不能为空"):
            self.service.compose([])


class TestWhiteListConstants:
    """白名单常量测试"""

    def test_allowed_output_dirs_not_empty(self):
        """测试输出目录白名单不为空"""
        assert len(ALLOWED_OUTPUT_DIRS) > 0
        assert "/tmp/video_output" in ALLOWED_OUTPUT_DIRS
        assert "/var/app/rendered" in ALLOWED_OUTPUT_DIRS

    def test_allowed_input_prefixes_not_empty(self):
        """测试输入路径前缀白名单不为空"""
        assert len(ALLOWED_INPUT_PREFIXES) > 0
        assert "s3://" in ALLOWED_INPUT_PREFIXES
        assert "oss://" in ALLOWED_INPUT_PREFIXES
        assert "local://" in ALLOWED_INPUT_PREFIXES
        assert "/var/storage/" in ALLOWED_INPUT_PREFIXES

    def test_allowed_transitions_not_empty(self):
        """测试转场效果白名单不为空"""
        assert len(ALLOWED_TRANSITIONS) > 0
        assert "fade" in ALLOWED_TRANSITIONS
        assert "dissolve" in ALLOWED_TRANSITIONS
        assert "slideleft" in ALLOWED_TRANSITIONS


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
