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
|
|
@ -98,12 +98,13 @@ class TestConfigFromEnv:
|
|||
Config.from_env(str(env_file))
|
||||
|
||||
def test_from_env_invalid_root_path(self, tmp_path):
|
||||
"""Test that non-existent root paths raise ConfigurationError."""
|
||||
"""Test that non-existent root paths are logged as warning but don't raise error."""
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text("ALLOWED_ROOTS=/nonexistent/path/that/does/not/exist\n")
|
||||
|
||||
with pytest.raises(ConfigurationError, match="does not exist"):
|
||||
Config.from_env(str(env_file))
|
||||
# Should not raise - just logs warning and returns empty allowed_roots
|
||||
config = Config.from_env(str(env_file))
|
||||
assert config.allowed_roots == [] # Non-existent roots are skipped
|
||||
|
||||
def test_from_env_empty_webapp_url(self, tmp_path):
|
||||
"""Test that empty webapp URL raises ConfigurationError."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -231,10 +231,12 @@ class TestMainWindowDragIntegration:
|
|||
assert window.drag_interceptor.drag_started is not None
|
||||
assert window.drag_interceptor.drag_failed is not None
|
||||
|
||||
def test_initiate_drag_delegates_to_interceptor(
|
||||
def test_handle_drag_delegates_to_interceptor(
|
||||
self, qtbot, sample_config, tmp_path
|
||||
):
|
||||
"""Test initiate_drag method delegates to interceptor."""
|
||||
"""Test drag handling delegates to interceptor."""
|
||||
from PySide6.QtCore import QCoreApplication
|
||||
|
||||
window = MainWindow(sample_config)
|
||||
qtbot.addWidget(window)
|
||||
|
||||
|
|
@ -243,29 +245,32 @@ class TestMainWindowDragIntegration:
|
|||
test_file.write_text("test")
|
||||
|
||||
with patch.object(
|
||||
window.drag_interceptor, "initiate_drag"
|
||||
window.drag_interceptor, "handle_drag"
|
||||
) as mock_drag:
|
||||
mock_drag.return_value = True
|
||||
result = window.initiate_drag([str(test_file)])
|
||||
# Call through bridge
|
||||
window._drag_bridge.start_file_drag(str(test_file))
|
||||
|
||||
# Process deferred QTimer.singleShot(0, ...) call
|
||||
QCoreApplication.processEvents()
|
||||
|
||||
mock_drag.assert_called_once_with([str(test_file)])
|
||||
assert result is True
|
||||
mock_drag.assert_called_once_with(str(test_file))
|
||||
|
||||
def test_on_drag_started_called(self, qtbot, sample_config):
|
||||
"""Test _on_drag_started handler can be called."""
|
||||
window = MainWindow(sample_config)
|
||||
qtbot.addWidget(window)
|
||||
|
||||
# Should not raise
|
||||
window._on_drag_started(["/some/path"])
|
||||
# Should not raise - new signature has source and local_path
|
||||
window._on_drag_started("https://example.com/file.png", "/local/path/file.png")
|
||||
|
||||
def test_on_drag_failed_called(self, qtbot, sample_config):
|
||||
"""Test _on_drag_failed handler can be called."""
|
||||
window = MainWindow(sample_config)
|
||||
qtbot.addWidget(window)
|
||||
|
||||
# Should not raise
|
||||
window._on_drag_failed("Test error message")
|
||||
# Should not raise - new signature has source and error
|
||||
window._on_drag_failed("https://example.com/file.png", "Test error message")
|
||||
|
||||
|
||||
class TestMainWindowURLWhitelist:
|
||||
|
|
|
|||
144
tests/unit/test_url_converter.py
Normal file
144
tests/unit/test_url_converter.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""Unit tests for URL converter."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from webdrop_bridge.config import Config, URLMapping
|
||||
from webdrop_bridge.core.url_converter import URLConverter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_config():
|
||||
"""Create test configuration with URL mappings."""
|
||||
return Config(
|
||||
app_name="Test App",
|
||||
app_version="1.0.0",
|
||||
log_level="INFO",
|
||||
log_file=None,
|
||||
allowed_roots=[],
|
||||
allowed_urls=[],
|
||||
webapp_url="https://wps.agravity.io/",
|
||||
url_mappings=[
|
||||
URLMapping(
|
||||
url_prefix="https://wpsagravitystg.file.core.windows.net/wpsagravitysync/",
|
||||
local_path="Z:"
|
||||
),
|
||||
URLMapping(
|
||||
url_prefix="https://other.blob.core.windows.net/container/",
|
||||
local_path="Y:\\shared"
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def converter(test_config):
|
||||
"""Create URL converter with test config."""
|
||||
return URLConverter(test_config)
|
||||
|
||||
|
||||
def test_convert_simple_url(converter):
|
||||
"""Test converting a simple Azure URL to local path."""
|
||||
url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/test/file.png"
|
||||
result = converter.convert_url_to_path(url)
|
||||
|
||||
assert result is not None
|
||||
assert str(result).endswith("test\\file.png") # Windows path separator
|
||||
|
||||
|
||||
def test_convert_url_with_special_characters(converter):
|
||||
"""Test URL with special characters (URL encoded)."""
|
||||
url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/folder/file%20with%20spaces.png"
|
||||
result = converter.convert_url_to_path(url)
|
||||
|
||||
assert result is not None
|
||||
assert "file with spaces.png" in str(result)
|
||||
|
||||
|
||||
def test_convert_url_with_subdirectories(converter):
|
||||
"""Test URL with deep directory structure."""
|
||||
url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/aN5PysnXIuRECzcRbvHkjL7g0/subfolder/file.png"
|
||||
result = converter.convert_url_to_path(url)
|
||||
|
||||
assert result is not None
|
||||
assert "aN5PysnXIuRECzcRbvHkjL7g0" in str(result)
|
||||
assert "subfolder" in str(result)
|
||||
|
||||
|
||||
def test_convert_unmapped_url(converter):
|
||||
"""Test URL that doesn't match any mapping."""
|
||||
url = "https://unknown.blob.core.windows.net/container/file.png"
|
||||
result = converter.convert_url_to_path(url)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_convert_empty_url(converter):
|
||||
"""Test empty URL."""
|
||||
result = converter.convert_url_to_path("")
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_convert_none_url(converter):
|
||||
"""Test None URL."""
|
||||
result = converter.convert_url_to_path(None)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_is_azure_url_positive(converter):
|
||||
"""Test recognizing valid Azure URLs."""
|
||||
url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/file.png"
|
||||
assert converter.is_azure_url(url) is True
|
||||
|
||||
|
||||
def test_is_azure_url_negative(converter):
|
||||
"""Test rejecting non-Azure URLs."""
|
||||
assert converter.is_azure_url("https://example.com/file.png") is False
|
||||
assert converter.is_azure_url("Z:\\file.png") is False
|
||||
assert converter.is_azure_url("") is False
|
||||
|
||||
|
||||
def test_multiple_mappings(converter):
|
||||
"""Test that correct mapping is used for URL."""
|
||||
url1 = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/file.png"
|
||||
url2 = "https://other.blob.core.windows.net/container/file.png"
|
||||
|
||||
result1 = converter.convert_url_to_path(url1)
|
||||
result2 = converter.convert_url_to_path(url2)
|
||||
|
||||
assert result1 is not None
|
||||
assert result2 is not None
|
||||
assert str(result1).startswith("Z:")
|
||||
assert str(result2).startswith("Y:")
|
||||
|
||||
|
||||
def test_url_mapping_validation_http():
|
||||
"""Test that URL mapping requires http:// or https://."""
|
||||
with pytest.raises(Exception): # ConfigurationError
|
||||
URLMapping(
|
||||
url_prefix="ftp://server/path/",
|
||||
local_path="Z:"
|
||||
)
|
||||
|
||||
|
||||
def test_url_mapping_adds_trailing_slash():
|
||||
"""Test that URL mapping adds trailing slash if missing."""
|
||||
mapping = URLMapping(
|
||||
url_prefix="https://example.com/path",
|
||||
local_path="Z:"
|
||||
)
|
||||
assert mapping.url_prefix.endswith("/")
|
||||
|
||||
|
||||
def test_convert_url_example_from_docs(converter):
|
||||
"""Test the exact example from documentation."""
|
||||
url = "https://wpsagravitystg.file.core.windows.net/wpsagravitysync/aN5PysnXIuRECzcRbvHkjL7g0/Hintergrund_Agravity.png"
|
||||
result = converter.convert_url_to_path(url)
|
||||
|
||||
assert result is not None
|
||||
# Should be: Z:\aN5PysnXIuRECzcRbvHkjL7g0\Hintergrund_Agravity.png
|
||||
expected_parts = ["Z:", "aN5PysnXIuRECzcRbvHkjL7g0", "Hintergrund_Agravity.png"]
|
||||
result_str = str(result)
|
||||
for part in expected_parts:
|
||||
assert part in result_str
|
||||
|
|
@ -22,11 +22,12 @@ class TestPathValidator:
|
|||
assert len(validator.allowed_roots) == 2
|
||||
|
||||
def test_validator_nonexistent_root(self, tmp_path):
|
||||
"""Test that nonexistent root raises ValidationError."""
|
||||
"""Test that nonexistent root is logged as warning but doesn't raise error."""
|
||||
nonexistent = tmp_path / "nonexistent"
|
||||
|
||||
with pytest.raises(ValidationError, match="does not exist"):
|
||||
PathValidator([nonexistent])
|
||||
# Should not raise - just logs warning and skips the root
|
||||
validator = PathValidator([nonexistent])
|
||||
assert len(validator.allowed_roots) == 0 # Non-existent roots are skipped
|
||||
|
||||
def test_validator_non_directory_root(self, tmp_path):
|
||||
"""Test that non-directory root raises ValidationError."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue