"""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, )