webdrop-bridge/tests/unit/test_main_window.py
claudi dbf8f2b92f Enhance test coverage and refactor code in various modules
- Achieved 85% overall test coverage with detailed results for individual components.
- Added unit tests for DragInterceptor and MainWindow components.
- Refactored imports and removed unused code in multiple files.
- Updated test configurations and ensured compliance with coverage standards.
2026-01-28 11:51:59 +01:00

435 lines
14 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,
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 TestMainWindowNavigation:
"""Test navigation toolbar and functionality."""
def test_navigation_toolbar_created(self, qtbot, sample_config):
"""Test navigation toolbar is created."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
toolbars = window.findChildren(QToolBar)
assert len(toolbars) > 0
def test_navigation_toolbar_not_movable(self, qtbot, sample_config):
"""Test navigation toolbar is not movable (locked for Kiosk-mode)."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
toolbar = window.findChild(QToolBar)
assert toolbar is not None
assert not toolbar.isMovable()
def test_navigate_home(self, qtbot, sample_config):
"""Test home button navigation."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
with patch.object(window.web_view, "load") as mock_load:
window._navigate_home()
mock_load.assert_called_once()
def test_navigate_home_with_http_url(self, qtbot, tmp_path):
"""Test home navigation with HTTP URL."""
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=[],
webapp_url="http://localhost:8000",
window_width=800,
window_height=600,
enable_logging=False,
)
window = MainWindow(config)
qtbot.addWidget(window)
with patch.object(window.web_view, "load") as mock_load:
window._navigate_home()
# Verify load was called with HTTP URL
call_args = mock_load.call_args
url = call_args[0][0]
assert url.scheme() == "http"
def test_navigate_home_with_file_url(self, qtbot, sample_config):
"""Test home navigation with file:// URL."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
with patch.object(window.web_view, "load") as mock_load:
window._navigate_home()
call_args = mock_load.call_args
url = call_args[0][0]
assert url.scheme() == "file"
class TestMainWindowWebAppLoading:
"""Test web application loading."""
def test_load_local_webapp_file(self, qtbot, sample_config):
"""Test loading local webapp file."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Window should load without errors
assert window.web_view is not None
def test_load_remote_webapp_url(self, qtbot, tmp_path):
"""Test loading remote webapp URL."""
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=["localhost"],
webapp_url="http://localhost:3000",
window_width=800,
window_height=600,
enable_logging=False,
)
window = MainWindow(config)
qtbot.addWidget(window)
assert window.web_view is not None
def test_load_nonexistent_file_shows_error(self, qtbot, tmp_path):
"""Test loading nonexistent file shows error HTML."""
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=[],
webapp_url="/nonexistent/file.html",
window_width=800,
window_height=600,
enable_logging=False,
)
with patch.object(config, "webapp_url", "/nonexistent/file.html"):
window = MainWindow(config)
qtbot.addWidget(window)
with patch.object(
window.web_view, "setHtml"
) as mock_set_html:
window._load_webapp()
mock_set_html.assert_called_once()
# Verify error message
call_args = mock_set_html.call_args[0][0]
assert "Error" in call_args
assert "not found" in call_args
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_initiate_drag_delegates_to_interceptor(
self, qtbot, sample_config, tmp_path
):
"""Test initiate_drag method delegates to interceptor."""
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, "initiate_drag"
) as mock_drag:
mock_drag.return_value = True
result = window.initiate_drag([str(test_file)])
mock_drag.assert_called_once_with([str(test_file)])
assert result is True
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"])
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")
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
def test_empty_allowed_urls_list(self, qtbot, tmp_path):
"""Test with empty allowed URLs (no restriction)."""
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=[], # Empty = no restriction
webapp_url="http://localhost",
window_width=800,
window_height=600,
enable_logging=False,
)
window = MainWindow(config)
qtbot.addWidget(window)
assert window.web_view.allowed_urls == []
class TestMainWindowSignals:
"""Test signal connections."""
def test_drag_started_signal_connection(self, qtbot, sample_config):
"""Test drag_started signal is connected to handler."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
with patch.object(window, "_on_drag_started") as mock_handler:
window.drag_interceptor.drag_started.emit(["/path/to/file"])
mock_handler.assert_called_once()
def test_drag_failed_signal_connection(self, qtbot, sample_config):
"""Test drag_failed signal is connected to handler."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
with patch.object(window, "_on_drag_failed") as mock_handler:
window.drag_interceptor.drag_failed.emit("Error message")
mock_handler.assert_called_once()
class TestMainWindowStylesheet:
"""Test stylesheet application."""
def test_stylesheet_loading_gracefully_handles_missing_file(
self, qtbot, sample_config
):
"""Test missing stylesheet doesn't crash application."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Should not raise even if stylesheet missing
window._apply_stylesheet()
def test_stylesheet_loading_with_nonexistent_file(
self, qtbot, sample_config
):
"""Test stylesheet loading with nonexistent file path."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
with patch("builtins.open", side_effect=OSError("File not found")):
# Should handle gracefully
window._apply_stylesheet()
class TestMainWindowCloseEvent:
"""Test window close handling."""
def test_close_event_accepted(self, qtbot, sample_config):
"""Test close event is accepted."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
from PySide6.QtGui import QCloseEvent
event = QCloseEvent()
window.closeEvent(event)
assert event.isAccepted()
class TestMainWindowIntegration:
"""Integration tests for MainWindow with all components."""
def test_full_initialization_flow(self, qtbot, sample_config):
"""Test complete initialization flow."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Verify all components initialized
assert window.web_view is not None
assert window.drag_interceptor is not None
assert window.config == sample_config
# Verify toolbar exists
toolbars = window.findChildren(QToolBar)
assert len(toolbars) > 0
def test_window_with_multiple_allowed_roots(self, qtbot, tmp_path):
"""Test MainWindow with multiple allowed root directories."""
root1 = tmp_path / "root1"
root2 = tmp_path / "root2"
root1.mkdir()
root2.mkdir()
webapp_file = tmp_path / "index.html"
webapp_file.write_text("<html></html>")
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[root1, root2],
allowed_urls=[],
webapp_url=str(webapp_file),
window_width=800,
window_height=600,
enable_logging=False,
)
window = MainWindow(config)
qtbot.addWidget(window)
# Verify validator has both roots
assert window.drag_interceptor._validator is not None
assert len(
window.drag_interceptor._validator.allowed_roots
) == 2
def test_window_with_url_whitelist(self, qtbot, tmp_path):
"""Test MainWindow respects URL whitelist."""
config = Config(
app_name="Test",
app_version="1.0.0",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=["*.example.com", "localhost"],
webapp_url="http://localhost",
window_width=800,
window_height=600,
enable_logging=False,
)
window = MainWindow(config)
qtbot.addWidget(window)
# Verify whitelist is set
assert window.web_view.allowed_urls == ["*.example.com", "localhost"]