mirror of https://github.com/caronc/apprise
356 lines
11 KiB
Python
356 lines
11 KiB
Python
# BSD 2-Clause License
|
|
#
|
|
# Apprise - Push Notification Library.
|
|
# Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
#
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from importlib import reload
|
|
|
|
# Disable logging for a cleaner testing output
|
|
import logging
|
|
import sys
|
|
import types
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
import apprise
|
|
|
|
logging.disable(logging.CRITICAL)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
(
|
|
"win32api" in sys.modules
|
|
or "win32con" in sys.modules
|
|
or "win32gui" in sys.modules
|
|
),
|
|
reason="Requires non-windows platform",
|
|
)
|
|
def test_plugin_windows_mocked():
|
|
"""NotifyWindows() General Checks (via non-Windows platform)"""
|
|
|
|
# We need to fake our windows environment for testing purposes
|
|
win32api_name = "win32api"
|
|
win32api = types.ModuleType(win32api_name)
|
|
sys.modules[win32api_name] = win32api
|
|
win32api.GetModuleHandle = mock.Mock(
|
|
name=win32api_name + ".GetModuleHandle"
|
|
)
|
|
win32api.PostQuitMessage = mock.Mock(
|
|
name=win32api_name + ".PostQuitMessage"
|
|
)
|
|
|
|
win32con_name = "win32con"
|
|
win32con = types.ModuleType(win32con_name)
|
|
sys.modules[win32con_name] = win32con
|
|
win32con.CW_USEDEFAULT = mock.Mock(name=win32con_name + ".CW_USEDEFAULT")
|
|
win32con.IDI_APPLICATION = mock.Mock(
|
|
name=win32con_name + ".IDI_APPLICATION"
|
|
)
|
|
win32con.IMAGE_ICON = mock.Mock(name=win32con_name + ".IMAGE_ICON")
|
|
win32con.LR_DEFAULTSIZE = 1
|
|
win32con.LR_LOADFROMFILE = 2
|
|
win32con.WM_DESTROY = mock.Mock(name=win32con_name + ".WM_DESTROY")
|
|
win32con.WM_USER = 0
|
|
win32con.WS_OVERLAPPED = 1
|
|
win32con.WS_SYSMENU = 2
|
|
|
|
win32gui_name = "win32gui"
|
|
win32gui = types.ModuleType(win32gui_name)
|
|
sys.modules[win32gui_name] = win32gui
|
|
win32gui.CreateWindow = mock.Mock(name=win32gui_name + ".CreateWindow")
|
|
win32gui.DestroyWindow = mock.Mock(name=win32gui_name + ".DestroyWindow")
|
|
win32gui.LoadIcon = mock.Mock(name=win32gui_name + ".LoadIcon")
|
|
win32gui.LoadImage = mock.Mock(name=win32gui_name + ".LoadImage")
|
|
win32gui.NIF_ICON = 1
|
|
win32gui.NIF_INFO = mock.Mock(name=win32gui_name + ".NIF_INFO")
|
|
win32gui.NIF_MESSAGE = 2
|
|
win32gui.NIF_TIP = 4
|
|
win32gui.NIM_ADD = mock.Mock(name=win32gui_name + ".NIM_ADD")
|
|
win32gui.NIM_DELETE = mock.Mock(name=win32gui_name + ".NIM_DELETE")
|
|
win32gui.NIM_MODIFY = mock.Mock(name=win32gui_name + ".NIM_MODIFY")
|
|
win32gui.RegisterClass = mock.Mock(name=win32gui_name + ".RegisterClass")
|
|
win32gui.UnregisterClass = mock.Mock(
|
|
name=win32gui_name + ".UnregisterClass"
|
|
)
|
|
win32gui.Shell_NotifyIcon = mock.Mock(
|
|
name=win32gui_name + ".Shell_NotifyIcon"
|
|
)
|
|
win32gui.UpdateWindow = mock.Mock(name=win32gui_name + ".UpdateWindow")
|
|
win32gui.WNDCLASS = mock.Mock(name=win32gui_name + ".WNDCLASS")
|
|
|
|
# The following allows our mocked content to kick in. In python 3.x keys()
|
|
# returns an iterator, therefore we need to convert the keys() back into
|
|
# a list object to prevent from getting the error:
|
|
# "RuntimeError: dictionary changed size during iteration"
|
|
#
|
|
for mod in list(sys.modules.keys()):
|
|
if mod.startswith("apprise."):
|
|
del sys.modules[mod]
|
|
reload(apprise)
|
|
|
|
# Create our instance
|
|
obj = apprise.Apprise.instantiate("windows://", suppress_exceptions=False)
|
|
obj.duration = 0
|
|
|
|
# Test URL functionality
|
|
assert isinstance(obj.url(), str)
|
|
|
|
# Verify that a URL ID can not be generated
|
|
assert obj.url_id() is None
|
|
|
|
# Check that it found our mocked environments
|
|
assert obj.enabled is True
|
|
|
|
# _on_destroy check
|
|
obj._on_destroy(0, "", 0, 0)
|
|
|
|
# test notifications
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?image=True", suppress_exceptions=False
|
|
)
|
|
obj.duration = 0
|
|
assert isinstance(obj.url(), str)
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?image=False", suppress_exceptions=False
|
|
)
|
|
obj.duration = 0
|
|
assert isinstance(obj.url(), str)
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=1", suppress_exceptions=False
|
|
)
|
|
assert isinstance(obj.url(), str)
|
|
# loads okay
|
|
assert obj.duration == 1
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=invalid", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=-1", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=0", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
# To avoid slowdowns (for testing), turn it to zero for now
|
|
obj.duration = 0
|
|
|
|
# Test our loading of our icon exception; it will still allow the
|
|
# notification to be sent
|
|
win32gui.LoadImage.side_effect = AttributeError
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
# Undo our change
|
|
win32gui.LoadImage.side_effect = None
|
|
|
|
# Test our global exception handling
|
|
win32gui.UpdateWindow.side_effect = AttributeError
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is False
|
|
)
|
|
# Undo our change
|
|
win32gui.UpdateWindow.side_effect = None
|
|
|
|
# Toggle our testing for when we can't send notifications because the
|
|
# package has been made unavailable to us
|
|
obj.enabled = False
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is False
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
"win32api" not in sys.modules
|
|
and "win32con" not in sys.modules
|
|
and "win32gui" not in sys.modules,
|
|
reason="Requires win32api, win32con, and win32gui",
|
|
)
|
|
@mock.patch("win32gui.UpdateWindow")
|
|
@mock.patch("win32gui.Shell_NotifyIcon")
|
|
@mock.patch("win32gui.LoadImage")
|
|
def test_plugin_windows_native(
|
|
mock_loadimage, mock_notify, mock_update_window
|
|
):
|
|
"""NotifyWindows() General Checks (via Windows platform)"""
|
|
|
|
# Create our instance
|
|
obj = apprise.Apprise.instantiate("windows://", suppress_exceptions=False)
|
|
obj.duration = 0
|
|
|
|
# Test URL functionality
|
|
assert isinstance(obj.url(), str)
|
|
|
|
# Check that it found our mocked environments
|
|
assert obj.enabled is True
|
|
|
|
# _on_destroy check
|
|
obj._on_destroy(0, "", 0, 0)
|
|
|
|
# test notifications
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?image=True", suppress_exceptions=False
|
|
)
|
|
obj.duration = 0
|
|
assert isinstance(obj.url(), str)
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?image=False", suppress_exceptions=False
|
|
)
|
|
obj.duration = 0
|
|
assert isinstance(obj.url(), str)
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=1", suppress_exceptions=False
|
|
)
|
|
assert isinstance(obj.url(), str)
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
# loads okay
|
|
assert obj.duration == 1
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=invalid", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=-1", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
obj = apprise.Apprise.instantiate(
|
|
"windows://_/?duration=0", suppress_exceptions=False
|
|
)
|
|
# Falls back to default
|
|
assert obj.duration == obj.default_popup_duration_sec
|
|
|
|
# To avoid slowdowns (for testing), turn it to zero for now
|
|
obj = apprise.Apprise.instantiate("windows://", suppress_exceptions=False)
|
|
obj.duration = 0
|
|
|
|
# Test our loading of our icon exception; it will still allow the
|
|
# notification to be sent
|
|
mock_loadimage.side_effect = AttributeError
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is True
|
|
)
|
|
# Undo our change
|
|
mock_loadimage.side_effect = None
|
|
|
|
# Test our global exception handling
|
|
mock_update_window.side_effect = AttributeError
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is False
|
|
)
|
|
# Undo our change
|
|
mock_update_window.side_effect = None
|
|
|
|
# Toggle our testing for when we can't send notifications because the
|
|
# package has been made unavailable to us
|
|
obj.enabled = False
|
|
assert (
|
|
obj.notify(
|
|
title="title", body="body", notify_type=apprise.NotifyType.INFO
|
|
)
|
|
is False
|
|
)
|