From dffb9f77beacfae1170a68ce4451449b5416c862 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 20 Jun 2022 21:02:26 +0200 Subject: notification: Add unit tests for error cases Those require a lot of patching (and other efforts), but it does not seem possible to write this kind of thing as an integration test: https://www.riverbankcomputing.com/pipermail/pyqt/2022-June/044733.html https://www.riverbankcomputing.com/pipermail/pyqt/2022-June/044734.html --- tests/unit/browser/test_notification.py | 174 ++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 tests/unit/browser/test_notification.py diff --git a/tests/unit/browser/test_notification.py b/tests/unit/browser/test_notification.py new file mode 100644 index 000000000..54519c248 --- /dev/null +++ b/tests/unit/browser/test_notification.py @@ -0,0 +1,174 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2022 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Unit tests for notification support.""" + +from typing import List, Dict, Any, Optional + +import pytest +from PyQt5.QtCore import QUrl +from PyQt5.QtGui import QImage +from PyQt5.QtDBus import QDBusMessage, QDBus, QDBusConnection + +from qutebrowser.misc import objects +from qutebrowser.browser.webengine import notification + + +pytestmark = [pytest.mark.qtwebengine_notifications] +dbus_test = pytest.mark.linux + + +class FakeDBusMessage: + + def __init__( + self, + signature: str, + *arguments: Any, + typ: QDBusMessage.MessageType = QDBusMessage.MessageType.ReplyMessage, + error_name: Optional[str] = None, + ) -> None: + self._signature = signature + self._arguments = arguments + self._type = typ + self._error_name = error_name + + def arguments(self) -> List[Any]: + return self._arguments + + def signature(self) -> str: + return self._signature + + def type(self) -> QDBusMessage.MessageType: + return self._type + + def errorName(self) -> str: + assert self._error_name is not None + return self._error_name + + def errorMessage(self): + assert self._error_name is not None + return f"error: {self._error_name}" + + @classmethod + def create_error(cls, error_name: str) -> "FakeDBusMessage": + return cls( + "s", + f"error argument: {error_name}", + typ=QDBusMessage.MessageType.ErrorMessage, + error_name=error_name, + ) + + +class FakeDBusInterface: + + CAPABILITIES_REPLY = FakeDBusMessage("as", ["actions"]) + + def __init__( + self, + service: str, + path: str, + interface: str, + bus: QDBusConnection, + ) -> None: + assert service.startswith(notification.DBusNotificationAdapter.TEST_SERVICE) + assert path == notification.DBusNotificationAdapter.PATH + assert interface == notification.DBusNotificationAdapter.INTERFACE + + self.notify_reply = None + + def isValid(self) -> bool: + return True + + def call(self, mode: QDBus.CallMode, method: str, *args: Any) -> FakeDBusMessage: + meth = getattr(self, f"_call_{method}") + return meth(*args) + + def _call_GetCapabilities(self) -> FakeDBusMessage: + return self.CAPABILITIES_REPLY + + def _call_Notify( + self, + appname: str, + replaces_id: int, + icon: str, + title: str, + body: str, + actions: List[str], + hints: Dict[str, Any], + timeout: int, + ) -> FakeDBusMessage: + assert self.notify_reply is not None + return self.notify_reply + + +class FakeWebEngineNotification: + + def origin(self) -> QUrl: + return QUrl("https://example.org") + + def icon(self) -> QImage: + return QImage() + + def title(self) -> str: + return "notification title" + + def message(self) -> str: + return "notification message" + + +@pytest.fixture +def dbus_adapter_patches(monkeypatch, config_stub): + monkeypatch.setattr(objects, "debug_flags", ["test-notification-service"]) + monkeypatch.setattr(notification, "QDBusInterface", FakeDBusInterface) + + +@pytest.fixture +def dbus_adapter(dbus_adapter_patches): + return notification.DBusNotificationAdapter() + + +@pytest.fixture +def fake_notification(): + return FakeWebEngineNotification() + + +@dbus_test +class TestDBus: + + def test_notify_fatal_error(self, dbus_adapter, fake_notification): + dbus_adapter.interface.notify_reply = FakeDBusMessage.create_error("test") + with pytest.raises(notification.DBusError): + dbus_adapter.present(fake_notification, replaces_id=None) + + def test_notify_non_fatal_error(self, qtbot, dbus_adapter, fake_notification): + error = "org.freedesktop.DBus.Error.NoReply" + dbus_adapter.interface.notify_reply = FakeDBusMessage.create_error(error) + with qtbot.wait_signal(dbus_adapter.error) as blocker: + dbus_adapter.present(fake_notification, replaces_id=None) + assert blocker.args == [f"error: {error}"] + + def test_capabilities_error(self, qtbot, dbus_adapter_patches, monkeypatch): + error = "org.freedesktop.DBus.Error.NoReply" + monkeypatch.setattr( + FakeDBusInterface, + "CAPABILITIES_REPLY", + FakeDBusMessage.create_error(error), + ) + with pytest.raises(notification.DBusError): + notification.DBusNotificationAdapter() -- cgit v1.2.3-54-g00ecf