Refactor drag handling and update tests
- Renamed `initiate_drag` to `handle_drag` in MainWindow and updated related tests. - Improved drag handling logic to utilize a bridge for starting file drags. - Updated `_on_drag_started` and `_on_drag_failed` methods to match new signatures. - Modified test cases to reflect changes in drag handling and assertions. Enhance path validation and logging - Updated `PathValidator` to log warnings for nonexistent roots instead of raising errors. - Adjusted tests to verify the new behavior of skipping nonexistent roots. Update web application UI and functionality - Changed displayed text for drag items to reflect local paths and Azure Blob Storage URLs. - Added debug logging for drag operations in the web application. - Improved instructions for testing drag and drop functionality. Add configuration documentation and example files - Created `CONFIG_README.md` to provide detailed configuration instructions for WebDrop Bridge. - Added `config.example.json` and `config_test.json` for reference and testing purposes. Implement URL conversion logic - Introduced `URLConverter` class to handle conversion of Azure Blob Storage URLs to local paths. - Added unit tests for URL conversion to ensure correct functionality. Develop download interceptor script - Created `download_interceptor.js` to intercept download-related actions in the web application. - Implemented logging for fetch calls, XMLHttpRequests, and Blob URL creations. Add download test page and related tests - Created `test_download.html` for testing various download scenarios. - Implemented `test_download.py` to verify download path resolution and file construction. - Added `test_url_mappings.py` to ensure URL mappings are loaded correctly. Add unit tests for URL converter - Created `test_url_converter.py` to validate URL conversion logic and mapping behavior.
This commit is contained in:
parent
c9704efc8d
commit
88dc358894
21 changed files with 1870 additions and 432 deletions
|
|
@ -3,63 +3,79 @@
|
|||
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
|
||||
from webdrop_bridge.core.validator import PathValidator
|
||||
|
||||
|
||||
@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://wps.agravity.io/",
|
||||
url_mappings=[],
|
||||
check_file_exists=True,
|
||||
)
|
||||
|
||||
|
||||
class TestDragInterceptorInitialization:
|
||||
"""Test DragInterceptor initialization and setup."""
|
||||
|
||||
def test_drag_interceptor_creation(self, qtbot):
|
||||
def test_drag_interceptor_creation(self, qtbot, test_config):
|
||||
"""Test DragInterceptor can be instantiated."""
|
||||
interceptor = DragInterceptor()
|
||||
interceptor = DragInterceptor(test_config)
|
||||
assert interceptor is not None
|
||||
assert interceptor._validator is None
|
||||
assert interceptor._validator is not None
|
||||
assert interceptor._url_converter is not None
|
||||
|
||||
def test_drag_interceptor_has_signals(self, qtbot):
|
||||
def test_drag_interceptor_has_signals(self, qtbot, test_config):
|
||||
"""Test DragInterceptor has required signals."""
|
||||
interceptor = DragInterceptor()
|
||||
interceptor = DragInterceptor(test_config)
|
||||
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
|
||||
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_initiate_drag_no_files(self, qtbot):
|
||||
"""Test initiating drag with no files fails."""
|
||||
interceptor = DragInterceptor()
|
||||
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.initiate_drag([])
|
||||
result = interceptor.handle_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."""
|
||||
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")
|
||||
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
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:
|
||||
|
|
@ -69,114 +85,91 @@ class TestDragInterceptorValidation:
|
|||
mock_drag_instance.exec.return_value = Qt.DropAction.CopyAction
|
||||
mock_drag.return_value = mock_drag_instance
|
||||
|
||||
result = interceptor.initiate_drag([str(test_file)])
|
||||
result = interceptor.handle_drag(str(test_file))
|
||||
|
||||
# Should return True on successful drag
|
||||
assert result is True
|
||||
|
||||
def test_initiate_drag_invalid_path(self, qtbot, tmp_path):
|
||||
def test_handle_drag_invalid_path(self, qtbot, test_config):
|
||||
"""Test drag with invalid path fails."""
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
interceptor = DragInterceptor(test_config)
|
||||
|
||||
# Path outside allowed roots
|
||||
invalid_path = Path("/etc/passwd")
|
||||
invalid_path = "/etc/passwd"
|
||||
|
||||
with qtbot.waitSignal(interceptor.drag_failed):
|
||||
result = interceptor.initiate_drag([str(invalid_path)])
|
||||
result = interceptor.handle_drag(invalid_path)
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_initiate_drag_nonexistent_file(self, qtbot, tmp_path):
|
||||
def test_handle_drag_nonexistent_file(self, qtbot, test_config, tmp_path):
|
||||
"""Test drag with nonexistent file fails."""
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
interceptor = DragInterceptor(test_config)
|
||||
|
||||
nonexistent = tmp_path / "nonexistent.txt"
|
||||
|
||||
with qtbot.waitSignal(interceptor.drag_failed):
|
||||
result = interceptor.initiate_drag([str(nonexistent)])
|
||||
result = interceptor.handle_drag(str(nonexistent))
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
class TestDragInterceptorMultipleFiles:
|
||||
"""Test drag operations with multiple files."""
|
||||
class TestDragInterceptorAzureURL:
|
||||
"""Test Azure URL to local path conversion in drag operations."""
|
||||
|
||||
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")
|
||||
def test_handle_drag_azure_url(self, qtbot, tmp_path):
|
||||
"""Test handling drag with Azure Blob Storage URL."""
|
||||
from webdrop_bridge.config import URLMapping
|
||||
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
# Create test file that would be the result
|
||||
test_file = tmp_path / "test.png"
|
||||
test_file.write_text("image data")
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
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://wpsagravitystg.file.core.windows.net/wpsagravitysync/",
|
||||
local_path=str(tmp_path)
|
||||
)
|
||||
],
|
||||
check_file_exists=True,
|
||||
)
|
||||
interceptor = DragInterceptor(config)
|
||||
|
||||
# Azure URL
|
||||
azure_url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/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.initiate_drag([str(file1), str(file2)])
|
||||
result = interceptor.handle_drag(azure_url)
|
||||
|
||||
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")
|
||||
def test_handle_drag_unmapped_url(self, qtbot, test_config):
|
||||
"""Test handling drag with unmapped URL fails."""
|
||||
interceptor = DragInterceptor(test_config)
|
||||
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
# URL with no mapping
|
||||
unmapped_url = "https://unknown.blob.core.windows.net/container/file.png"
|
||||
|
||||
# Mix of valid and invalid paths
|
||||
with qtbot.waitSignal(interceptor.drag_failed):
|
||||
result = interceptor.initiate_drag(
|
||||
[str(test_file), "/etc/passwd"]
|
||||
)
|
||||
result = interceptor.handle_drag(unmapped_url)
|
||||
|
||||
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."""
|
||||
|
||||
|
|
@ -185,153 +178,60 @@ class TestDragInterceptorSignals:
|
|||
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
|
||||
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 paths: signal_spy.append(paths))
|
||||
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.initiate_drag([str(test_file)])
|
||||
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_no_files(self, qtbot):
|
||||
"""Test drag_failed signal on empty file list."""
|
||||
interceptor = DragInterceptor()
|
||||
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 msg: signal_spy.append(msg))
|
||||
interceptor.drag_failed.connect(lambda src, msg: signal_spy.append((src, msg)))
|
||||
|
||||
result = interceptor.initiate_drag([])
|
||||
result = interceptor.handle_drag("")
|
||||
|
||||
# Verify result and signal emission
|
||||
assert result is False
|
||||
assert len(signal_spy) == 1
|
||||
assert "No files" in signal_spy[0]
|
||||
assert "Empty" in signal_spy[0][1]
|
||||
|
||||
def test_drag_failed_signal_on_validation_error(self, qtbot, tmp_path):
|
||||
def test_drag_failed_signal_on_validation_error(self, qtbot, test_config):
|
||||
"""Test drag_failed signal on validation failure."""
|
||||
interceptor = DragInterceptor()
|
||||
validator = PathValidator([tmp_path])
|
||||
interceptor.set_validator(validator)
|
||||
interceptor = DragInterceptor(test_config)
|
||||
|
||||
# Connect to signal manually
|
||||
signal_spy = []
|
||||
interceptor.drag_failed.connect(lambda msg: signal_spy.append(msg))
|
||||
interceptor.drag_failed.connect(lambda src, msg: signal_spy.append((src, msg)))
|
||||
|
||||
result = interceptor.initiate_drag(["/invalid/path/file.txt"])
|
||||
result = interceptor.handle_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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue