"""Unit tests for path validator.""" from pathlib import Path from tempfile import TemporaryDirectory import pytest from webdrop_bridge.core.validator import PathValidator, ValidationError class TestPathValidator: """Test path validation.""" def test_validator_initialization(self, tmp_path): """Test creating a validator with valid roots.""" dir1 = tmp_path / "dir1" dir2 = tmp_path / "dir2" dir1.mkdir() dir2.mkdir() validator = PathValidator([dir1, dir2]) assert len(validator.allowed_roots) == 2 def test_validator_nonexistent_root(self, tmp_path): """Test that nonexistent root raises ValidationError.""" nonexistent = tmp_path / "nonexistent" with pytest.raises(ValidationError, match="does not exist"): PathValidator([nonexistent]) def test_validator_non_directory_root(self, tmp_path): """Test that non-directory root raises ValidationError.""" file_path = tmp_path / "file.txt" file_path.write_text("test") with pytest.raises(ValidationError, match="not a directory"): PathValidator([file_path]) def test_validate_valid_file(self, tmp_path): """Test validating a file within allowed root.""" file_path = tmp_path / "test.txt" file_path.write_text("test content") validator = PathValidator([tmp_path]) assert validator.validate(file_path) is True assert validator.is_valid(file_path) is True def test_validate_nonexistent_file(self, tmp_path): """Test that nonexistent file raises ValidationError.""" validator = PathValidator([tmp_path]) nonexistent = tmp_path / "nonexistent.txt" with pytest.raises(ValidationError, match="does not exist"): validator.validate(nonexistent) def test_validate_directory_path(self, tmp_path): """Test that directory path raises ValidationError.""" subdir = tmp_path / "subdir" subdir.mkdir() validator = PathValidator([tmp_path]) with pytest.raises(ValidationError, match="not a regular file"): validator.validate(subdir) def test_validate_file_outside_roots(self, tmp_path): """Test that file outside allowed roots raises ValidationError.""" allowed_dir = tmp_path / "allowed" other_dir = tmp_path / "other" allowed_dir.mkdir() other_dir.mkdir() file_in_other = other_dir / "test.txt" file_in_other.write_text("test") validator = PathValidator([allowed_dir]) with pytest.raises(ValidationError, match="not within allowed roots"): validator.validate(file_in_other) def test_validate_multiple_roots(self, tmp_path): """Test validating files in multiple allowed roots.""" dir1 = tmp_path / "dir1" dir2 = tmp_path / "dir2" dir1.mkdir() dir2.mkdir() file1 = dir1 / "file1.txt" file2 = dir2 / "file2.txt" file1.write_text("content1") file2.write_text("content2") validator = PathValidator([dir1, dir2]) assert validator.validate(file1) is True assert validator.validate(file2) is True def test_validate_with_relative_path(self, tmp_path): """Test validating with relative path (gets resolved).""" import os file_path = tmp_path / "test.txt" file_path.write_text("test") validator = PathValidator([tmp_path]) # Change to tmp_path directory original_cwd = os.getcwd() try: os.chdir(tmp_path) # Use relative path assert validator.validate(Path("test.txt")) is True finally: os.chdir(original_cwd) def test_validate_with_path_traversal(self, tmp_path): """Test that path traversal attacks are blocked.""" allowed_dir = tmp_path / "allowed" other_dir = tmp_path / "other" allowed_dir.mkdir() other_dir.mkdir() file_in_other = other_dir / "secret.txt" file_in_other.write_text("secret") validator = PathValidator([allowed_dir]) # Try to access file outside root using .. traversal_path = allowed_dir / ".." / "other" / "secret.txt" with pytest.raises(ValidationError, match="not within allowed roots"): validator.validate(traversal_path) def test_is_valid_doesnt_raise(self, tmp_path): """Test that is_valid() never raises exceptions.""" validator = PathValidator([tmp_path]) # These should all return False, not raise assert validator.is_valid(Path("/nonexistent")) is False assert validator.is_valid(tmp_path) is False # Directory, not file # Valid file should return True file_path = tmp_path / "test.txt" file_path.write_text("test") assert validator.is_valid(file_path) is True class TestPathValidatorEdgeCases: """Test edge cases in path validation.""" def test_symlink_to_valid_file(self, tmp_path): """Test validating a symlink to a valid file.""" # Skip on Windows if symlink creation fails actual_file = tmp_path / "actual.txt" actual_file.write_text("content") try: symlink = tmp_path / "link.txt" symlink.symlink_to(actual_file) validator = PathValidator([tmp_path]) # Symlinks resolve to their target, should validate assert validator.validate(symlink) is True except (OSError, NotImplementedError): # Skip if symlinks not supported pytest.skip("Symlinks not supported on this platform") def test_nested_files_in_allowed_root(self, tmp_path): """Test validating files in nested subdirectories.""" nested_dir = tmp_path / "a" / "b" / "c" nested_dir.mkdir(parents=True) nested_file = nested_dir / "file.txt" nested_file.write_text("content") validator = PathValidator([tmp_path]) assert validator.validate(nested_file) is True