222 lines
8.3 KiB
Python
222 lines
8.3 KiB
Python
"""Unit tests for MainWindow component."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from PySide6.QtWidgets import QToolBar
|
|
|
|
from webdrop_bridge.config import Config
|
|
from webdrop_bridge.ui.main_window import MainWindow
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_config(tmp_path):
|
|
"""Provide a test configuration."""
|
|
# Create required directories
|
|
allowed_root = tmp_path / "allowed"
|
|
allowed_root.mkdir(exist_ok=True)
|
|
|
|
# Create webapp HTML
|
|
webapp_dir = tmp_path / "webapp"
|
|
webapp_dir.mkdir(exist_ok=True)
|
|
webapp_file = webapp_dir / "index.html"
|
|
webapp_file.write_text("<html><body>Test</body></html>")
|
|
|
|
config = Config(
|
|
app_name="Test WebDrop",
|
|
app_version="1.0.0",
|
|
log_level="INFO",
|
|
log_file=None,
|
|
allowed_roots=[allowed_root],
|
|
allowed_urls=["localhost", "127.0.0.1"],
|
|
webapp_url=str(webapp_file),
|
|
window_width=800,
|
|
window_height=600,
|
|
window_title="Test WebDrop v1.0.0",
|
|
enable_logging=False,
|
|
)
|
|
return config
|
|
|
|
|
|
class TestMainWindowInitialization:
|
|
"""Test MainWindow initialization and setup."""
|
|
|
|
def test_main_window_creation(self, qtbot, sample_config):
|
|
"""Test MainWindow can be instantiated."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
assert window is not None
|
|
assert window.config == sample_config
|
|
|
|
def test_main_window_title(self, qtbot, sample_config):
|
|
"""Test window title is set correctly."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
expected_title = f"{sample_config.app_name} v{sample_config.app_version}"
|
|
assert window.windowTitle() == expected_title
|
|
|
|
def test_main_window_geometry(self, qtbot, sample_config):
|
|
"""Test window geometry is set correctly."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
assert window.width() == sample_config.window_width
|
|
assert window.height() == sample_config.window_height
|
|
|
|
def test_main_window_has_web_view(self, qtbot, sample_config):
|
|
"""Test MainWindow has web_view attribute."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
assert hasattr(window, "web_view")
|
|
assert window.web_view is not None
|
|
|
|
def test_main_window_has_drag_interceptor(self, qtbot, sample_config):
|
|
"""Test MainWindow has drag_interceptor attribute."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
assert hasattr(window, "drag_interceptor")
|
|
assert window.drag_interceptor is not None
|
|
|
|
|
|
class TestMainWindowDragIntegration:
|
|
"""Test drag-and-drop integration."""
|
|
|
|
def test_drag_interceptor_validator_set(self, qtbot, sample_config):
|
|
"""Test drag interceptor validator is configured."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
assert window.drag_interceptor._validator is not None
|
|
|
|
def test_drag_interceptor_signals_connected(self, qtbot, sample_config):
|
|
"""Test drag interceptor signals are connected."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
# Signals should be connected
|
|
assert window.drag_interceptor.drag_started is not None
|
|
assert window.drag_interceptor.drag_failed is not None
|
|
|
|
def test_handle_drag_delegates_to_interceptor(self, qtbot, sample_config, tmp_path):
|
|
"""Test drag handling delegates to interceptor."""
|
|
from PySide6.QtCore import QCoreApplication
|
|
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
# Create test file
|
|
test_file = sample_config.allowed_roots[0] / "test.txt"
|
|
test_file.write_text("test")
|
|
|
|
with patch.object(window.drag_interceptor, "handle_drag") as mock_drag:
|
|
mock_drag.return_value = True
|
|
# 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))
|
|
|
|
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 - 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 - new signature has source and error
|
|
window._on_drag_failed("https://example.com/file.png", "Test error message")
|
|
|
|
|
|
class TestMainWindowURLWhitelist:
|
|
"""Test URL whitelisting integration."""
|
|
|
|
def test_restricted_web_view_receives_allowed_urls(self, qtbot, sample_config):
|
|
"""Test RestrictedWebEngineView receives allowed URLs from config."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
# web_view should have allowed_urls configured
|
|
assert window.web_view.allowed_urls == sample_config.allowed_urls
|
|
|
|
|
|
class TestMainWindowOpenWith:
|
|
"""Test Open With chooser behavior."""
|
|
|
|
def test_open_with_app_chooser_windows(self, qtbot, sample_config):
|
|
"""Windows should use ShellExecuteW with the openas verb."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
test_file = sample_config.allowed_roots[0] / "open_with_test.txt"
|
|
test_file.write_text("test")
|
|
|
|
with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
|
|
with patch("ctypes.windll.shell32.ShellExecuteW", return_value=33) as mock_shell:
|
|
assert window._open_with_app_chooser(str(test_file)) is True
|
|
mock_shell.assert_called_once_with(
|
|
None,
|
|
"openas",
|
|
str(test_file),
|
|
None,
|
|
None,
|
|
1,
|
|
)
|
|
|
|
def test_open_with_app_chooser_windows_shellexecute_failure(self, qtbot, sample_config):
|
|
"""Windows should fall back to OpenAs_RunDLL when ShellExecuteW fails."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
test_file = sample_config.allowed_roots[0] / "open_with_fallback.txt"
|
|
test_file.write_text("test")
|
|
|
|
with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
|
|
with patch("ctypes.windll.shell32.ShellExecuteW", return_value=31):
|
|
with patch("webdrop_bridge.ui.main_window.subprocess.Popen") as mock_popen:
|
|
assert window._open_with_app_chooser(str(test_file)) is True
|
|
mock_popen.assert_called_once_with(
|
|
["rundll32.exe", "shell32.dll,OpenAs_RunDLL", str(test_file)]
|
|
)
|
|
|
|
def test_open_with_app_chooser_missing_file(self, qtbot, sample_config):
|
|
"""Missing files should fail before platform-specific invocation."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
|
|
assert window._open_with_app_chooser("C:/tmp/does_not_exist.txt") is False
|
|
|
|
def test_open_with_app_chooser_macos_success(self, qtbot, sample_config):
|
|
"""macOS should return True when osascript exits successfully."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
test_file = sample_config.allowed_roots[0] / "open_with_macos.txt"
|
|
test_file.write_text("test")
|
|
|
|
class _Result:
|
|
returncode = 0
|
|
|
|
with patch("webdrop_bridge.ui.main_window.sys.platform", "darwin"):
|
|
with patch("webdrop_bridge.ui.main_window.subprocess.run", return_value=_Result()):
|
|
assert window._open_with_app_chooser(str(test_file)) is True
|
|
|
|
def test_open_with_app_chooser_unsupported_platform(self, qtbot, sample_config):
|
|
"""Unsupported platforms should return False."""
|
|
window = MainWindow(sample_config)
|
|
qtbot.addWidget(window)
|
|
|
|
with patch("webdrop_bridge.ui.main_window.sys.platform", "linux"):
|
|
assert window._open_with_app_chooser("/tmp/test.txt") is False
|