summaryrefslogtreecommitdiff
path: root/tests/unit/browser/test_notification.py
blob: 54519c2484dddb798a7a734957979e44e77e0fbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:

# Copyright 2022 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <https://www.gnu.org/licenses/>.

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