"""Edge-case tests for path_validator — control chars, null bytes, and case normalization."""

import tempfile
from pathlib import Path

import pytest

from local_deep_research.security.path_validator import PathValidator


class TestControlCharactersRejected:
    """Verify various control characters (ord < 32) are rejected."""

    def test_tab_character_rejected(self):
        """Tab (\\t, ord 9) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\tdir")

    def test_bell_character_rejected(self):
        """Bell (\\x07, ord 7) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\x07dir")

    def test_newline_in_path_rejected(self):
        """Newline (\\n, ord 10) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\ndir")


class TestNullBytesRejected:
    """Verify validate_config_path rejects null bytes."""

    def test_multiple_null_bytes_rejected(self):
        """Multiple null bytes in config path should raise ValueError."""
        with pytest.raises(ValueError, match="Null bytes"):
            PathValidator.validate_config_path(
                "s\x00e\x00ttings.json",
                config_root="/tmp",
            )


class TestEtcWithDifferentCasingBlocked:
    """Verify .lower() normalization blocks case-varied restricted paths."""

    def test_etc_with_different_casing_blocked(self):
        """/ETC/passwd should be blocked via .lower() normalization in validate_config_path."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/ETC/passwd")

    def test_etc_mixed_case_blocked(self):
        """/Etc/shadow should also be blocked."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/Etc/shadow")


class TestSafeJoinReturnsNoneRaisesValueError:
    """Verify path traversal via safe_join → None → ValueError."""

    @pytest.fixture
    def temp_base_dir(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            yield Path(tmpdir)

    def test_safe_join_returns_none_raises_valueerror(self, temp_base_dir):
        """Path traversal attempt where safe_join returns None must raise ValueError."""
        with pytest.raises(ValueError):
            PathValidator.validate_safe_path(
                "../../../../../etc/passwd",
                temp_base_dir,
            )
