"""Unit tests for RestrictedWebEngineView URL filtering.""" from unittest.mock import MagicMock, patch from PySide6.QtCore import QUrl from PySide6.QtWebEngineCore import QWebEngineNavigationRequest from webdrop_bridge.ui.restricted_web_view import RestrictedWebEngineView class TestRestrictedWebEngineView: """Test URL whitelist enforcement.""" def test_no_restrictions_empty_list(self, qtbot): """Test that empty allowed_urls means no restrictions.""" view = RestrictedWebEngineView([]) # Mock navigation request request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://example.com/page") # Should not reject any URL view._on_navigation_requested(request) request.reject.assert_not_called() def test_no_restrictions_none(self, qtbot): """Test that None allowed_urls means no restrictions.""" view = RestrictedWebEngineView(None) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://blocked.com/page") view._on_navigation_requested(request) request.reject.assert_not_called() def test_exact_domain_match(self, qtbot): """Test exact domain matching.""" view = RestrictedWebEngineView(["example.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://example.com/page") view._on_navigation_requested(request) request.reject.assert_not_called() def test_exact_domain_mismatch(self, qtbot): """Test that mismatched domains are rejected.""" view = RestrictedWebEngineView(["example.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://other.com/page") with patch("webdrop_bridge.ui.restricted_web_view.QDesktopServices"): view._on_navigation_requested(request) request.reject.assert_called_once() def test_wildcard_pattern_match(self, qtbot): """Test wildcard pattern matching.""" view = RestrictedWebEngineView(["*.example.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://sub.example.com/page") view._on_navigation_requested(request) request.reject.assert_not_called() def test_wildcard_pattern_mismatch(self, qtbot): """Test that non-matching wildcard patterns are rejected.""" view = RestrictedWebEngineView(["*.example.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://example.org/page") with patch("webdrop_bridge.ui.restricted_web_view.QDesktopServices"): view._on_navigation_requested(request) request.reject.assert_called_once() def test_localhost_allowed(self, qtbot): """Test that localhost is allowed.""" view = RestrictedWebEngineView(["localhost"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("http://localhost:8000/page") view._on_navigation_requested(request) request.reject.assert_not_called() def test_file_url_always_allowed(self, qtbot): """Test that file:// URLs are always allowed.""" view = RestrictedWebEngineView(["example.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("file:///var/www/index.html") view._on_navigation_requested(request) request.reject.assert_not_called() def test_multiple_allowed_urls(self, qtbot): """Test multiple allowed URLs.""" view = RestrictedWebEngineView(["example.com", "test.org"]) # First allowed URL request1 = MagicMock(spec=QWebEngineNavigationRequest) request1.url = QUrl("https://example.com/page") view._on_navigation_requested(request1) request1.reject.assert_not_called() # Second allowed URL request2 = MagicMock(spec=QWebEngineNavigationRequest) request2.url = QUrl("https://test.org/page") view._on_navigation_requested(request2) request2.reject.assert_not_called() # Non-allowed URL request3 = MagicMock(spec=QWebEngineNavigationRequest) request3.url = QUrl("https://blocked.com/page") with patch("webdrop_bridge.ui.restricted_web_view.QDesktopServices"): view._on_navigation_requested(request3) request3.reject.assert_called_once() def test_rejected_url_opens_system_browser(self, qtbot): """Test that rejected URLs open in system browser.""" view = RestrictedWebEngineView(["allowed.com"]) request = MagicMock(spec=QWebEngineNavigationRequest) request.url = QUrl("https://blocked.com/page") with patch( "webdrop_bridge.ui.restricted_web_view.QDesktopServices.openUrl" ) as mock_open: view._on_navigation_requested(request) request.reject.assert_called_once() mock_open.assert_called_once_with(request.url) class TestURLAllowedLogic: """Test _is_url_allowed method directly.""" def test_is_url_allowed_empty_list(self, qtbot): """Test that empty whitelist allows all URLs.""" view = RestrictedWebEngineView([]) assert view._is_url_allowed(QUrl("https://anything.com")) is True def test_is_url_allowed_file_scheme(self, qtbot): """Test that file:// URLs are always allowed.""" view = RestrictedWebEngineView(["example.com"]) assert view._is_url_allowed(QUrl("file:///app/index.html")) is True def test_is_url_allowed_exact_match(self, qtbot): """Test exact domain match.""" view = RestrictedWebEngineView(["example.com"]) assert view._is_url_allowed(QUrl("https://example.com/page")) is True assert view._is_url_allowed(QUrl("https://other.com/page")) is False def test_is_url_allowed_with_port(self, qtbot): """Test domain matching with port number.""" view = RestrictedWebEngineView(["localhost"]) assert view._is_url_allowed(QUrl("http://localhost:8000/page")) is True def test_is_url_allowed_wildcard(self, qtbot): """Test wildcard pattern matching.""" view = RestrictedWebEngineView(["*.example.com", "localhost"]) # Wildcard *.example.com will match sub.example.com assert view._is_url_allowed(QUrl("https://sub.example.com/page")) is True # *.example.com will also match example.com (fnmatch behavior) assert view._is_url_allowed(QUrl("https://example.com/page")) is True # But not other domains assert view._is_url_allowed(QUrl("https://other.org/page")) is False # localhost should work assert view._is_url_allowed(QUrl("http://localhost:3000")) is True