From 07e7998abcfb622e233992349d2fd16820b10a7f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 21 Jun 2022 12:08:05 +0200 Subject: notification: Improve error case tests --- tests/unit/browser/test_notification.py | 155 +++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 23 deletions(-) diff --git a/tests/unit/browser/test_notification.py b/tests/unit/browser/test_notification.py index 54519c248..3bd6ef963 100644 --- a/tests/unit/browser/test_notification.py +++ b/tests/unit/browser/test_notification.py @@ -19,13 +19,19 @@ """Unit tests for notification support.""" -from typing import List, Dict, Any, Optional +import logging +import itertools +import inspect +from typing import List, Dict, Any, Optional, TYPE_CHECKING import pytest -from PyQt5.QtCore import QUrl +from PyQt5.QtCore import pyqtSignal, QUrl, QObject from PyQt5.QtGui import QImage from PyQt5.QtDBus import QDBusMessage, QDBus, QDBusConnection +if TYPE_CHECKING: + from PyQt5.QtWebEngineCore import QWebEngineNotification +from qutebrowser.config import configdata from qutebrowser.misc import objects from qutebrowser.browser.webengine import notification @@ -117,7 +123,9 @@ class FakeDBusInterface: return self.notify_reply -class FakeWebEngineNotification: +class FakeWebEngineNotification(QObject): + + closed = pyqtSignal() def origin(self) -> QUrl: return QUrl("https://example.org") @@ -131,16 +139,11 @@ class FakeWebEngineNotification: def message(self) -> str: return "notification message" + def tag(self) -> None: + return None -@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() + def show(self) -> None: + pass @pytest.fixture @@ -148,27 +151,133 @@ def fake_notification(): return FakeWebEngineNotification() +def _get_notification_adapters(): + return [value for _name, value in inspect.getmembers(notification, lambda obj: ( + inspect.isclass(obj) and + issubclass(obj, notification.AbstractNotificationAdapter) and + obj is not notification.AbstractNotificationAdapter + ))] + + +@pytest.mark.parametrize("klass", _get_notification_adapters()) +def test_name_attribute(klass, configdata_init): + values = configdata.DATA["content.notifications.presenter"].typ.valid_values + assert klass.NAME not in {"auto", "qt"} and klass.NAME in values + + +class FakeNotificationAdapter(notification.AbstractNotificationAdapter): + + NAME = "fake" + + def __init__(self) -> None: + super().__init__() + self.presented = [] + self.id_gen = itertools.count(1) + + def present( + self, + qt_notification: "QWebEngineNotification", *, + replaces_id: Optional[int], + ) -> int: + self.presented.append(qt_notification) + return next(self.id_gen) + + @dbus_test class TestDBus: + NO_REPLY_ERROR = FakeDBusMessage.create_error("org.freedesktop.DBus.Error.NoReply") + FATAL_ERROR = FakeDBusMessage.create_error("test") + + @pytest.fixture + def dbus_adapter_patches(self, monkeypatch, config_stub): + monkeypatch.setattr(objects, "debug_flags", ["test-notification-service"]) + monkeypatch.setattr(notification, "QDBusInterface", FakeDBusInterface) + + @pytest.fixture + def dbus_adapter(self, dbus_adapter_patches): + return notification.DBusNotificationAdapter() + + @pytest.fixture + def dbus_presenter(self, dbus_adapter_patches, monkeypatch): + monkeypatch.setattr( + notification.NotificationBridgePresenter, + "_get_adapter_candidates", + lambda _self, _setting: [ + notification.DBusNotificationAdapter, + FakeNotificationAdapter, + ], + ) + return notification.NotificationBridgePresenter() + def test_notify_fatal_error(self, dbus_adapter, fake_notification): - dbus_adapter.interface.notify_reply = FakeDBusMessage.create_error("test") + dbus_adapter.interface.notify_reply = self.FATAL_ERROR with pytest.raises(notification.DBusError): dbus_adapter.present(fake_notification, replaces_id=None) + def test_notify_fatal_error_presenter(self, dbus_presenter, fake_notification): + dbus_presenter._init_adapter() + dbus_presenter._adapter.interface.notify_reply = self.FATAL_ERROR + with pytest.raises(notification.DBusError): + dbus_presenter.present(fake_notification) + 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) + dbus_adapter.interface.notify_reply = self.NO_REPLY_ERROR with qtbot.wait_signal(dbus_adapter.error) as blocker: dbus_adapter.present(fake_notification, replaces_id=None) - assert blocker.args == [f"error: {error}"] + assert blocker.args == [f"error: {self.NO_REPLY_ERROR.errorName()}"] - 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), + def test_notify_non_fatal_error_presenter( + self, + dbus_presenter, + fake_notification, + caplog, + ): + dbus_presenter._init_adapter() + dbus_presenter._adapter.interface.notify_reply = self.NO_REPLY_ERROR + + with caplog.at_level(logging.ERROR): + dbus_presenter.present(fake_notification) + + message = ( + 'Notification error from libnotify adapter: ' + f'{self.NO_REPLY_ERROR.errorMessage()}' ) - with pytest.raises(notification.DBusError): + assert message in caplog.messages + assert dbus_presenter._adapter is None # adapter dropped + + @pytest.mark.parametrize("error, exctype", [ + (NO_REPLY_ERROR, notification.DBusError), + (FATAL_ERROR, notification.Error), + ]) + def test_capabilities_error( + self, + dbus_adapter_patches, + monkeypatch, + error, + exctype, + ): + monkeypatch.setattr(FakeDBusInterface, "CAPABILITIES_REPLY", error) + with pytest.raises(exctype): notification.DBusNotificationAdapter() + + @pytest.mark.parametrize("error", [NO_REPLY_ERROR, FATAL_ERROR], + ids=lambda e: e.errorName()) + def test_capabilities_error_presenter( + self, + dbus_presenter, + fake_notification, + monkeypatch, + caplog, + error, + ): + monkeypatch.setattr(FakeDBusInterface, "CAPABILITIES_REPLY", error) + dbus_presenter.present(fake_notification) + message = ( + 'Failed to initialize libnotify notification adapter: ' + f'{error.errorName()}: {error.errorMessage()}' + ) + assert message in caplog.messages + + assert isinstance(dbus_presenter._adapter, FakeNotificationAdapter) + assert dbus_presenter._adapter.presented == [fake_notification] -- cgit v1.2.3-54-g00ecf