"""Unit tests for DragInterceptor component.""" from pathlib import Path from unittest.mock import MagicMock, patch import pytest from webdrop_bridge.config import Config from webdrop_bridge.core.drag_interceptor import DragInterceptor @pytest.fixture def test_config(tmp_path): """Create test configuration.""" return Config( app_name="Test App", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://dev.agravity.io/", url_mappings=[], check_file_exists=True, ) class TestDragInterceptorInitialization: """Test DragInterceptor initialization and setup.""" def test_drag_interceptor_creation(self, qtbot, test_config): """Test DragInterceptor can be instantiated.""" interceptor = DragInterceptor(test_config) assert interceptor is not None assert interceptor._validator is not None assert interceptor._url_converter is not None def test_drag_interceptor_has_signals(self, qtbot, test_config): """Test DragInterceptor has required signals.""" interceptor = DragInterceptor(test_config) assert hasattr(interceptor, "drag_started") assert hasattr(interceptor, "drag_failed") def test_set_validator(self, qtbot, test_config): """Test validator is set during construction.""" interceptor = DragInterceptor(test_config) assert interceptor._validator is not None class TestDragInterceptorValidation: """Test path validation in drag operations.""" def test_handle_drag_empty_text(self, qtbot, test_config): """Test handling drag with empty text fails.""" interceptor = DragInterceptor(test_config) with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag("") assert result is False def test_handle_drag_valid_file_path(self, qtbot, tmp_path): """Test handling drag with valid file path.""" # Create a test file test_file = tmp_path / "test.txt" test_file.write_text("test content") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) # 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.handle_drag(str(test_file)) # Should return True on successful drag assert result is True def test_handle_drag_invalid_path(self, qtbot, test_config): """Test drag with invalid path fails.""" interceptor = DragInterceptor(test_config) # Path outside allowed roots invalid_path = "/etc/passwd" with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag(invalid_path) assert result is False def test_handle_drag_nonexistent_file(self, qtbot, test_config, tmp_path): """Test drag with nonexistent file fails.""" interceptor = DragInterceptor(test_config) nonexistent = tmp_path / "nonexistent.txt" with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag(str(nonexistent)) assert result is False class TestDragInterceptorAzureURL: """Test Azure URL to local path conversion in drag operations.""" def test_handle_drag_azure_url(self, qtbot, tmp_path): """Test handling drag with Azure Blob Storage URL.""" from webdrop_bridge.config import URLMapping # Create test file that would be the result test_file = tmp_path / "test.png" test_file.write_text("image data") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[ URLMapping( url_prefix="https://devagravitystg.file.core.windows.net/devagravitysync/", local_path=str(tmp_path), ) ], check_file_exists=True, ) interceptor = DragInterceptor(config) # Azure URL azure_url = "https://devagravitystg.file.core.windows.net/devagravitysync/test.png" # Mock the drag operation with patch("webdrop_bridge.core.drag_interceptor.QDrag") as mock_drag: mock_drag_instance = MagicMock() from PySide6.QtCore import Qt mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction mock_drag.return_value = mock_drag_instance result = interceptor.handle_drag(azure_url) assert result is True def test_handle_drag_unmapped_url(self, qtbot, test_config): """Test handling drag with unmapped URL fails.""" interceptor = DragInterceptor(test_config) # URL with no mapping unmapped_url = "https://unknown.blob.core.windows.net/container/file.png" with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag(unmapped_url) assert result is False 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") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) # Connect to signal manually signal_spy = [] interceptor.drag_started.connect(lambda src, path: signal_spy.append((src, path))) 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.handle_drag(str(test_file)) # Verify result and signal emission assert result is True assert len(signal_spy) == 1 def test_drag_failed_signal_on_empty_text(self, qtbot, test_config): """Test drag_failed signal on empty text.""" interceptor = DragInterceptor(test_config) # Connect to signal manually signal_spy = [] interceptor.drag_failed.connect(lambda src, msg: signal_spy.append((src, msg))) result = interceptor.handle_drag("") # Verify result and signal emission assert result is False assert len(signal_spy) == 1 assert "Empty" in signal_spy[0][1] def test_drag_failed_signal_on_validation_error(self, qtbot, test_config): """Test drag_failed signal on validation failure.""" interceptor = DragInterceptor(test_config) # Connect to signal manually signal_spy = [] interceptor.drag_failed.connect(lambda src, msg: signal_spy.append((src, msg))) result = interceptor.handle_drag("/invalid/path/file.txt") # Verify result and signal emission assert result is False assert len(signal_spy) == 1 class TestDragInterceptorMultipleDrags: """Test multiple file drag support.""" def test_handle_drag_with_list_single_item(self, qtbot, tmp_path): """Test handle_drag with list containing single file path.""" test_file = tmp_path / "test.txt" test_file.write_text("content") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) 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.handle_drag([str(test_file)]) assert result is True def test_handle_drag_with_multiple_files(self, qtbot, tmp_path): """Test handle_drag with list of multiple file paths.""" # Create multiple test files test_file1 = tmp_path / "test1.txt" test_file1.write_text("content1") test_file2 = tmp_path / "test2.txt" test_file2.write_text("content2") test_file3 = tmp_path / "test3.txt" test_file3.write_text("content3") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) 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.handle_drag( [ str(test_file1), str(test_file2), str(test_file3), ] ) assert result is True def test_handle_drag_with_multiple_azure_urls(self, qtbot, tmp_path): """Test handle_drag with list of multiple Azure URLs.""" from webdrop_bridge.config import URLMapping config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[ URLMapping( url_prefix="https://produktagravitystg.file.core.windows.net/produktagravitysync/", local_path=str(tmp_path), ) ], check_file_exists=False, # Don't check file existence for this test ) interceptor = DragInterceptor(config) # Multiple Azure URLs (as would be in a multi-drag) azure_urls = [ "https://produktagravitystg.file.core.windows.net/produktagravitysync/axtZdPVjs5iUaKU2muKMFN1WZ/igkjieyjcko.jpg", "https://produktagravitystg.file.core.windows.net/produktagravitysync/aWd7mDjnsm2w0PHU9AryQBYz2/457101023fd46d673e2ce6642f78fb0d62736f0f06c7.jpg", ] 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.handle_drag(azure_urls) assert result is True # Verify QDrag.exec was called (meaning drag was set up correctly) mock_drag_instance.exec.assert_called_once() def test_handle_drag_mixed_urls_and_paths(self, qtbot, tmp_path): """Test handle_drag with mixed Azure URLs and local paths.""" from webdrop_bridge.config import URLMapping # Create test file test_file = tmp_path / "local_file.txt" test_file.write_text("local content") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[ URLMapping( url_prefix="https://devagravitystg.file.core.windows.net/devagravitysync/", local_path=str(tmp_path), ) ], check_file_exists=False, # Don't check existence for remote files ) interceptor = DragInterceptor(config) mixed_items = [ str(test_file), # local path "https://devagravitystg.file.core.windows.net/devagravitysync/remote.jpg", # Azure URL ] 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.handle_drag(mixed_items) assert result is True def test_handle_drag_multiple_empty_list(self, qtbot, test_config): """Test handle_drag with empty list fails.""" interceptor = DragInterceptor(test_config) with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag([]) assert result is False def test_handle_drag_multiple_one_invalid_fails(self, qtbot, tmp_path): """Test handle_drag with multiple files fails if one is invalid.""" test_file1 = tmp_path / "test1.txt" test_file1.write_text("content1") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) # One valid, one invalid files = [ str(test_file1), "/etc/passwd", # Invalid - outside allowed roots ] with qtbot.waitSignal(interceptor.drag_failed): result = interceptor.handle_drag(files) assert result is False def test_handle_drag_multiple_signal_with_pipes(self, qtbot, tmp_path): """Test drag_started signal contains pipe-separated paths for multiple files.""" test_file1 = tmp_path / "test1.txt" test_file1.write_text("content1") test_file2 = tmp_path / "test2.txt" test_file2.write_text("content2") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="https://test.com/", url_mappings=[], check_file_exists=True, ) interceptor = DragInterceptor(config) signal_spy = [] interceptor.drag_started.connect(lambda src, path: signal_spy.append((src, path))) 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.handle_drag([str(test_file1), str(test_file2)]) assert result is True assert len(signal_spy) == 1 # Multiple paths should be separated by " | " assert " | " in signal_spy[0][1]