"""Unit tests for DragInterceptor component.""" from pathlib import Path from unittest.mock import MagicMock, patch from webdrop_bridge.core.drag_interceptor import DragInterceptor from webdrop_bridge.core.validator import PathValidator class TestDragInterceptorInitialization: """Test DragInterceptor initialization and setup.""" def test_drag_interceptor_creation(self, qtbot): """Test DragInterceptor can be instantiated.""" interceptor = DragInterceptor() assert interceptor is not None assert interceptor._validator is None def test_drag_interceptor_has_signals(self, qtbot): """Test DragInterceptor has required signals.""" interceptor = DragInterceptor() assert hasattr(interceptor, "drag_started") assert hasattr(interceptor, "drag_failed") def test_set_validator(self, qtbot, tmp_path): """Test setting validator on drag interceptor.""" interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) assert interceptor._validator is validator class TestDragInterceptorValidation: """Test path validation in drag operations.""" def test_initiate_drag_no_files(self, qtbot): """Test initiating drag with no files fails.""" interceptor = DragInterceptor() with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.initiate_drag([]) assert result is False def test_initiate_drag_no_validator(self, qtbot): """Test initiating drag without validator fails.""" interceptor = DragInterceptor() with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.initiate_drag(["file.txt"]) assert result is False def test_initiate_drag_single_valid_file(self, qtbot, tmp_path): """Test initiating drag with single valid file.""" # Create a test file test_file = tmp_path / "test.txt" test_file.write_text("test content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) # Mock the drag operation to simulate success with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() # Simulate successful copy action from PySide6.QtCore import Qt mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance result = interceptor.initiate_drag([str(test_file)]) # Should return True on successful drag assert result is True def test_initiate_drag_invalid_path(self, qtbot, tmp_path): """Test drag with invalid path fails.""" interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) # Path outside allowed roots invalid_path = Path("/etc/passwd") with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.initiate_drag([str(invalid_path)]) assert result is False def test_initiate_drag_nonexistent_file(self, qtbot, tmp_path): """Test drag with nonexistent file fails.""" interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) nonexistent = tmp_path / "nonexistent.txt" with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.initiate_drag([str(nonexistent)]) assert result is False class TestDragInterceptorMultipleFiles: """Test drag operations with multiple files.""" def test_initiate_drag_multiple_files(self, qtbot, tmp_path): """Test drag with multiple valid files.""" # Create test files file1 = tmp_path / "file1.txt" file2 = tmp_path / "file2.txt" file1.write_text("content 1") file2.write_text("content 2") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) from PySide6.QtCore import Qt with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance result = interceptor.initiate_drag([str(file1), str(file2)]) assert result is True def test_initiate_drag_mixed_valid_invalid(self, qtbot, tmp_path): """Test drag with mix of valid and invalid paths fails.""" test_file = tmp_path / "valid.txt" test_file.write_text("content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) # Mix of valid and invalid paths with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.initiate_drag( [str(test_file), "/etc/passwd"] ) assert result is False class TestDragInterceptorMimeData: """Test MIME data creation and file URL formatting.""" def test_mime_data_creation(self, qtbot, tmp_path): """Test MIME data is created with proper file URLs.""" test_file = tmp_path / "test.txt" test_file.write_text("test") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) from PySide6.QtCore import Qt with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance interceptor.initiate_drag([str(test_file)]) # Check MIME data was set correctly call_args = mock_drag_instance.setMimeData.call_args mime_data = call_args[0][0] # Verify URLs were set urls = mime_data.urls() assert len(urls) == 1 # Check that the URL contains file:// scheme (can be string repr or QUrl) url_str = str(urls[0]).lower() assert "file://" in url_str class TestDragInterceptorSignals: """Test signal emission on drag operations.""" def test_drag_started_signal_emitted(self, qtbot, tmp_path): """Test drag_started signal is emitted on success.""" test_file = tmp_path / "test.txt" test_file.write_text("content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) from PySide6.QtCore import Qt # Connect to signal manually signal_spy = [] interceptor.drag_started.connect(lambda paths: signal_spy.append(paths)) with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance result = interceptor.initiate_drag([str(test_file)]) # Verify result and signal emission assert result is True assert len(signal_spy) == 1 def test_drag_failed_signal_on_no_files(self, qtbot): """Test drag_failed signal on empty file list.""" interceptor = DragInterceptor() # Connect to signal manually signal_spy = [] interceptor.drag_failed.connect(lambda msg: signal_spy.append(msg)) result = interceptor.initiate_drag([]) # Verify result and signal emission assert result is False assert len(signal_spy) == 1 assert "No files" in signal_spy[0] def test_drag_failed_signal_on_validation_error(self, qtbot, tmp_path): """Test drag_failed signal on validation failure.""" interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) # Connect to signal manually signal_spy = [] interceptor.drag_failed.connect(lambda msg: signal_spy.append(msg)) result = interceptor.initiate_drag(["/invalid/path/file.txt"]) # Verify result and signal emission assert result is False assert len(signal_spy) == 1 class TestDragInterceptorDragExecution: """Test drag operation execution and result handling.""" def test_drag_cancelled_returns_false(self, qtbot, tmp_path): """Test drag cancellation returns False.""" test_file = tmp_path / "test.txt" test_file.write_text("content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = 0 # Cancelled/failed mock_drag.return_value = mock_drag_instance # Connect to signal manually signal_spy = [] interceptor.drag_failed.connect(lambda msg: signal_spy.append(msg)) result = interceptor.initiate_drag([str(test_file)]) assert result is False assert len(signal_spy) == 1 def test_pixmap_created_from_widget(self, qtbot, tmp_path): """Test pixmap is created from widget grab.""" test_file = tmp_path / "test.txt" test_file.write_text("content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) from PySide6.QtCore import Qt with patch.object(interceptor, "grab") as mock_grab: mock_pixmap = MagicMock() mock_grab.return_value.scaled.return_value = mock_pixmap with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance interceptor.initiate_drag([str(test_file)]) # Verify grab was called and pixmap was set mock_grab.assert_called_once() mock_drag_instance.setPixmap.assert_called_once_with(mock_pixmap) class TestDragInterceptorIntegration: """Integration tests with PathValidator.""" def test_drag_with_nested_file(self, qtbot, tmp_path): """Test drag with file in nested directory.""" nested_dir = tmp_path / "nested" / "dir" nested_dir.mkdir(parents=True) test_file = nested_dir / "file.txt" test_file.write_text("nested content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) from PySide6.QtCore import Qt with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance result = interceptor.initiate_drag([str(test_file)]) assert result is True def test_drag_with_relative_path(self, qtbot, tmp_path): """Test drag with relative path resolution.""" test_file = tmp_path / "relative.txt" test_file.write_text("content") interceptor = DragInterceptor() validator = PathValidator([tmp_path]) interceptor.set_validator(validator) # This would work if run from the directory, but we'll just verify # the interceptor handles Path objects correctly from PySide6.QtCore import Qt with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance # Direct absolute path for reliable test result = interceptor.initiate_drag([str(test_file)]) assert result is True