webdrop-bridge/tests/unit/test_drag_interceptor.py

471 lines
16 KiB
Python

"""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]