mirror of
https://github.com/caronc/apprise.git
synced 2024-11-26 01:53:10 +01:00
Improve testing of NotifyDBus
, NotifyGnome
, and NotifyMacOSX
(#689)
This commit is contained in:
parent
c81d2465e4
commit
f1836cff84
@ -1,8 +1,8 @@
|
|||||||
# Base
|
# Base
|
||||||
FROM python:3.10-buster
|
FROM python:3.10-buster
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y libdbus-1-dev build-essential musl-dev bash
|
apt-get install -y libdbus-1-dev libgirepository1.0-dev build-essential musl-dev bash
|
||||||
RUN pip install dbus-python
|
RUN pip install dbus-python PyGObject
|
||||||
|
|
||||||
# Apprise Setup
|
# Apprise Setup
|
||||||
VOLUME ["/apprise"]
|
VOLUME ["/apprise"]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Base
|
# Base
|
||||||
FROM python:3.6-buster
|
FROM python:3.6-buster
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y libdbus-1-dev build-essential musl-dev bash
|
apt-get install -y libdbus-1-dev libgirepository1.0-dev build-essential musl-dev bash
|
||||||
RUN pip install dbus-python
|
RUN pip install dbus-python PyGObject
|
||||||
|
|
||||||
# Apprise Setup
|
# Apprise Setup
|
||||||
VOLUME ["/apprise"]
|
VOLUME ["/apprise"]
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
@ -77,6 +78,13 @@ try:
|
|||||||
NOTIFY_DBUS_SUPPORT_ENABLED = (
|
NOTIFY_DBUS_SUPPORT_ENABLED = (
|
||||||
LOOP_GLIB is not None or LOOP_QT is not None)
|
LOOP_GLIB is not None or LOOP_QT is not None)
|
||||||
|
|
||||||
|
# ImportError: When using gi.repository you must not import static modules
|
||||||
|
# like "gobject". Please change all occurrences of "import gobject" to
|
||||||
|
# "from gi.repository import GObject".
|
||||||
|
# See: https://bugzilla.gnome.org/show_bug.cgi?id=709183
|
||||||
|
if "gobject" in sys.modules: # pragma: no cover
|
||||||
|
del sys.modules["gobject"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# The following is required for Image/Icon loading only
|
# The following is required for Image/Icon loading only
|
||||||
import gi
|
import gi
|
||||||
@ -272,12 +280,9 @@ class NotifyDBus(NotifyBase):
|
|||||||
self.x_axis = None
|
self.x_axis = None
|
||||||
self.y_axis = None
|
self.y_axis = None
|
||||||
|
|
||||||
# Track whether or not we want to send an image with our notification
|
# Track whether we want to add an image to the notification.
|
||||||
# or not.
|
|
||||||
self.include_image = include_image
|
self.include_image = include_image
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform DBus Notification
|
Perform DBus Notification
|
||||||
@ -286,10 +291,10 @@ class NotifyDBus(NotifyBase):
|
|||||||
try:
|
try:
|
||||||
session = SessionBus(mainloop=MAINLOOP_MAP[self.schema])
|
session = SessionBus(mainloop=MAINLOOP_MAP[self.schema])
|
||||||
|
|
||||||
except DBusException:
|
except DBusException as e:
|
||||||
# Handle exception
|
# Handle exception
|
||||||
self.logger.warning('Failed to send DBus notification.')
|
self.logger.warning('Failed to send DBus notification.')
|
||||||
self.logger.exception('DBus Exception')
|
self.logger.debug(f'DBus Exception: {e}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If there is no title, but there is a body, swap the two to get rid
|
# If there is no title, but there is a body, swap the two to get rid
|
||||||
@ -342,8 +347,8 @@ class NotifyDBus(NotifyBase):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Could not load Gnome notification icon ({}): {}"
|
"Could not load notification icon (%s).", icon_path)
|
||||||
.format(icon_path, e))
|
self.logger.debug(f'DBus Exception: {e}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Always call throttle() before any remote execution is made
|
# Always call throttle() before any remote execution is made
|
||||||
@ -370,9 +375,9 @@ class NotifyDBus(NotifyBase):
|
|||||||
|
|
||||||
self.logger.info('Sent DBus notification.')
|
self.logger.info('Sent DBus notification.')
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self.logger.warning('Failed to send DBus notification.')
|
self.logger.warning('Failed to send DBus notification.')
|
||||||
self.logger.exception('DBus Exception')
|
self.logger.debug(f'DBus Exception: {e}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -54,8 +54,8 @@ except (ImportError, ValueError, AttributeError):
|
|||||||
# be in microsoft windows, or we just don't have the python-gobject
|
# be in microsoft windows, or we just don't have the python-gobject
|
||||||
# library available to us (or maybe one we don't support)?
|
# library available to us (or maybe one we don't support)?
|
||||||
|
|
||||||
# Alternativey A ValueError will get thrown upon calling
|
# Alternatively, a `ValueError` will get raised upon calling
|
||||||
# gi.require_version() if the requested Notify namespace isn't available
|
# gi.require_version() if the requested Notify namespace isn't available.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -175,12 +175,9 @@ class NotifyGnome(NotifyBase):
|
|||||||
if str(urgency).lower().startswith(k)),
|
if str(urgency).lower().startswith(k)),
|
||||||
NotifyGnome.template_args['urgency']['default']))
|
NotifyGnome.template_args['urgency']['default']))
|
||||||
|
|
||||||
# Track whether or not we want to send an image with our notification
|
# Track whether we want to add an image to the notification.
|
||||||
# or not.
|
|
||||||
self.include_image = include_image
|
self.include_image = include_image
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Gnome Notification
|
Perform Gnome Notification
|
||||||
@ -214,15 +211,15 @@ class NotifyGnome(NotifyBase):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Could not load Gnome notification icon ({}): {}"
|
"Could not load notification icon (%s). ", icon_path)
|
||||||
.format(icon_path, e))
|
self.logger.debug(f'Gnome Exception: {e}')
|
||||||
|
|
||||||
notification.show()
|
notification.show()
|
||||||
self.logger.info('Sent Gnome notification.')
|
self.logger.info('Sent Gnome notification.')
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self.logger.warning('Failed to send Gnome notification.')
|
self.logger.warning('Failed to send Gnome notification.')
|
||||||
self.logger.exception('Gnome Exception')
|
self.logger.debug(f'Gnome Exception: {e}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -39,13 +39,15 @@ from ..AppriseLocale import gettext_lazy as _
|
|||||||
# Default our global support flag
|
# Default our global support flag
|
||||||
NOTIFY_MACOSX_SUPPORT_ENABLED = False
|
NOTIFY_MACOSX_SUPPORT_ENABLED = False
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: The module will be easier to test without module-level code.
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
# Check this is Mac OS X 10.8, or higher
|
# Check this is Mac OS X 10.8, or higher
|
||||||
major, minor = platform.mac_ver()[0].split('.')[:2]
|
major, minor = platform.mac_ver()[0].split('.')[:2]
|
||||||
|
|
||||||
# Toggle our enabled flag if verion is correct and executable
|
# Toggle our enabled flag, if version is correct and executable
|
||||||
# found. This is done in such a way to provide verbosity to the
|
# found. This is done in such a way to provide verbosity to the
|
||||||
# end user so they know why it may or may not work for them.
|
# end user, so they know why it may or may not work for them.
|
||||||
NOTIFY_MACOSX_SUPPORT_ENABLED = \
|
NOTIFY_MACOSX_SUPPORT_ENABLED = \
|
||||||
(int(major) > 10 or (int(major) == 10 and int(minor) >= 8))
|
(int(major) > 10 or (int(major) == 10 and int(minor) >= 8))
|
||||||
|
|
||||||
@ -95,6 +97,8 @@ class NotifyMacOSX(NotifyBase):
|
|||||||
notify_paths = (
|
notify_paths = (
|
||||||
'/opt/homebrew/bin/terminal-notifier',
|
'/opt/homebrew/bin/terminal-notifier',
|
||||||
'/usr/local/bin/terminal-notifier',
|
'/usr/local/bin/terminal-notifier',
|
||||||
|
'/usr/bin/terminal-notifier',
|
||||||
|
'/bin/terminal-notifier',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
@ -126,17 +130,15 @@ class NotifyMacOSX(NotifyBase):
|
|||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Track whether or not we want to send an image with our notification
|
# Track whether we want to add an image to the notification.
|
||||||
# or not.
|
|
||||||
self.include_image = include_image
|
self.include_image = include_image
|
||||||
|
|
||||||
# Acquire the notify path
|
# Acquire the path to the `terminal-notifier` program.
|
||||||
self.notify_path = next( # pragma: no branch
|
self.notify_path = next( # pragma: no branch
|
||||||
(p for p in self.notify_paths if os.access(p, os.X_OK)), None)
|
(p for p in self.notify_paths if os.access(p, os.X_OK)), None)
|
||||||
|
|
||||||
# Set sound object (no q/a for now)
|
# Set sound object (no q/a for now)
|
||||||
self.sound = sound
|
self.sound = sound
|
||||||
return
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
version: "3.3"
|
version: "3.3"
|
||||||
services:
|
services:
|
||||||
test.py35:
|
test.py36:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.py36
|
dockerfile: Dockerfile.py36
|
||||||
|
@ -22,24 +22,18 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
from importlib import import_module, reload
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
|
|
||||||
|
def reload_plugin(name, replace_in=None):
|
||||||
def reload_plugin(name):
|
|
||||||
"""
|
"""
|
||||||
Reload builtin plugin module, e.g. `NotifyGnome`.
|
Reload built-in plugin module, e.g. `NotifyGnome`.
|
||||||
set filename to plugin to be reloaded (for example NotifyGnome.py)
|
|
||||||
|
|
||||||
The following libraries need to be reloaded to prevent
|
Reloading plugin modules is needed when testing module-level code of
|
||||||
TypeError: super(type, obj): obj must be an instance or subtype of type
|
notification plugins.
|
||||||
This is better explained in this StackOverflow post:
|
|
||||||
https://stackoverflow.com/questions/31363311/\
|
|
||||||
any-way-to-manually-fix-operation-of-\
|
|
||||||
super-after-ipython-reload-avoiding-ty
|
|
||||||
|
|
||||||
|
See also https://stackoverflow.com/questions/31363311.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
module_name = f"apprise.plugins.{name}"
|
module_name = f"apprise.plugins.{name}"
|
||||||
@ -53,3 +47,10 @@ def reload_plugin(name):
|
|||||||
reload(sys.modules['apprise.Apprise'])
|
reload(sys.modules['apprise.Apprise'])
|
||||||
reload(sys.modules['apprise.utils'])
|
reload(sys.modules['apprise.utils'])
|
||||||
reload(sys.modules['apprise'])
|
reload(sys.modules['apprise'])
|
||||||
|
|
||||||
|
# Fix reference to new plugin class in given module.
|
||||||
|
# Needed for updating the module-level import reference like
|
||||||
|
# `from apprise.plugins.NotifyMacOSX import NotifyMacOSX`.
|
||||||
|
if replace_in is not None:
|
||||||
|
mod = import_module(module_name)
|
||||||
|
setattr(replace_in, name, getattr(mod, name))
|
||||||
|
@ -22,40 +22,37 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import pytest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from unittest.mock import Mock, call, ANY
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import apprise
|
import apprise
|
||||||
from helpers import reload_plugin
|
from helpers import reload_plugin
|
||||||
from importlib import reload
|
|
||||||
|
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
|
# Skip tests when Python environment does not provide the `dbus` package.
|
||||||
if 'dbus' not in sys.modules:
|
if 'dbus' not in sys.modules:
|
||||||
# Environment doesn't allow for dbus
|
|
||||||
pytest.skip("Skipping dbus-python based tests", allow_module_level=True)
|
pytest.skip("Skipping dbus-python based tests", allow_module_level=True)
|
||||||
|
|
||||||
|
|
||||||
from dbus import DBusException # noqa E402
|
from dbus import DBusException # noqa E402
|
||||||
from apprise.plugins.NotifyDBus import DBusUrgency # noqa E402
|
from apprise.plugins.NotifyDBus import DBusUrgency, NotifyDBus # noqa E402
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('dbus.SessionBus')
|
def setup_glib_environment():
|
||||||
@mock.patch('dbus.Interface')
|
|
||||||
@mock.patch('dbus.ByteArray')
|
|
||||||
@mock.patch('dbus.Byte')
|
|
||||||
@mock.patch('dbus.mainloop')
|
|
||||||
def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|
||||||
mock_interface, mock_sessionbus):
|
|
||||||
"""
|
"""
|
||||||
NotifyDBus() General Tests
|
Setup a heavily mocked Glib environment.
|
||||||
"""
|
"""
|
||||||
|
mock_mainloop = Mock()
|
||||||
|
|
||||||
# Our module base
|
# Our module base
|
||||||
gi_name = 'gi'
|
gi_name = 'gi'
|
||||||
@ -68,15 +65,15 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
# for the purpose of testing and capture the handling of the
|
# for the purpose of testing and capture the handling of the
|
||||||
# library when it is missing
|
# library when it is missing
|
||||||
del sys.modules[gi_name]
|
del sys.modules[gi_name]
|
||||||
reload(sys.modules['apprise.plugins.NotifyDBus'])
|
importlib.reload(sys.modules['apprise.plugins.NotifyDBus'])
|
||||||
|
|
||||||
# We need to fake our dbus environment for testing purposes since
|
# We need to fake our dbus environment for testing purposes since
|
||||||
# the gi library isn't available in Travis CI
|
# the gi library isn't available in Travis CI
|
||||||
gi = types.ModuleType(gi_name)
|
gi = types.ModuleType(gi_name)
|
||||||
gi.repository = types.ModuleType(gi_name + '.repository')
|
gi.repository = types.ModuleType(gi_name + '.repository')
|
||||||
|
|
||||||
mock_pixbuf = mock.Mock()
|
mock_pixbuf = Mock()
|
||||||
mock_image = mock.Mock()
|
mock_image = Mock()
|
||||||
mock_pixbuf.new_from_file.return_value = mock_image
|
mock_pixbuf.new_from_file.return_value = mock_image
|
||||||
|
|
||||||
mock_image.get_width.return_value = 100
|
mock_image.get_width.return_value = 100
|
||||||
@ -92,7 +89,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
gi.repository.GdkPixbuf.Pixbuf = mock_pixbuf
|
gi.repository.GdkPixbuf.Pixbuf = mock_pixbuf
|
||||||
|
|
||||||
# Emulate require_version function:
|
# Emulate require_version function:
|
||||||
gi.require_version = mock.Mock(
|
gi.require_version = Mock(
|
||||||
name=gi_name + '.require_version')
|
name=gi_name + '.require_version')
|
||||||
|
|
||||||
# Force the fake module to exist
|
# Force the fake module to exist
|
||||||
@ -103,18 +100,53 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
mock_mainloop.qt.DBusQtMainLoop.return_value = True
|
mock_mainloop.qt.DBusQtMainLoop.return_value = True
|
||||||
mock_mainloop.qt.DBusQtMainLoop.side_effect = ImportError
|
mock_mainloop.qt.DBusQtMainLoop.side_effect = ImportError
|
||||||
sys.modules['dbus.mainloop.qt'] = mock_mainloop.qt
|
sys.modules['dbus.mainloop.qt'] = mock_mainloop.qt
|
||||||
reload(sys.modules['apprise.plugins.NotifyDBus'])
|
|
||||||
mock_mainloop.qt.DBusQtMainLoop.side_effect = None
|
mock_mainloop.qt.DBusQtMainLoop.side_effect = None
|
||||||
|
|
||||||
mock_mainloop.glib.NativeMainLoop.return_value = True
|
mock_mainloop.glib.NativeMainLoop.return_value = True
|
||||||
mock_mainloop.glib.NativeMainLoop.side_effect = ImportError()
|
mock_mainloop.glib.NativeMainLoop.side_effect = ImportError()
|
||||||
sys.modules['dbus.mainloop.glib'] = mock_mainloop.glib
|
sys.modules['dbus.mainloop.glib'] = mock_mainloop.glib
|
||||||
reload(sys.modules['apprise.plugins.NotifyDBus'])
|
|
||||||
mock_mainloop.glib.DBusGMainLoop.side_effect = None
|
mock_mainloop.glib.DBusGMainLoop.side_effect = None
|
||||||
mock_mainloop.glib.NativeMainLoop.side_effect = None
|
mock_mainloop.glib.NativeMainLoop.side_effect = None
|
||||||
|
|
||||||
reload_plugin('NotifyDBus')
|
# When patching something which has a side effect on the module-level code
|
||||||
from apprise.plugins.NotifyDBus import NotifyDBus
|
# of a plugin, make sure to reload it.
|
||||||
|
current_module = sys.modules[__name__]
|
||||||
|
reload_plugin('NotifyDBus', replace_in=current_module)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dbus_environment(mocker):
|
||||||
|
"""
|
||||||
|
Fixture to provide a mocked Dbus environment to test case functions.
|
||||||
|
"""
|
||||||
|
interface_mock = mocker.patch('dbus.Interface', spec=True,
|
||||||
|
Notify=Mock())
|
||||||
|
mocker.patch('dbus.SessionBus', spec=True,
|
||||||
|
**{"get_object.return_value": interface_mock})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def glib_environment():
|
||||||
|
"""
|
||||||
|
Fixture to provide a mocked Glib environment to test case functions.
|
||||||
|
"""
|
||||||
|
setup_glib_environment()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dbus_glib_environment(dbus_environment, glib_environment):
|
||||||
|
"""
|
||||||
|
Fixture to provide a mocked Glib/DBus environment to test case functions.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_dbus_general_success(mocker, dbus_glib_environment):
|
||||||
|
"""
|
||||||
|
NotifyDBus() general tests
|
||||||
|
|
||||||
|
Test class loading using different arguments, provided via URL.
|
||||||
|
"""
|
||||||
|
|
||||||
# Create our instance (identify all supported types)
|
# Create our instance (identify all supported types)
|
||||||
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||||
@ -135,10 +167,6 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
assert obj.url().startswith('glib://_/')
|
assert obj.url().startswith('glib://_/')
|
||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
|
|
||||||
# Test our class loading using a series of arguments
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
NotifyDBus(**{'schema': 'invalid'})
|
|
||||||
|
|
||||||
# Set our X and Y coordinate and try the notification
|
# Set our X and Y coordinate and try the notification
|
||||||
assert NotifyDBus(
|
assert NotifyDBus(
|
||||||
x_axis=0, y_axis=0, **{'schema': 'dbus'})\
|
x_axis=0, y_axis=0, **{'schema': 'dbus'})\
|
||||||
@ -245,9 +273,21 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
title='title', body='body',
|
title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_dbus_general_failure(dbus_glib_environment):
|
||||||
|
"""
|
||||||
|
Verify a few failure conditions.
|
||||||
|
"""
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
obj = apprise.Apprise.instantiate(
|
NotifyDBus(**{'schema': 'invalid'})
|
||||||
'dbus://_/?x=invalid&y=invalid', suppress_exceptions=False)
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
apprise.Apprise.instantiate('dbus://_/?x=invalid&y=invalid',
|
||||||
|
suppress_exceptions=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_dbus_parse_configuration(dbus_glib_environment):
|
||||||
|
|
||||||
# Test configuration parsing
|
# Test configuration parsing
|
||||||
content = """
|
content = """
|
||||||
@ -318,104 +358,160 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
for s in aobj.find(tag='dbus_invalid'):
|
for s in aobj.find(tag='dbus_invalid'):
|
||||||
assert s.urgency == DBusUrgency.NORMAL
|
assert s.urgency == DBusUrgency.NORMAL
|
||||||
|
|
||||||
# If our underlining object throws for whatever rea on, we will
|
|
||||||
# gracefully fail
|
|
||||||
mock_notify = mock.Mock()
|
|
||||||
mock_interface.return_value = mock_notify
|
|
||||||
mock_notify.Notify.side_effect = AttributeError()
|
|
||||||
assert obj.notify(
|
|
||||||
title='', body='body',
|
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
|
||||||
mock_notify.Notify.side_effect = None
|
|
||||||
|
|
||||||
# Test our loading of our icon exception; it will still allow the
|
def test_plugin_dbus_missing_icon(mocker, dbus_glib_environment):
|
||||||
# notification to be sent
|
"""
|
||||||
mock_pixbuf.new_from_file.side_effect = AttributeError()
|
Test exception when loading icon; the notification will still be sent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inject error when loading icon.
|
||||||
|
gi = importlib.import_module("gi")
|
||||||
|
gi.repository.GdkPixbuf.Pixbuf.new_from_file.side_effect = \
|
||||||
|
AttributeError("Something failed")
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||||
|
logger: Mock = mocker.spy(obj, "logger")
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='title', body='body',
|
title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
# Undo our change
|
assert logger.mock_calls == [
|
||||||
mock_pixbuf.new_from_file.side_effect = None
|
call.warning('Could not load notification icon (%s). '
|
||||||
|
'Reason: Something failed', ANY),
|
||||||
|
call.info('Sent DBus notification.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_dbus_disabled_plugin(dbus_glib_environment):
|
||||||
|
"""
|
||||||
|
Verify notification will not be submitted if plugin is disabled.
|
||||||
|
"""
|
||||||
|
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||||
|
|
||||||
# Test our exception handling during initialization
|
|
||||||
# Toggle our testing for when we can't send notifications because the
|
|
||||||
# package has been made unavailable to us
|
|
||||||
obj.enabled = False
|
obj.enabled = False
|
||||||
|
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='title', body='body',
|
title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Test the setting of a the urgency
|
|
||||||
|
def test_plugin_dbus_set_urgency():
|
||||||
|
"""
|
||||||
|
Test the setting of an urgency.
|
||||||
|
"""
|
||||||
NotifyDBus(urgency=0)
|
NotifyDBus(urgency=0)
|
||||||
|
|
||||||
#
|
|
||||||
# We can still notify if the gi library is the only inaccessible
|
|
||||||
# compontent
|
|
||||||
#
|
|
||||||
|
|
||||||
# Emulate require_version function:
|
def test_plugin_dbus_gi_missing(dbus_glib_environment):
|
||||||
|
"""
|
||||||
|
Verify notification succeeds even if the `gi` package is not available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Make `require_version` function raise an ImportError.
|
||||||
|
gi = importlib.import_module("gi")
|
||||||
gi.require_version.side_effect = ImportError()
|
gi.require_version.side_effect = ImportError()
|
||||||
reload_plugin('NotifyDBus')
|
|
||||||
from apprise.plugins.NotifyDBus import NotifyDBus
|
|
||||||
|
|
||||||
# Create our instance
|
# When patching something which has a side effect on the module-level code
|
||||||
|
# of a plugin, make sure to reload it.
|
||||||
|
current_module = sys.modules[__name__]
|
||||||
|
reload_plugin('NotifyDBus', replace_in=current_module)
|
||||||
|
|
||||||
|
# Create the instance.
|
||||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||||
assert isinstance(obj, NotifyDBus) is True
|
assert isinstance(obj, NotifyDBus) is True
|
||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
|
|
||||||
# Test url() call
|
# Test url() call.
|
||||||
assert isinstance(obj.url(), str) is True
|
assert isinstance(obj.url(), str) is True
|
||||||
|
|
||||||
# Our notification succeeds even though the gi library was not loaded
|
# The notification succeeds even though the gi library was not loaded.
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='title', body='body',
|
title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
# Verify this all works in the event a ValueError is also thronw
|
|
||||||
# out of the call to gi.require_version()
|
|
||||||
|
|
||||||
mock_sessionbus.side_effect = DBusException('test')
|
def test_plugin_dbus_gi_require_version_error(dbus_glib_environment):
|
||||||
# Handle Dbus Session Initialization error
|
"""
|
||||||
assert obj.notify(
|
Verify notification succeeds even if `gi.require_version()` croaks.
|
||||||
title='title', body='body',
|
"""
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
|
||||||
|
|
||||||
# Return side effect to normal
|
# Make `require_version` function raise a ValueError.
|
||||||
mock_sessionbus.side_effect = None
|
gi = importlib.import_module("gi")
|
||||||
|
gi.require_version.side_effect = ValueError("Something failed")
|
||||||
|
|
||||||
# Emulate require_version function:
|
# When patching something which has a side effect on the module-level code
|
||||||
gi.require_version.side_effect = ValueError()
|
# of a plugin, make sure to reload it.
|
||||||
reload_plugin('NotifyDBus')
|
current_module = sys.modules[__name__]
|
||||||
from apprise.plugins.NotifyDBus import NotifyDBus
|
reload_plugin('NotifyDBus', replace_in=current_module)
|
||||||
|
|
||||||
# Create our instance
|
# Create instance.
|
||||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||||
assert isinstance(obj, NotifyDBus) is True
|
assert isinstance(obj, NotifyDBus) is True
|
||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
|
|
||||||
# Test url() call
|
# Test url() call.
|
||||||
assert isinstance(obj.url(), str) is True
|
assert isinstance(obj.url(), str) is True
|
||||||
|
|
||||||
# Our notification succeeds even though the gi library was not loaded
|
# The notification succeeds even though the gi library was not loaded.
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='title', body='body',
|
title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
# Force a global import error
|
|
||||||
_session_bus = sys.modules['dbus']
|
|
||||||
sys.modules['dbus'] = compile('raise ImportError()', 'dbus', 'exec')
|
|
||||||
|
|
||||||
# Reload our modules
|
def test_plugin_dbus_module_croaks(mocker, dbus_glib_environment):
|
||||||
reload_plugin('NotifyDBus')
|
"""
|
||||||
|
Verify plugin is not available when `dbus` module is missing.
|
||||||
|
"""
|
||||||
|
|
||||||
# We can no longer instantiate an instance because dbus has been
|
# Make importing `dbus` raise an ImportError.
|
||||||
# officialy marked unavailable and thus the module is marked
|
mocker.patch.dict(
|
||||||
# as such
|
sys.modules, {'dbus': compile('raise ImportError()', 'dbus', 'exec')})
|
||||||
|
|
||||||
|
# When patching something which has a side effect on the module-level code
|
||||||
|
# of a plugin, make sure to reload it.
|
||||||
|
current_module = sys.modules[__name__]
|
||||||
|
reload_plugin('NotifyDBus', replace_in=current_module)
|
||||||
|
|
||||||
|
# Verify plugin is not available.
|
||||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
|
||||||
# Since playing with the sys.modules is not such a good idea,
|
|
||||||
# let's just put our old configuration back:
|
def test_plugin_dbus_session_croaks(mocker, dbus_glib_environment):
|
||||||
sys.modules['dbus'] = _session_bus
|
"""
|
||||||
# Reload our modules
|
Verify notification fails if DBus croaks.
|
||||||
reload_plugin('NotifyDBus')
|
"""
|
||||||
|
|
||||||
|
mocker.patch('dbus.SessionBus', side_effect=DBusException('test'))
|
||||||
|
setup_glib_environment()
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||||
|
|
||||||
|
# Emulate DBus session initialization error.
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_dbus_interface_notify_croaks(mocker):
|
||||||
|
"""
|
||||||
|
Fail gracefully if underlying object croaks for whatever reason.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inject an error when invoking `dbus.Interface().Notify()`.
|
||||||
|
mocker.patch('dbus.SessionBus', spec=True)
|
||||||
|
mocker.patch('dbus.Interface', spec=True,
|
||||||
|
Notify=Mock(side_effect=AttributeError("Something failed")))
|
||||||
|
setup_glib_environment()
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, NotifyDBus) is True
|
||||||
|
|
||||||
|
logger: Mock = mocker.spy(obj, "logger")
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
assert [
|
||||||
|
call.warning('Failed to send DBus notification. '
|
||||||
|
'Reason: Something failed'),
|
||||||
|
call.exception('DBus Exception')
|
||||||
|
] in logger.mock_calls
|
||||||
|
@ -22,24 +22,26 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from unittest.mock import Mock, call, ANY
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import apprise
|
import apprise
|
||||||
from apprise.plugins.NotifyGnome import GnomeUrgency
|
from apprise.plugins.NotifyGnome import GnomeUrgency, NotifyGnome
|
||||||
from helpers import reload_plugin
|
from helpers import reload_plugin
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_gnome_general():
|
def setup_glib_environment():
|
||||||
"""
|
"""
|
||||||
NotifyGnome() General Checks
|
Setup a heavily mocked Glib environment.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Our module base
|
# Our module base
|
||||||
@ -89,12 +91,30 @@ def test_plugin_gnome_general():
|
|||||||
mock_notify.new.return_value = notify_obj
|
mock_notify.new.return_value = notify_obj
|
||||||
mock_pixbuf.new_from_file.return_value = True
|
mock_pixbuf.new_from_file.return_value = True
|
||||||
|
|
||||||
reload_plugin('NotifyGnome')
|
# When patching something which has a side effect on the module-level code
|
||||||
from apprise.plugins.NotifyGnome import NotifyGnome
|
# of a plugin, make sure to reload it.
|
||||||
|
current_module = sys.modules[__name__]
|
||||||
|
reload_plugin('NotifyGnome', replace_in=current_module)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def glib_environment():
|
||||||
|
"""
|
||||||
|
Fixture to provide a mocked Glib environment to test case functions.
|
||||||
|
"""
|
||||||
|
setup_glib_environment()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def obj(glib_environment):
|
||||||
|
"""
|
||||||
|
Fixture to provide a mocked Apprise instance.
|
||||||
|
"""
|
||||||
|
|
||||||
# Create our instance
|
# Create our instance
|
||||||
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
||||||
assert obj is not None
|
assert obj is not None
|
||||||
|
assert isinstance(obj, NotifyGnome) is True
|
||||||
|
|
||||||
# Set our duration to 0 to speed up timeouts (for testing)
|
# Set our duration to 0 to speed up timeouts (for testing)
|
||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
@ -102,6 +122,14 @@ def test_plugin_gnome_general():
|
|||||||
# Check that it found our mocked environments
|
# Check that it found our mocked environments
|
||||||
assert obj.enabled is True
|
assert obj.enabled is True
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_general_success(obj):
|
||||||
|
"""
|
||||||
|
NotifyGnome() general checks
|
||||||
|
"""
|
||||||
|
|
||||||
# Test url() call
|
# Test url() call
|
||||||
assert isinstance(obj.url(), str) is True
|
assert isinstance(obj.url(), str) is True
|
||||||
|
|
||||||
@ -113,9 +141,14 @@ def test_plugin_gnome_general():
|
|||||||
assert obj.notify(title='', body='body',
|
assert obj.notify(title='', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_image_success(glib_environment):
|
||||||
|
"""
|
||||||
|
Verify using the `image` query argument works as intended.
|
||||||
|
"""
|
||||||
|
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate(
|
||||||
'gnome://_/?image=True', suppress_exceptions=False)
|
'gnome://_/?image=True', suppress_exceptions=False)
|
||||||
print("obj:", obj, type(obj))
|
|
||||||
assert isinstance(obj, NotifyGnome) is True
|
assert isinstance(obj, NotifyGnome) is True
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
@ -126,6 +159,12 @@ def test_plugin_gnome_general():
|
|||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_priority(glib_environment):
|
||||||
|
"""
|
||||||
|
Verify correctness of the `priority` query argument.
|
||||||
|
"""
|
||||||
|
|
||||||
# Test Priority (alias of urgency)
|
# Test Priority (alias of urgency)
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate(
|
||||||
'gnome://_/?priority=invalid', suppress_exceptions=False)
|
'gnome://_/?priority=invalid', suppress_exceptions=False)
|
||||||
@ -148,6 +187,12 @@ def test_plugin_gnome_general():
|
|||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_urgency(glib_environment):
|
||||||
|
"""
|
||||||
|
Verify correctness of the `urgency` query argument.
|
||||||
|
"""
|
||||||
|
|
||||||
# Test Urgeny
|
# Test Urgeny
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate(
|
||||||
'gnome://_/?urgency=invalid', suppress_exceptions=False)
|
'gnome://_/?urgency=invalid', suppress_exceptions=False)
|
||||||
@ -170,6 +215,12 @@ def test_plugin_gnome_general():
|
|||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_parse_configuration(obj):
|
||||||
|
"""
|
||||||
|
Verify configuration parsing works correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
# Test configuration parsing
|
# Test configuration parsing
|
||||||
content = """
|
content = """
|
||||||
urls:
|
urls:
|
||||||
@ -239,43 +290,82 @@ def test_plugin_gnome_general():
|
|||||||
for s in aobj.find(tag='gnome_invalid'):
|
for s in aobj.find(tag='gnome_invalid'):
|
||||||
assert s.urgency == GnomeUrgency.NORMAL
|
assert s.urgency == GnomeUrgency.NORMAL
|
||||||
|
|
||||||
# Test our loading of our icon exception; it will still allow the
|
|
||||||
# notification to be sent
|
def test_plugin_gnome_missing_icon(mocker, obj):
|
||||||
mock_pixbuf.new_from_file.side_effect = AttributeError()
|
"""
|
||||||
|
Verify the notification will be submitted, even if loading the icon fails.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inject error when loading icon.
|
||||||
|
gi = importlib.import_module("gi")
|
||||||
|
gi.repository.GdkPixbuf.Pixbuf.new_from_file.side_effect = \
|
||||||
|
AttributeError("Something failed")
|
||||||
|
|
||||||
|
logger: Mock = mocker.spy(obj, "logger")
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
# Undo our change
|
assert logger.mock_calls == [
|
||||||
mock_pixbuf.new_from_file.side_effect = None
|
call.warning('Could not load notification icon (%s). '
|
||||||
|
'Reason: Something failed', ANY),
|
||||||
|
call.info('Sent Gnome notification.'),
|
||||||
|
]
|
||||||
|
|
||||||
# Test our exception handling during initialization
|
|
||||||
sys.modules['gi.repository.Notify']\
|
|
||||||
.Notification.new.return_value = None
|
|
||||||
sys.modules['gi.repository.Notify']\
|
|
||||||
.Notification.new.side_effect = AttributeError()
|
|
||||||
assert obj.notify(title='title', body='body',
|
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
|
||||||
|
|
||||||
# Undo our change
|
def test_plugin_gnome_disabled_plugin(obj):
|
||||||
sys.modules['gi.repository.Notify']\
|
"""
|
||||||
.Notification.new.side_effect = None
|
Verify notification will not be submitted if plugin is disabled.
|
||||||
|
"""
|
||||||
# Toggle our testing for when we can't send notifications because the
|
|
||||||
# package has been made unavailable to us
|
|
||||||
obj.enabled = False
|
obj.enabled = False
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Test the setting of a the urgency (through priority keyword)
|
|
||||||
|
def test_plugin_gnome_set_urgency():
|
||||||
|
"""
|
||||||
|
Test the setting of an urgency, through `priority` keyword argument.
|
||||||
|
"""
|
||||||
NotifyGnome(priority=0)
|
NotifyGnome(priority=0)
|
||||||
|
|
||||||
# Verify this all works in the event a ValueError is also thronw
|
|
||||||
# out of the call to gi.require_version()
|
|
||||||
|
|
||||||
# Emulate require_version function:
|
def test_plugin_gnome_gi_croaks():
|
||||||
gi.require_version.side_effect = ValueError()
|
"""
|
||||||
reload_plugin('NotifyGnome')
|
Verify notification fails when `gi.require_version()` croaks.
|
||||||
|
"""
|
||||||
|
|
||||||
# We can now no longer load our instance
|
# Make `require_version` function raise an error.
|
||||||
# The object internally is marked disabled
|
try:
|
||||||
|
gi = importlib.import_module("gi")
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
raise pytest.skip("`gi` package not installed")
|
||||||
|
gi.require_version.side_effect = ValueError("Something failed")
|
||||||
|
|
||||||
|
# When patching something which has a side effect on the module-level code
|
||||||
|
# of a plugin, make sure to reload it.
|
||||||
|
current_module = sys.modules[__name__]
|
||||||
|
reload_plugin('NotifyGnome', replace_in=current_module)
|
||||||
|
|
||||||
|
# Create instance.
|
||||||
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
||||||
|
|
||||||
|
# The notifier is marked disabled.
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_gnome_notify_croaks(mocker, obj):
|
||||||
|
"""
|
||||||
|
Fail gracefully if underlying object croaks for whatever reason.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inject an error when invoking `gi.repository.Notify`.
|
||||||
|
mocker.patch('gi.repository.Notify.Notification.new',
|
||||||
|
side_effect=AttributeError("Something failed"))
|
||||||
|
|
||||||
|
logger: Mock = mocker.spy(obj, "logger")
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
assert logger.mock_calls == [
|
||||||
|
call.warning('Failed to send Gnome notification. '
|
||||||
|
'Reason: Something failed'),
|
||||||
|
call.exception('Gnome Exception')
|
||||||
|
]
|
||||||
|
@ -22,49 +22,67 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from unittest import mock
|
import sys
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from helpers import reload_plugin
|
import pytest
|
||||||
|
|
||||||
import apprise
|
import apprise
|
||||||
|
from apprise.plugins.NotifyMacOSX import NotifyMacOSX
|
||||||
|
from helpers import reload_plugin
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
|
||||||
import logging
|
# Disable logging for a cleaner testing output.
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('subprocess.Popen')
|
@pytest.fixture
|
||||||
@mock.patch('platform.system')
|
def pretend_macos(mocker):
|
||||||
@mock.patch('platform.mac_ver')
|
|
||||||
def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir):
|
|
||||||
"""
|
"""
|
||||||
NotifyMacOSX() General Checks
|
Fixture to simulate a macOS environment.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
mocker.patch("platform.system", return_value="Darwin")
|
||||||
|
mocker.patch("platform.mac_ver", return_value=('10.8', ('', '', ''), ''))
|
||||||
|
|
||||||
# Create a temporary binary file we can reference
|
# Reload plugin module, in order to re-run module-level code.
|
||||||
script = tmpdir.join("terminal-notifier")
|
current_module = sys.modules[__name__]
|
||||||
script.write('')
|
reload_plugin("NotifyMacOSX", replace_in=current_module)
|
||||||
# Give execute bit
|
|
||||||
os.chmod(str(script), 0o755)
|
|
||||||
mock_cmd_response = mock.Mock()
|
|
||||||
|
|
||||||
# Set a successful response
|
|
||||||
mock_cmd_response.returncode = 0
|
|
||||||
|
|
||||||
# Simulate a Mac Environment
|
@pytest.fixture
|
||||||
mock_system.return_value = 'Darwin'
|
def terminal_notifier(mocker, tmp_path):
|
||||||
mock_macver.return_value = ('10.8', ('', '', ''), '')
|
"""
|
||||||
mock_popen.return_value = mock_cmd_response
|
Fixture for providing a surrogate for the `terminal-notifier` program.
|
||||||
|
"""
|
||||||
|
notifier_program = tmp_path.joinpath("terminal-notifier")
|
||||||
|
notifier_program.write_text('#!/bin/sh\n\necho hello')
|
||||||
|
|
||||||
# Ensure our environment is loaded with this configuration
|
# Set execute bit.
|
||||||
reload_plugin('NotifyMacOSX')
|
os.chmod(notifier_program, 0o755)
|
||||||
from apprise.plugins.NotifyMacOSX import NotifyMacOSX
|
|
||||||
|
|
||||||
# Point our object to our new temporary existing file
|
# Make the notifier use the temporary file instead of `terminal-notifier`.
|
||||||
NotifyMacOSX.notify_paths = (str(script), )
|
mocker.patch("apprise.plugins.NotifyMacOSX.NotifyMacOSX.notify_paths",
|
||||||
|
(str(notifier_program),))
|
||||||
|
|
||||||
|
yield notifier_program
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def macos_notify_environment(pretend_macos, terminal_notifier):
|
||||||
|
"""
|
||||||
|
Fixture to bundle general test case setup.
|
||||||
|
|
||||||
|
Use this fixture if you don't need access to the individual members.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_macosx_general_success(macos_notify_environment):
|
||||||
|
"""
|
||||||
|
NotifyMacOSX() general checks
|
||||||
|
"""
|
||||||
|
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate(
|
||||||
'macosx://_/?image=True', suppress_exceptions=False)
|
'macosx://_/?image=True', suppress_exceptions=False)
|
||||||
@ -103,68 +121,81 @@ def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir):
|
|||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
# If our binary is inacccessible (or not executable), we can
|
|
||||||
# no longer send our notifications
|
def test_plugin_macosx_terminal_notifier_not_executable(
|
||||||
os.chmod(str(script), 0o644)
|
pretend_macos, terminal_notifier):
|
||||||
|
"""
|
||||||
|
When the `terminal-notifier` program is inaccessible or not executable,
|
||||||
|
we are unable to send notifications.
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('macosx://', suppress_exceptions=False)
|
||||||
|
|
||||||
|
# Unset the executable bit.
|
||||||
|
os.chmod(terminal_notifier, 0o644)
|
||||||
|
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Restore permission
|
|
||||||
os.chmod(str(script), 0o755)
|
|
||||||
|
|
||||||
# But now let's disrupt the path location
|
def test_plugin_macosx_terminal_notifier_invalid(macos_notify_environment):
|
||||||
|
"""
|
||||||
|
When the `terminal-notifier` program is wrongly addressed,
|
||||||
|
notifications should fail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('macosx://', suppress_exceptions=False)
|
||||||
|
|
||||||
|
# Let's disrupt the path location.
|
||||||
obj.notify_path = 'invalid_missing-file'
|
obj.notify_path = 'invalid_missing-file'
|
||||||
assert not os.path.isfile(obj.notify_path)
|
assert not os.path.isfile(obj.notify_path)
|
||||||
|
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Test cases where the script just flat out fails
|
|
||||||
mock_cmd_response.returncode = 1
|
def test_plugin_macosx_terminal_notifier_croaks(
|
||||||
obj = apprise.Apprise.instantiate(
|
mocker, macos_notify_environment):
|
||||||
'macosx://', suppress_exceptions=False)
|
"""
|
||||||
|
When the `terminal-notifier` program croaks on execution,
|
||||||
|
notifications should fail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Emulate a failing program.
|
||||||
|
mocker.patch("subprocess.Popen", return_value=Mock(returncode=1))
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate('macosx://', suppress_exceptions=False)
|
||||||
assert isinstance(obj, NotifyMacOSX) is True
|
assert isinstance(obj, NotifyMacOSX) is True
|
||||||
assert obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Restore script return value
|
|
||||||
mock_cmd_response.returncode = 0
|
|
||||||
|
|
||||||
# Test case where we simply aren't on a mac
|
def test_plugin_macosx_pretend_linux(mocker, pretend_macos):
|
||||||
mock_system.return_value = 'Linux'
|
"""
|
||||||
reload_plugin('NotifyMacOSX')
|
The notification object is disabled when pretending to run on Linux.
|
||||||
|
"""
|
||||||
|
|
||||||
# Point our object to our new temporary existing file
|
# When patching something which has a side effect on the module-level code
|
||||||
NotifyMacOSX.notify_paths = (str(script), )
|
# of a plugin, make sure to reload it.
|
||||||
|
mocker.patch("platform.system", return_value="Linux")
|
||||||
|
reload_plugin("NotifyMacOSX")
|
||||||
|
|
||||||
# Our object is disabled
|
# Our object is disabled.
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate('macosx://', suppress_exceptions=False)
|
||||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
|
||||||
# Restore mac environment
|
|
||||||
mock_system.return_value = 'Darwin'
|
|
||||||
|
|
||||||
# Now we must be Mac OS v10.8 or higher...
|
@pytest.mark.parametrize("macos_version", ["9.12", "10.7"])
|
||||||
mock_macver.return_value = ('10.7', ('', '', ''), '')
|
def test_plugin_macosx_pretend_old_macos(mocker, macos_version):
|
||||||
reload_plugin('NotifyMacOSX')
|
"""
|
||||||
|
The notification object is disabled when pretending to run on older macOS.
|
||||||
|
"""
|
||||||
|
|
||||||
# Point our object to our new temporary existing file
|
# When patching something which has a side effect on the module-level code
|
||||||
NotifyMacOSX.notify_paths = (str(script), )
|
# of a plugin, make sure to reload it.
|
||||||
|
mocker.patch("platform.mac_ver",
|
||||||
|
return_value=(macos_version, ('', '', ''), ''))
|
||||||
|
reload_plugin("NotifyMacOSX")
|
||||||
|
|
||||||
obj = apprise.Apprise.instantiate(
|
obj = apprise.Apprise.instantiate('macosx://', suppress_exceptions=False)
|
||||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
|
||||||
assert obj is None
|
|
||||||
|
|
||||||
# A newer environment to test edge case where this is tested
|
|
||||||
mock_macver.return_value = ('9.12', ('', '', ''), '')
|
|
||||||
reload_plugin('NotifyMacOSX')
|
|
||||||
|
|
||||||
# Point our object to our new temporary existing file
|
|
||||||
NotifyMacOSX.notify_paths = (str(script), )
|
|
||||||
|
|
||||||
# This is just to test that the the minor (in this case .12)
|
|
||||||
# is only weighed with respect to the major number as wel
|
|
||||||
# with respect to the versioning
|
|
||||||
obj = apprise.Apprise.instantiate(
|
|
||||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
Loading…
Reference in New Issue
Block a user