"""Security tests for the PPTX Generator MCP Server.

Tests XSS prevention and path traversal protection.
"""

import os
import tempfile
from pathlib import Path

from main import (
    _clean_markdown_text,
    _escape_html,
    _is_safe_local_path,
)


class TestHTMLEscaping:
    """Tests for HTML escaping to prevent XSS attacks."""

    def test_escape_html_basic_tags(self):
        """Test that basic HTML tags are escaped."""
        assert _escape_html("<script>alert('xss')</script>") == "&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;"

    def test_escape_html_angle_brackets(self):
        """Test that angle brackets are escaped."""
        assert _escape_html("<div>test</div>") == "&lt;div&gt;test&lt;/div&gt;"

    def test_escape_html_ampersand(self):
        """Test that ampersands are escaped."""
        assert _escape_html("a & b") == "a &amp; b"

    def test_escape_html_quotes(self):
        """Test that quotes are escaped."""
        result = _escape_html('test "quoted" text')
        # Double quotes should be escaped to &quot;
        assert "&quot;" in result
        assert '"' not in result

    def test_escape_html_preserves_safe_text(self):
        """Test that safe text is preserved."""
        safe_text = "Hello World 123"
        assert _escape_html(safe_text) == safe_text

    def test_escape_html_malicious_onclick(self):
        """Test that onclick handlers are escaped."""
        malicious = '<img src="x" onerror="alert(1)">'
        escaped = _escape_html(malicious)
        # HTML tags should be escaped
        assert "<img" not in escaped
        assert "&lt;img" in escaped

    def test_escape_html_event_handlers(self):
        """Test that event handler injections are escaped."""
        malicious = '" onmouseover="alert(1)" "'
        escaped = _escape_html(malicious)
        # Should not contain unescaped quotes that could break out of attributes
        assert "&quot;" in escaped or "&#x27;" in escaped


class TestPathTraversalProtection:
    """Tests for path traversal attack prevention."""

    def test_safe_path_relative(self):
        """Test that relative paths within base directory are allowed."""
        # Create a temporary file in the current directory
        with tempfile.NamedTemporaryFile(delete=False, dir=".") as f:
            temp_path = f.name
        try:
            # Should be safe since it's in the current directory
            assert _is_safe_local_path(temp_path)
        finally:
            os.unlink(temp_path)

    def test_unsafe_path_traversal(self):
        """Test that path traversal attempts are blocked."""
        # Try to access /etc/passwd via path traversal
        assert _is_safe_local_path("../../../etc/passwd") is False
        assert _is_safe_local_path("../../../../etc/passwd") is False

    def test_unsafe_path_absolute(self):
        """Test that absolute paths outside base directory are blocked."""
        # These absolute paths should be blocked as they're outside base directory
        assert _is_safe_local_path("/etc/passwd") is False
        assert _is_safe_local_path("/var/log/syslog") is False

    def test_safe_path_empty(self):
        """Test that empty paths are rejected."""
        assert _is_safe_local_path("") is False
        assert _is_safe_local_path(None) is False

    def test_unsafe_path_with_null_bytes(self):
        """Test that paths with null bytes are handled safely."""
        # Paths with null bytes could be used in certain attacks
        # The function should handle these without crashing and reject them
        try:
            result = _is_safe_local_path("test\x00file.txt")
            # Null bytes in paths should be rejected
            assert result is False
        except (ValueError, OSError):
            # These exceptions are acceptable for malformed paths
            pass

    def test_unsafe_path_double_encoding(self):
        """Test that URL-encoded paths are treated as literal filenames."""
        # %2e%2e is URL encoding for .. but Python's Path treats it as a literal filename
        # URL decoding should happen at the web framework layer, not in path validation
        # This path would be treated as a literal filename in the current directory
        result = _is_safe_local_path("..%2f..%2fetc%2fpasswd")
        # This is actually safe because it's a literal filename, not a decoded path
        # The important thing is that actual path traversal with .. is blocked
        assert isinstance(result, bool)

    def test_safe_path_subdirectory(self):
        """Test that paths to subdirectories are allowed when valid."""
        # Create temp directory structure
        with tempfile.TemporaryDirectory(dir=".") as tmpdir:
            subdir = Path(tmpdir) / "subdir"
            subdir.mkdir()
            test_file = subdir / "test.txt"
            test_file.write_text("test")

            # Relative path to file in subdirectory should be safe
            relative_path = str(test_file)
            # Note: this should be safe because it's within the base directory
            result = _is_safe_local_path(relative_path)
            # The result depends on how the path resolves
            assert isinstance(result, bool)


class TestMarkdownCleaningDoesNotPreventXSS:
    """Test that _clean_markdown_text alone is not sufficient for XSS prevention."""

    def test_clean_markdown_allows_html(self):
        """Test that _clean_markdown_text does NOT escape HTML - it only cleans markdown."""
        # This test documents the expected behavior that markdown cleaning
        # is separate from HTML escaping
        text_with_html = "<script>alert('xss')</script>"
        cleaned = _clean_markdown_text(text_with_html)
        # The markdown cleaner should NOT escape HTML - that's the job of _escape_html
        # It might remove or preserve the text, but the key is we need _escape_html for security
        assert isinstance(cleaned, str)


class TestIntegrationXSSPrevention:
    """Integration tests for XSS prevention in generated HTML."""

    def test_html_generation_escapes_malicious_title(self):
        """Test that malicious content in titles is escaped in generated HTML."""
        # This would require importing the markdown_to_pptx function
        # and checking the generated HTML output
        # For now, we test the building blocks are in place
        malicious_title = "<script>alert('xss')</script>"
        safe_title = _escape_html(_clean_markdown_text(malicious_title))

        # The result should not contain executable script tags
        assert "<script>" not in safe_title
        assert "alert" in safe_title or "xss" in safe_title  # Content preserved but escaped

    def test_html_generation_escapes_malicious_content(self):
        """Test that malicious bullet points are escaped in generated HTML."""
        malicious_content = "- <img src=x onerror=alert(1)>"
        # Clean markdown first (removes bullet point marker)
        cleaned = _clean_markdown_text(malicious_content.lstrip("- "))
        # Then escape for HTML
        safe_content = _escape_html(cleaned)

        # Should not contain executable HTML - tags should be escaped
        assert "<img" not in safe_content
        assert "&lt;img" in safe_content
