"""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("Test") 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