mirror of
https://github.com/caronc/apprise.git
synced 2024-11-25 01:24:03 +01:00
Refactored Unit Testing and Dependencies (#483)
This commit is contained in:
parent
1dfde961e8
commit
fe83c62669
@ -1,5 +1,4 @@
|
||||
[run]
|
||||
omit=*/gntp/*
|
||||
disable_warnings = no-data-collected
|
||||
branch = True
|
||||
source =
|
||||
@ -15,4 +14,4 @@ source =
|
||||
show_missing = True
|
||||
skip_covered = True
|
||||
skip_empty = True
|
||||
fail_under = 99.0
|
||||
fail_under = 95.0
|
||||
|
13
.travis.yml
13
.travis.yml
@ -11,8 +11,6 @@ matrix:
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36
|
||||
- python: "3.7"
|
||||
@ -23,10 +21,15 @@ matrix:
|
||||
env: TOXENV=py39
|
||||
- python: "3.9-dev"
|
||||
env: TOXENV=py39-dev
|
||||
# PyPy Environments
|
||||
- python: "pypy2.7-6.0"
|
||||
env: TOXENV=pypy
|
||||
- python: "pypy3.5-7.0"
|
||||
env: TOXENV=pypy3
|
||||
# An extra environment where additional packages are not installed
|
||||
- python: "3.9"
|
||||
env:
|
||||
- TOXENV=bare
|
||||
|
||||
install:
|
||||
- pip install babel
|
||||
@ -36,8 +39,10 @@ install:
|
||||
- pip install codecov
|
||||
- pip install -r dev-requirements.txt
|
||||
- pip install -r requirements.txt
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install dbus-python; fi
|
||||
|
||||
# bare installs do not include extra package dependencies
|
||||
- if [[ $TOXENV != 'bare' ]]; then pip install -r all-plugin-requirements.txt; fi
|
||||
# pypy and bare installs do not include dbus-python
|
||||
- if [[ $TOXENV != 'bare' ]] && [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install dbus-python; fi
|
||||
|
||||
# run tests
|
||||
script:
|
||||
|
15
all-plugin-requirements.txt
Normal file
15
all-plugin-requirements.txt
Normal file
@ -0,0 +1,15 @@
|
||||
#
|
||||
# Plugin Dependencies
|
||||
#
|
||||
|
||||
# Provides fcm:// and spush://
|
||||
cryptography
|
||||
|
||||
# Provides xmpp:// support
|
||||
slixmpp; python_version >= '3.7'
|
||||
|
||||
# Provides growl:// support
|
||||
gntp
|
||||
|
||||
# Provides mqtt:// support
|
||||
paho-mqtt
|
@ -164,6 +164,16 @@ class Apprise(object):
|
||||
type(url))
|
||||
return None
|
||||
|
||||
if not plugins.SCHEMA_MAP[results['schema']].enabled:
|
||||
#
|
||||
# First Plugin Enable Check (Pre Initialization)
|
||||
#
|
||||
|
||||
# Plugin has been disabled at a global level
|
||||
logger.error(
|
||||
'%s:// is disabled on this system.', results['schema'])
|
||||
return None
|
||||
|
||||
# Build a list of tags to associate with the newly added notifications
|
||||
results['tag'] = set(parse_list(tag))
|
||||
|
||||
@ -199,6 +209,24 @@ class Apprise(object):
|
||||
# URL information but don't wrap it in a try catch
|
||||
plugin = plugins.SCHEMA_MAP[results['schema']](**results)
|
||||
|
||||
if not plugin.enabled:
|
||||
#
|
||||
# Second Plugin Enable Check (Post Initialization)
|
||||
#
|
||||
|
||||
# Service/Plugin is disabled (on a more local level). This is a
|
||||
# case where the plugin was initially enabled but then after the
|
||||
# __init__() was called under the hood something pre-determined
|
||||
# that it could no longer be used.
|
||||
|
||||
# The only downside to doing it this way is services are
|
||||
# initialized prior to returning the details() if 3rd party tools
|
||||
# are polling what is available. These services that become
|
||||
# disabled thereafter are shown initially that they can be used.
|
||||
logger.error(
|
||||
'%s:// has become disabled on this system.', results['schema'])
|
||||
return None
|
||||
|
||||
return plugin
|
||||
|
||||
def add(self, servers, asset=None, tag=None):
|
||||
@ -585,7 +613,7 @@ class Apprise(object):
|
||||
attach=attach
|
||||
)
|
||||
|
||||
def details(self, lang=None):
|
||||
def details(self, lang=None, show_requirements=False, show_disabled=False):
|
||||
"""
|
||||
Returns the details associated with the Apprise object
|
||||
|
||||
@ -601,8 +629,27 @@ class Apprise(object):
|
||||
'asset': self.asset.details(),
|
||||
}
|
||||
|
||||
# to add it's mapping to our hash table
|
||||
for plugin in set(plugins.SCHEMA_MAP.values()):
|
||||
# Iterate over our hashed plugins and dynamically build details on
|
||||
# their status:
|
||||
|
||||
content = {
|
||||
'service_name': getattr(plugin, 'service_name', None),
|
||||
'service_url': getattr(plugin, 'service_url', None),
|
||||
'setup_url': getattr(plugin, 'setup_url', None),
|
||||
# Placeholder - populated below
|
||||
'details': None
|
||||
}
|
||||
|
||||
# Standard protocol(s) should be None or a tuple
|
||||
enabled = getattr(plugin, 'enabled', True)
|
||||
if not show_disabled and not enabled:
|
||||
# Do not show inactive plugins
|
||||
continue
|
||||
|
||||
elif show_disabled:
|
||||
# Add current state to response
|
||||
content['enabled'] = enabled
|
||||
|
||||
# Standard protocol(s) should be None or a tuple
|
||||
protocols = getattr(plugin, 'protocol', None)
|
||||
@ -614,23 +661,27 @@ class Apprise(object):
|
||||
if isinstance(secure_protocols, six.string_types):
|
||||
secure_protocols = (secure_protocols, )
|
||||
|
||||
# Add our protocol details to our content
|
||||
content.update({
|
||||
'protocols': protocols,
|
||||
'secure_protocols': secure_protocols,
|
||||
})
|
||||
|
||||
if not lang:
|
||||
# Simply return our results
|
||||
details = plugins.details(plugin)
|
||||
content['details'] = plugins.details(plugin)
|
||||
if show_requirements:
|
||||
content['requirements'] = plugins.requirements(plugin)
|
||||
|
||||
else:
|
||||
# Emulate the specified language when returning our results
|
||||
with self.locale.lang_at(lang):
|
||||
details = plugins.details(plugin)
|
||||
content['details'] = plugins.details(plugin)
|
||||
if show_requirements:
|
||||
content['requirements'] = plugins.requirements(plugin)
|
||||
|
||||
# Build our response object
|
||||
response['schemas'].append({
|
||||
'service_name': getattr(plugin, 'service_name', None),
|
||||
'service_url': getattr(plugin, 'service_url', None),
|
||||
'setup_url': getattr(plugin, 'setup_url', None),
|
||||
'protocols': protocols,
|
||||
'secure_protocols': secure_protocols,
|
||||
'details': details,
|
||||
})
|
||||
response['schemas'].append(content)
|
||||
|
||||
return response
|
||||
|
||||
|
@ -26,8 +26,10 @@
|
||||
import click
|
||||
import logging
|
||||
import platform
|
||||
import six
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
from os.path import isfile
|
||||
from os.path import expanduser
|
||||
@ -136,6 +138,9 @@ def print_version_msg():
|
||||
help='Perform a trial run but only prints the notification '
|
||||
'services to-be triggered to stdout. Notifications are never '
|
||||
'sent using this mode.')
|
||||
@click.option('--details', '-l', is_flag=True,
|
||||
help='Prints details about the current services supported by '
|
||||
'Apprise.')
|
||||
@click.option('--recursion-depth', '-R', default=DEFAULT_RECURSION_DEPTH,
|
||||
type=int,
|
||||
help='The number of recursive import entries that can be '
|
||||
@ -153,7 +158,7 @@ def print_version_msg():
|
||||
metavar='SERVER_URL [SERVER_URL2 [SERVER_URL3]]',)
|
||||
def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
input_format, dry_run, recursion_depth, verbose, disable_async,
|
||||
interpret_escapes, debug, version):
|
||||
details, interpret_escapes, debug, version):
|
||||
"""
|
||||
Send a notification to all of the specified servers identified by their
|
||||
URLs the content provided within the title, body and notification-type.
|
||||
@ -248,6 +253,78 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
# Create our Apprise object
|
||||
a = Apprise(asset=asset, debug=debug, location=ContentLocation.LOCAL)
|
||||
|
||||
if details:
|
||||
# Print details and exit
|
||||
results = a.details(show_requirements=True, show_disabled=True)
|
||||
|
||||
# Sort our results:
|
||||
plugins = sorted(
|
||||
results['schemas'], key=lambda i: str(i['service_name']))
|
||||
for entry in plugins:
|
||||
protocols = [] if not entry['protocols'] else \
|
||||
[p for p in entry['protocols']
|
||||
if isinstance(p, six.string_types)]
|
||||
protocols.extend(
|
||||
[] if not entry['secure_protocols'] else
|
||||
[p for p in entry['secure_protocols']
|
||||
if isinstance(p, six.string_types)])
|
||||
|
||||
if len(protocols) == 1:
|
||||
# Simplify view by swapping {schema} with the single
|
||||
# protocol value
|
||||
|
||||
# Convert tuple to list
|
||||
entry['details']['templates'] = \
|
||||
list(entry['details']['templates'])
|
||||
|
||||
for x in range(len(entry['details']['templates'])):
|
||||
entry['details']['templates'][x] = \
|
||||
re.sub(
|
||||
r'^[^}]+}://',
|
||||
'{}://'.format(protocols[0]),
|
||||
entry['details']['templates'][x])
|
||||
|
||||
click.echo(click.style(
|
||||
'{} {:<30} '.format(
|
||||
'+' if entry['enabled'] else '-',
|
||||
str(entry['service_name'])),
|
||||
fg="green" if entry['enabled'] else "red", bold=True),
|
||||
nl=(not entry['enabled'] or len(protocols) == 1))
|
||||
|
||||
if not entry['enabled']:
|
||||
if entry['requirements']['details']:
|
||||
click.echo(
|
||||
' ' + str(entry['requirements']['details']))
|
||||
|
||||
if entry['requirements']['packages_required']:
|
||||
click.echo(' Python Packages Required:')
|
||||
for req in entry['requirements']['packages_required']:
|
||||
click.echo(' - ' + req)
|
||||
|
||||
if entry['requirements']['packages_recommended']:
|
||||
click.echo(' Python Packages Recommended:')
|
||||
for req in entry['requirements']['packages_recommended']:
|
||||
click.echo(' - ' + req)
|
||||
|
||||
# new line padding between entries
|
||||
click.echo()
|
||||
continue
|
||||
|
||||
if len(protocols) > 1:
|
||||
click.echo('| Schema(s): {}'.format(
|
||||
', '.join(protocols),
|
||||
))
|
||||
|
||||
prefix = ' - '
|
||||
click.echo('{}{}'.format(
|
||||
prefix,
|
||||
'\n{}'.format(prefix).join(entry['details']['templates'])))
|
||||
|
||||
# new line padding between entries
|
||||
click.echo()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# The priorities of what is accepted are parsed in order below:
|
||||
# 1. URLs by command line
|
||||
# 2. Configuration by command line
|
||||
|
@ -52,6 +52,54 @@ class NotifyBase(BASE_OBJECT):
|
||||
This is the base class for all notification services
|
||||
"""
|
||||
|
||||
# An internal flag used to test the state of the plugin. If set to
|
||||
# False, then the plugin is not used. Plugins can disable themselves
|
||||
# due to enviroment issues (such as missing libraries, or platform
|
||||
# dependencies that are not present). By default all plugins are
|
||||
# enabled.
|
||||
enabled = True
|
||||
|
||||
# Some plugins may require additional packages above what is provided
|
||||
# already by Apprise.
|
||||
#
|
||||
# Use this section to relay this information to the users of the script to
|
||||
# help guide them with what they need to know if they plan on using your
|
||||
# plugin. The below configuration should otherwise accomodate all normal
|
||||
# situations and will not requrie any updating:
|
||||
requirements = {
|
||||
# Use the description to provide a human interpretable description of
|
||||
# what is required to make the plugin work. This is only nessisary
|
||||
# if there are package dependencies. Setting this to default will
|
||||
# cause a general response to be returned. Only set this if you plan
|
||||
# on over-riding the default. Always consider language support here.
|
||||
# So before providing a value do the following in your code base:
|
||||
#
|
||||
# from apprise.AppriseLocale import gettext_lazy as _
|
||||
#
|
||||
# 'details': _('My detailed requirements')
|
||||
'details': None,
|
||||
|
||||
# Define any required packages needed for the plugin to run. This is
|
||||
# an array of strings that simply look like lines residing in a
|
||||
# `requirements.txt` file...
|
||||
#
|
||||
# As an example, an entry may look like:
|
||||
# 'packages_required': [
|
||||
# 'cryptography < 3.4`,
|
||||
# ]
|
||||
'packages_required': [],
|
||||
|
||||
# Recommended packages identify packages that are not required to make
|
||||
# your plugin work, but would improve it's use or grant it access to
|
||||
# full functionality (that might otherwise be limited).
|
||||
|
||||
# Similar to `packages_required`, you would identify each entry in
|
||||
# the array as you would in a `requirements.txt` file.
|
||||
#
|
||||
# - Do not re-provide entries already in the `packages_required`
|
||||
'packages_recommended': [],
|
||||
}
|
||||
|
||||
# The services URL
|
||||
service_url = None
|
||||
|
||||
@ -223,6 +271,13 @@ class NotifyBase(BASE_OBJECT):
|
||||
|
||||
"""
|
||||
|
||||
if not self.enabled:
|
||||
# Deny notifications issued to services that are disabled
|
||||
self.logger.warning(
|
||||
"{} is currently disabled on this system.".format(
|
||||
self.service_name))
|
||||
return False
|
||||
|
||||
# Prepare attachments if required
|
||||
if attach is not None and not isinstance(attach, AppriseAttachment):
|
||||
try:
|
||||
|
@ -38,10 +38,6 @@ NOTIFY_DBUS_SUPPORT_ENABLED = False
|
||||
# Image support is dependant on the GdkPixbuf library being available
|
||||
NOTIFY_DBUS_IMAGE_SUPPORT = False
|
||||
|
||||
# The following are required to hook into the notifications:
|
||||
NOTIFY_DBUS_INTERFACE = 'org.freedesktop.Notifications'
|
||||
NOTIFY_DBUS_SETTING_LOCATION = '/org/freedesktop/Notifications'
|
||||
|
||||
# Initialize our mainloops
|
||||
LOOP_GLIB = None
|
||||
LOOP_QT = None
|
||||
@ -132,8 +128,19 @@ class NotifyDBus(NotifyBase):
|
||||
A wrapper for local DBus/Qt Notifications
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_DBUS_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'details': _('libdbus-1.so.x must be installed.')
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'DBus Notification'
|
||||
service_name = _('DBus Notification')
|
||||
|
||||
# The services URL
|
||||
service_url = 'http://www.freedesktop.org/Software/dbus/'
|
||||
|
||||
# The default protocols
|
||||
# Python 3 keys() does not return a list object, it's it's own dict_keys()
|
||||
@ -158,14 +165,9 @@ class NotifyDBus(NotifyBase):
|
||||
# content to display
|
||||
body_max_line_count = 10
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the gnome packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
# packages actually are present but we need to test that they aren't.
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_glib_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = NOTIFY_DBUS_SUPPORT_ENABLED
|
||||
# The following are required to hook into the notifications:
|
||||
dbus_interface = 'org.freedesktop.Notifications'
|
||||
dbus_setting_location = '/org/freedesktop/Notifications'
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
@ -241,12 +243,6 @@ class NotifyDBus(NotifyBase):
|
||||
"""
|
||||
Perform DBus Notification
|
||||
"""
|
||||
|
||||
if not self._enabled or MAINLOOP_MAP[self.schema] is None:
|
||||
self.logger.warning(
|
||||
"{} notifications could not be loaded.".format(self.schema))
|
||||
return False
|
||||
|
||||
# Acquire our session
|
||||
try:
|
||||
session = SessionBus(mainloop=MAINLOOP_MAP[self.schema])
|
||||
@ -265,14 +261,14 @@ class NotifyDBus(NotifyBase):
|
||||
|
||||
# acquire our dbus object
|
||||
dbus_obj = session.get_object(
|
||||
NOTIFY_DBUS_INTERFACE,
|
||||
NOTIFY_DBUS_SETTING_LOCATION,
|
||||
self.dbus_interface,
|
||||
self.dbus_setting_location,
|
||||
)
|
||||
|
||||
# Acquire our dbus interface
|
||||
dbus_iface = Interface(
|
||||
dbus_obj,
|
||||
dbus_interface=NOTIFY_DBUS_INTERFACE,
|
||||
dbus_interface=self.dbus_interface,
|
||||
)
|
||||
|
||||
# image path
|
||||
|
@ -48,7 +48,6 @@
|
||||
import six
|
||||
import requests
|
||||
from json import dumps
|
||||
from .oauth import GoogleOAuth
|
||||
from ..NotifyBase import NotifyBase
|
||||
from ...common import NotifyType
|
||||
from ...utils import validate_regex
|
||||
@ -56,6 +55,23 @@ from ...utils import parse_list
|
||||
from ...AppriseAttachment import AppriseAttachment
|
||||
from ...AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Default our global support flag
|
||||
NOTIFY_FCM_SUPPORT_ENABLED = False
|
||||
|
||||
try:
|
||||
from .oauth import GoogleOAuth
|
||||
|
||||
# We're good to go
|
||||
NOTIFY_FCM_SUPPORT_ENABLED = True
|
||||
|
||||
except ImportError:
|
||||
# cryptography is the dependency of the .oauth library
|
||||
|
||||
# Create a dummy object for init() call to work
|
||||
class GoogleOAuth(object):
|
||||
pass
|
||||
|
||||
|
||||
# Our lookup map
|
||||
FCM_HTTP_ERROR_MAP = {
|
||||
400: 'A bad request was made to the server.',
|
||||
@ -88,6 +104,15 @@ class NotifyFCM(NotifyBase):
|
||||
"""
|
||||
A wrapper for Google's Firebase Cloud Messaging Notifications
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_FCM_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'packages_required': 'cryptography'
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Firebase Cloud Messaging'
|
||||
|
||||
|
@ -52,6 +52,7 @@ except ImportError:
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
|
||||
try:
|
||||
# Python 3.x
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
except ImportError:
|
||||
|
@ -78,8 +78,19 @@ class NotifyGnome(NotifyBase):
|
||||
A wrapper for local Gnome Notifications
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_GNOME_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'details': _('A local Gnome environment is required.')
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Gnome Notification'
|
||||
service_name = _('Gnome Notification')
|
||||
|
||||
# The service URL
|
||||
service_url = 'https://www.gnome.org/'
|
||||
|
||||
# The default protocol
|
||||
protocol = 'gnome'
|
||||
@ -102,15 +113,6 @@ class NotifyGnome(NotifyBase):
|
||||
# cause any title (if defined) to get placed into the message body.
|
||||
title_maxlen = 0
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the gnome packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
# packages actually are present but we need to test that they aren't.
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_gnome_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = NOTIFY_GNOME_SUPPORT_ENABLED
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://',
|
||||
@ -157,11 +159,6 @@ class NotifyGnome(NotifyBase):
|
||||
Perform Gnome Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
self.logger.warning(
|
||||
"Gnome Notifications are not supported by this system.")
|
||||
return False
|
||||
|
||||
try:
|
||||
# App initialization
|
||||
Notify.init(self.app_id)
|
||||
|
@ -68,6 +68,13 @@ class NotifyGrowl(NotifyBase):
|
||||
A wrapper to Growl Notifications
|
||||
|
||||
"""
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_GROWL_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'packages_required': 'gntp'
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Growl'
|
||||
@ -84,15 +91,6 @@ class NotifyGrowl(NotifyBase):
|
||||
# Allows the user to specify the NotifyImageSize object
|
||||
image_size = NotifyImageSize.XY_72
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the windows packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
# packages actually are present but we need to test that they aren't.
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_growl_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = NOTIFY_GROWL_SUPPORT_ENABLED
|
||||
|
||||
# Disable throttle rate for Growl requests since they are normally
|
||||
# local anyway
|
||||
request_rate_per_sec = 0
|
||||
@ -251,13 +249,6 @@ class NotifyGrowl(NotifyBase):
|
||||
"""
|
||||
Perform Growl Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
self.logger.warning(
|
||||
"Growl Notifications are not supported by this system; "
|
||||
"`pip install gntp`.")
|
||||
return False
|
||||
|
||||
# Register ourselves with the server if we haven't done so already
|
||||
if not self.growl and not self.register():
|
||||
# We failed to register
|
||||
|
@ -87,6 +87,14 @@ class NotifyMQTT(NotifyBase):
|
||||
A wrapper for MQTT Notifications
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_MQTT_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'packages_required': 'paho-mqtt'
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'MQTT Notification'
|
||||
|
||||
@ -110,15 +118,6 @@ class NotifyMQTT(NotifyBase):
|
||||
# locally hosted anyway
|
||||
request_rate_per_sec = 0.5
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the mqtt packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
# packages actually are present but we need to test that they aren't.
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_mqtt_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = NOTIFY_MQTT_SUPPORT_ENABLED
|
||||
|
||||
# Port Defaults (unless otherwise specified)
|
||||
mqtt_insecure_port = 1883
|
||||
|
||||
@ -275,10 +274,6 @@ class NotifyMQTT(NotifyBase):
|
||||
(cert for cert in self.CA_CERTIFICATE_FILE_LOCATIONS
|
||||
if isfile(cert)), None)
|
||||
|
||||
if not self._enabled:
|
||||
# Nothing more we can do
|
||||
return
|
||||
|
||||
# Set up our MQTT Publisher
|
||||
try:
|
||||
# Get our protocol
|
||||
@ -310,12 +305,6 @@ class NotifyMQTT(NotifyBase):
|
||||
Perform MQTT Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
self.logger.warning(
|
||||
"MQTT Notifications are not supported by this system; "
|
||||
"`pip install paho-mqtt`.")
|
||||
return False
|
||||
|
||||
if len(self.topics) == 0:
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no MQTT topics to notify.')
|
||||
|
@ -36,6 +36,19 @@ from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Default our global support flag
|
||||
NOTIFY_MACOSX_SUPPORT_ENABLED = False
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
# Check this is Mac OS X 10.8, or higher
|
||||
major, minor = platform.mac_ver()[0].split('.')[:2]
|
||||
|
||||
# Toggle our enabled flag if verion is correct and executable
|
||||
# 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.
|
||||
NOTIFY_MACOSX_SUPPORT_ENABLED = \
|
||||
(int(major) > 10 or (int(major) == 10 and int(minor) >= 8))
|
||||
|
||||
|
||||
class NotifyMacOSX(NotifyBase):
|
||||
"""
|
||||
@ -44,8 +57,22 @@ class NotifyMacOSX(NotifyBase):
|
||||
Source: https://github.com/julienXX/terminal-notifier
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_MACOSX_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'details': _(
|
||||
'Only works with Mac OS X 10.8 and higher. Additionally '
|
||||
' requires that /usr/local/bin/terminal-notifier is locally '
|
||||
'accessible.')
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'MacOSX Notification'
|
||||
service_name = _('MacOSX Notification')
|
||||
|
||||
# The services URL
|
||||
service_url = 'https://github.com/julienXX/terminal-notifier'
|
||||
|
||||
# The default protocol
|
||||
protocol = 'macosx'
|
||||
@ -100,31 +127,8 @@ class NotifyMacOSX(NotifyBase):
|
||||
# or not.
|
||||
self.include_image = include_image
|
||||
|
||||
self._enabled = False
|
||||
if platform.system() == 'Darwin':
|
||||
# Check this is Mac OS X 10.8, or higher
|
||||
major, minor = platform.mac_ver()[0].split('.')[:2]
|
||||
|
||||
# Toggle our _enabled flag if verion is correct and executable
|
||||
# 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.
|
||||
if not (int(major) > 10 or (int(major) == 10 and int(minor) >= 8)):
|
||||
self.logger.warning(
|
||||
"MacOSX Notifications require your OS to be at least "
|
||||
"v10.8 (detected {}.{})".format(major, minor))
|
||||
|
||||
elif not os.access(self.notify_path, os.X_OK):
|
||||
self.logger.warning(
|
||||
"MacOSX Notifications require '{}' to be in place."
|
||||
.format(self.notify_path))
|
||||
|
||||
else:
|
||||
# We're good to go
|
||||
self._enabled = True
|
||||
|
||||
# Set sound object (no q/a for now)
|
||||
self.sound = sound
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
@ -132,9 +136,10 @@ class NotifyMacOSX(NotifyBase):
|
||||
Perform MacOSX Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
if not os.access(self.notify_path, os.X_OK):
|
||||
self.logger.warning(
|
||||
"MacOSX Notifications are not supported by this system.")
|
||||
"MacOSX Notifications require '{}' to be in place."
|
||||
.format(self.notify_path))
|
||||
return False
|
||||
|
||||
# Start with our notification path
|
||||
@ -160,6 +165,9 @@ class NotifyMacOSX(NotifyBase):
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
# Capture some output for helpful debugging later on
|
||||
self.logger.debug('MacOSX CMD: {}'.format(' '.join(cmd)))
|
||||
|
||||
# Send our notification
|
||||
output = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
@ -32,20 +32,37 @@ from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from base64 import urlsafe_b64encode
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# We're good to go!
|
||||
NOTIFY_SIMPLEPUSH_ENABLED = True
|
||||
|
||||
except ImportError:
|
||||
# cryptography is required in order for this package to work
|
||||
NOTIFY_SIMPLEPUSH_ENABLED = False
|
||||
|
||||
|
||||
class NotifySimplePush(NotifyBase):
|
||||
"""
|
||||
A wrapper for SimplePush Notifications
|
||||
"""
|
||||
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_SIMPLEPUSH_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'packages_required': 'cryptography'
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'SimplePush'
|
||||
|
||||
|
@ -56,6 +56,13 @@ class NotifyWindows(NotifyBase):
|
||||
"""
|
||||
A wrapper for local Windows Notifications
|
||||
"""
|
||||
# Set our global enabled flag
|
||||
enabled = NOTIFY_WINDOWS_SUPPORT_ENABLED
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'details': _('A local Microsoft Windows environment is required.')
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Windows Notification'
|
||||
@ -80,15 +87,6 @@ class NotifyWindows(NotifyBase):
|
||||
# The number of seconds to display the popup for
|
||||
default_popup_duration_sec = 12
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the windows packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
# packages actually are present but we need to test that they aren't.
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_windows_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = NOTIFY_WINDOWS_SUPPORT_ENABLED
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://',
|
||||
@ -144,12 +142,6 @@ class NotifyWindows(NotifyBase):
|
||||
Perform Windows Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
self.logger.warning(
|
||||
"Windows Notifications are not supported by this system; "
|
||||
"`pip install pywin32`.")
|
||||
return False
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
|
@ -149,13 +149,8 @@ class SliXmppAdapter(object):
|
||||
|
||||
# If the user specified a port, skip SRV resolving, otherwise it is a
|
||||
# lot easier to let slixmpp handle DNS instead of the user.
|
||||
if self.port is None:
|
||||
override_connection = None
|
||||
else:
|
||||
override_connection = (self.host, self.port)
|
||||
|
||||
# Instruct slixmpp to connect to the XMPP service.
|
||||
self.xmpp.connect(override_connection, use_ssl=self.secure)
|
||||
self.override_connection = \
|
||||
None if not self.port else (self.host, self.port)
|
||||
|
||||
# We're good
|
||||
return True
|
||||
@ -166,6 +161,11 @@ class SliXmppAdapter(object):
|
||||
|
||||
"""
|
||||
|
||||
# Instruct slixmpp to connect to the XMPP service.
|
||||
if not self.xmpp.connect(
|
||||
self.override_connection, use_ssl=self.secure):
|
||||
return False
|
||||
|
||||
# Run the asyncio event loop, and return once disconnected,
|
||||
# for any reason.
|
||||
self.xmpp.process(forever=False)
|
||||
@ -191,7 +191,8 @@ class SliXmppAdapter(object):
|
||||
self.before_message()
|
||||
|
||||
# The message we wish to send, and the JID that will receive it.
|
||||
self.xmpp.send_message(mto=target, msubject=self.subject,
|
||||
self.xmpp.send_message(
|
||||
mto=target, msubject=self.subject,
|
||||
mbody=self.body, mtype='chat')
|
||||
|
||||
# Using wait=True ensures that the send queue will be
|
||||
|
@ -40,10 +40,22 @@ class NotifyXMPP(NotifyBase):
|
||||
"""
|
||||
A wrapper for XMPP Notifications
|
||||
"""
|
||||
# Set our global enabled flag
|
||||
enabled = SliXmppAdapter._enabled
|
||||
|
||||
requirements = {
|
||||
# Define our required packaging in order to work
|
||||
'packages_required': [
|
||||
"slixmpp; python_version >= '3.7'",
|
||||
]
|
||||
}
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'XMPP'
|
||||
|
||||
# The services URL
|
||||
service_url = 'https://xmpp.org/'
|
||||
|
||||
# The default protocol
|
||||
protocol = 'xmpp'
|
||||
|
||||
@ -56,14 +68,7 @@ class NotifyXMPP(NotifyBase):
|
||||
# Lower throttle rate for XMPP
|
||||
request_rate_per_sec = 0.5
|
||||
|
||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||
# in an environment that simply doesn't have the slixmpp package
|
||||
# available to us.
|
||||
#
|
||||
# If anyone is seeing this had knows a better way of testing this
|
||||
# outside of what is defined in test/test_xmpp_plugin.py, please
|
||||
# let me know! :)
|
||||
_enabled = SliXmppAdapter._enabled
|
||||
# Our XMPP Adapter we use to communicate through
|
||||
_adapter = SliXmppAdapter if SliXmppAdapter._enabled else None
|
||||
|
||||
# Define object templates
|
||||
@ -211,12 +216,6 @@ class NotifyXMPP(NotifyBase):
|
||||
Perform XMPP Notification
|
||||
"""
|
||||
|
||||
if not self._enabled:
|
||||
self.logger.warning(
|
||||
'XMPP Notifications are not supported by this system '
|
||||
'- install slixmpp.')
|
||||
return False
|
||||
|
||||
# Detect our JID if it isn't otherwise specified
|
||||
jid = self.jid
|
||||
password = self.password
|
||||
|
@ -439,6 +439,92 @@ def details(plugin):
|
||||
}
|
||||
|
||||
|
||||
def requirements(plugin):
|
||||
"""
|
||||
Provides a list of packages and its requirement details
|
||||
|
||||
"""
|
||||
requirements = {
|
||||
# Use the description to provide a human interpretable description of
|
||||
# what is required to make the plugin work. This is only nessisary
|
||||
# if there are package dependencies
|
||||
'details': '',
|
||||
|
||||
# Define any required packages needed for the plugin to run. This is
|
||||
# an array of strings that simply look like lines in the
|
||||
# `requirements.txt` file...
|
||||
#
|
||||
# A single string is perfectly acceptable:
|
||||
# 'packages_required' = 'cryptography'
|
||||
#
|
||||
# Multiple entries should look like the following
|
||||
# 'packages_required' = [
|
||||
# 'cryptography < 3.4`,
|
||||
# ]
|
||||
#
|
||||
'packages_required': [],
|
||||
|
||||
# Recommended packages identify packages that are not required to make
|
||||
# your plugin work, but would improve it's use or grant it access to
|
||||
# full functionality (that might otherwise be limited).
|
||||
|
||||
# Similar to `packages_required`, you would identify each entry in
|
||||
# the array as you would in a `requirements.txt` file.
|
||||
#
|
||||
# - Do not re-provide entries already in the `packages_required`
|
||||
'packages_recommended': [],
|
||||
}
|
||||
|
||||
# Populate our template differently if we don't find anything above
|
||||
if not (hasattr(plugin, 'requirements')
|
||||
and isinstance(plugin.requirements, dict)):
|
||||
# We're done early
|
||||
return requirements
|
||||
|
||||
# Get our required packages
|
||||
_req_packages = plugin.requirements.get('packages_required')
|
||||
if isinstance(_req_packages, six.string_types):
|
||||
# Convert to list
|
||||
_req_packages = [_req_packages]
|
||||
|
||||
elif not isinstance(_req_packages, (set, list, tuple)):
|
||||
# Allow one to set the required packages to None (as an example)
|
||||
_req_packages = []
|
||||
|
||||
requirements['packages_required'] = [str(p) for p in _req_packages]
|
||||
|
||||
# Get our recommended packages
|
||||
_opt_packages = plugin.requirements.get('packages_recommended')
|
||||
if isinstance(_opt_packages, six.string_types):
|
||||
# Convert to list
|
||||
_opt_packages = [_opt_packages]
|
||||
|
||||
elif not isinstance(_opt_packages, (set, list, tuple)):
|
||||
# Allow one to set the recommended packages to None (as an example)
|
||||
_opt_packages = []
|
||||
|
||||
requirements['packages_recommended'] = [str(p) for p in _opt_packages]
|
||||
|
||||
# Get our package details
|
||||
_req_details = plugin.requirements.get('details')
|
||||
if not _req_details:
|
||||
if not (_req_packages and _opt_packages):
|
||||
_req_details = _('No dependencies.')
|
||||
|
||||
elif _req_packages:
|
||||
_req_details = _('Packages are required to function.')
|
||||
|
||||
else: # opt_packages
|
||||
_req_details = \
|
||||
_('Packages are recommended to improve functionality.')
|
||||
else:
|
||||
# Store our details if defined
|
||||
requirements['details'] = _req_details
|
||||
|
||||
# Return our compiled package requirements
|
||||
return requirements
|
||||
|
||||
|
||||
def url_to_dict(url, secure_logging=True):
|
||||
"""
|
||||
Takes an apprise URL and returns the tokens associated with it
|
||||
|
@ -5,16 +5,3 @@ pytest
|
||||
pytest-cov
|
||||
tox
|
||||
babel
|
||||
|
||||
#
|
||||
# Plugin Dependencies
|
||||
#
|
||||
|
||||
# Provides xmpp:// support
|
||||
slixmpp; python_version >= '3.7'
|
||||
|
||||
# Provides growl:// support
|
||||
gntp
|
||||
|
||||
# Provides mqtt:// support
|
||||
paho-mqtt
|
||||
|
@ -1,224 +1,141 @@
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "APPRISE" "1" "February 2021" "ff" ""
|
||||
.
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "APPRISE" "1" "November 2021" ""
|
||||
.SH "NAME"
|
||||
\fBapprise\fR \- Push Notifications that work with just about every platform!
|
||||
.
|
||||
.SH "SYNOPSIS"
|
||||
\fBapprise\fR [\fIoptions\fR\.\.\.] \fIservice\-url\fR\.\.\.
|
||||
.
|
||||
\fBapprise\fR [\fIoptions\fR\|\.\|\.\|\.] \fIservice\-url\fR\|\.\|\.\|\.
|
||||
.br
|
||||
.
|
||||
.SH "DESCRIPTION"
|
||||
\fBApprise\fR allows you to send a notification to \fIalmost all\fR of the most popular notification services available to us today such as: Discord, Telegram, Pushbullet, Slack, Twitter, etc\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
One notification library to rule them all\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
A common and intuitive notification syntax\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
Supports the handling of images (to the notification services that will accept them)\.
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "OPTIONS"
|
||||
The Apprise options are as follows:
|
||||
.
|
||||
.P
|
||||
\fB\-b\fR, \fB\-\-body=\fR\fITEXT\fR: Specify the message body\. If no body is specified then content is read from \fIstdin\fR\.
|
||||
.
|
||||
.P
|
||||
\fB\-t\fR, \fB\-\-title=\fR\fITEXT\fR: Specify the message title\. This field is complete optional\.
|
||||
.
|
||||
.P
|
||||
\fB\-c\fR, \fB\-\-config=\fR\fICONFIG\-URL\fR: Specify one or more configuration locations\.
|
||||
.
|
||||
.P
|
||||
\fB\-a\fR, \fB\-\-attach=\fR\fIATTACH\-URL\fR: Specify one or more file attachment locations\.
|
||||
.
|
||||
.P
|
||||
\fB\-n\fR, \fB\-\-notification\-type=\fR\fITYPE\fR: Specify the message type (default=info)\. Possible values are "info", "success", "failure", and "warning"\.
|
||||
.
|
||||
.P
|
||||
\fB\-i\fR, \fB\-\-input\-format=\fR\fIFORMAT\fR: Specify the input message format (default=text)\. Possible values are "text", "html", and "markdown"\.
|
||||
.
|
||||
.P
|
||||
\fB\-T\fR, \fB\-\-theme=\fRTHEME: Specify the default theme\.
|
||||
.
|
||||
.P
|
||||
\fB\-g\fR, \fB\-\-tag=\fRTAG: Specify one or more tags to filter which services to notify\. Use multiple \fB\-\-tag\fR (\fB\-g\fR) entries to \fBOR\fR the tags together and comma separated to \fBAND\fR them\. If no tags are specified then all services are notified\.
|
||||
.
|
||||
.P
|
||||
\fB\-Da\fR, \fB\-\-disable\-async\fR: Send notifications synchronously (one after the other) instead of all at once\.
|
||||
.
|
||||
.P
|
||||
\fB\-R\fR, \fB\-\-recursion\-depth\fR: he number of recursive import entries that can be loaded from within Apprise configuration\. By default this is set to 1\. If this is set to zero, then import statements found in any configuration is ignored\.
|
||||
.
|
||||
.P
|
||||
\fB\-e\fR, \fB\-\-interpret\-escapes\fR Enable interpretation of backslash escapes\. For example, this would convert sequences such as \en and \er to their respected ascii new\-line and carriage
|
||||
.
|
||||
.P
|
||||
\fB\-d\fR, \fB\-\-dry\-run\fR: Perform a trial run but only prints the notification services to\-be triggered to \fBstdout\fR\. Notifications are never sent using this mode\.
|
||||
.
|
||||
.P
|
||||
return characters prior to the delivery of the notification\.
|
||||
.
|
||||
.P
|
||||
\fB\-l\fR, \fB\-\-details\fR Prints details about the current services supported by Apprise\.
|
||||
.P
|
||||
\fB\-v\fR, \fB\-\-verbose\fR: The more of these you specify, the more verbose the output is\. e\.g: \-vvvv
|
||||
.
|
||||
.P
|
||||
\fB\-D\fR, \fB\-\-debug\fR: A debug mode; useful for troubleshooting\.
|
||||
.
|
||||
.P
|
||||
\fB\-V\fR, \fB\-\-version\fR: Display the apprise version and exit\.
|
||||
.
|
||||
.P
|
||||
\fB\-h\fR, \fB\-\-help\fR: Show this message and exit\.
|
||||
.
|
||||
.SH "EXIT STATUS"
|
||||
\fBapprise\fR exits with a status of:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
\fB0\fR if all of the notifications were sent successfully\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
\fB1\fR if one or more notifications could not be sent\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
\fB2\fR if there was an error specified on the command line such as not providing an valid argument\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
.IP "\[ci]" 4
|
||||
\fB3\fR if there was one or more Apprise Service URLs successfully loaded but none could be notified due to user filtering (via tags)\.
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "SERVICE URLS"
|
||||
There are to many service URL and combinations to list here\. It\'s best to visit the Apprise GitHub page \fIhttps://github\.com/caronc/apprise/wiki#notification\-services\fR and see what\'s available\.
|
||||
.
|
||||
.SH "EXAMPLES"
|
||||
Send a notification to as many servers as you want to specify as you can easily chain them together:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "my title" \-b "my notification body" \e
|
||||
"mailto://myemail:mypass@gmail\.com" \e
|
||||
"pbul://o\.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b"
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
If you don\'t specify a \fB\-\-body\fR (\fB\-b\fR) then stdin is used allowing you to use the tool as part of your every day administration:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ cat /proc/cpuinfo | apprise \-vv \-t "cpu info" \e
|
||||
"mailto://myemail:mypass@gmail\.com"
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Load in a configuration file which identifies all of your notification service URLs and notify them all:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "my title" \-b "my notification body" \e
|
||||
\-\-config=~/apprise\.yml
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Load in a configuration file from a remote server that identifies all of your notification service URLs and only notify the ones tagged as \fIdevops\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "my title" \-b "my notification body" \e
|
||||
\-\-config=https://localhost/my/apprise/config \e
|
||||
\-t devops
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Include an attachment:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "School Assignment" \-b "See attached" \e
|
||||
\-\-attach=Documents/FinalReport\.docx
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "CONFIGURATION"
|
||||
A configuration file can be in the format of either \fBTEXT\fR or \fBYAML\fR where [TEXT][textconfig] is the easiest and most ideal solution for most users\. However YAML \fIhttps://github\.com/caronc/apprise/wiki/config_yaml\fR configuration files grants the user a bit more leverage and access to some of the internal features of Apprise\. Reguardless of which format you choose, both provide the users the ability to leverage \fBtagging\fR which adds a more rich and powerful notification environment\.
|
||||
.
|
||||
.P
|
||||
Configuration files can be directly referenced via \fBapprise\fR when referencing the \fB\-\-config=\fR (\fB\-c\fR) CLI directive\. You can identify as many as you like on the command line and all of them will be loaded\. You can also point your configuration to a cloud location (by referencing \fBhttp://\fR or \fBhttps://\fR\. By default \fBapprise\fR looks in the following local locations for configuration files and loads them:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ ~/\.apprise
|
||||
$ ~/\.apprise\.yml
|
||||
$ ~/\.config/apprise
|
||||
$ ~/\.config/apprise\.yml
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
If a default configuration file is referenced in any way by the \fBapprise\fR tool, you no longer need to provide it a Service URL\. Usage of the \fBapprise\fR tool simplifies to:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "my title" \-b "my notification body"
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
If you leveraged tagging \fIhttps://github\.com/caronc/apprise/wiki/CLI_Usage#label\-leverage\-tagging\fR, you can define all of Apprise Service URLs in your configuration that you want and only specifically notify a subset of them:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ apprise \-vv \-t "Will Be Late" \-b "Go ahead and make dinner without me" \e
|
||||
\-\-tag=family
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "BUGS"
|
||||
If you find any bugs, please make them known at: \fIhttps://github\.com/caronc/apprise/issues\fR
|
||||
.
|
||||
.SH "COPYRIGHT"
|
||||
Apprise is Copyright (C) 2021 Chris Caron \fIlead2gold@gmail\.com\fR
|
||||
|
@ -68,6 +68,9 @@ The Apprise options are as follows:
|
||||
|
||||
return characters prior to the delivery of the notification.
|
||||
|
||||
`-l`, `--details`
|
||||
Prints details about the current services supported by Apprise.
|
||||
|
||||
`-v`, `--verbose`:
|
||||
The more of these you specify, the more verbose the output is. e.g: -vvvv
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
cryptography
|
||||
requests
|
||||
requests-oauthlib
|
||||
six
|
||||
|
@ -18,6 +18,7 @@ test=pytest
|
||||
[tool:pytest]
|
||||
addopts = --verbose -ra
|
||||
python_files = test/test_*.py
|
||||
norecursedirs=test/helpers
|
||||
filterwarnings =
|
||||
once::Warning
|
||||
strict = true
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
@ -22,43 +22,6 @@
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_nextcloud_empty_body(mock_post):
|
||||
"""
|
||||
API: NotifyNextcloud() empty body
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# A response
|
||||
robj = mock.Mock()
|
||||
robj.content = ''
|
||||
robj.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = robj
|
||||
|
||||
# Variation Initializations
|
||||
obj = plugins.NotifyNextcloud(
|
||||
host="localhost", user="admin", password="pass", targets="user")
|
||||
assert isinstance(obj, plugins.NotifyNextcloud) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# An empty body
|
||||
assert obj.send(body="") is True
|
||||
assert 'data' in mock_post.call_args_list[0][1]
|
||||
assert 'shortMessage' in mock_post.call_args_list[0][1]['data']
|
||||
# The longMessage argument is not set
|
||||
assert 'longMessage' not in mock_post.call_args_list[0][1]['data']
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers'))
|
@ -22,42 +22,8 @@
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from .rest import AppriseURLTester
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_homeassistant_plugin(mock_post):
|
||||
"""
|
||||
API: NotifyHomeAssistant() Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
response = mock.Mock()
|
||||
response.content = ''
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Variation Initializations
|
||||
obj = Apprise.instantiate('hassio://localhost/accesstoken')
|
||||
assert isinstance(obj, plugins.NotifyHomeAssistant) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body="test") is True
|
||||
|
||||
assert mock_post.call_count == 1
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'http://localhost:8123/api/services/persistent_notification/create'
|
||||
__all__ = [
|
||||
'AppriseURLTester',
|
||||
]
|
519
test/helpers/rest.py
Normal file
519
test/helpers/rest.py
Normal file
@ -0,0 +1,519 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import re
|
||||
import os
|
||||
import six
|
||||
import requests
|
||||
import mock
|
||||
from json import dumps
|
||||
from random import choice
|
||||
from string import ascii_uppercase as str_alpha
|
||||
from string import digits as str_num
|
||||
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAsset
|
||||
from apprise import AppriseAttachment
|
||||
from apprise.common import OverflowMode
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
class AppriseURLTester(object):
|
||||
|
||||
# Some exception handling we'll use
|
||||
req_exceptions = (
|
||||
requests.ConnectionError(
|
||||
0, 'requests.ConnectionError() not handled'),
|
||||
requests.RequestException(
|
||||
0, 'requests.RequestException() not handled'),
|
||||
requests.HTTPError(
|
||||
0, 'requests.HTTPError() not handled'),
|
||||
requests.ReadTimeout(
|
||||
0, 'requests.ReadTimeout() not handled'),
|
||||
requests.TooManyRedirects(
|
||||
0, 'requests.TooManyRedirects() not handled'),
|
||||
)
|
||||
|
||||
# Attachment Testing Directory
|
||||
__test_var_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)), 'var')
|
||||
|
||||
# Our URLs we'll test against
|
||||
__tests = []
|
||||
|
||||
# Define how many characters exist per line
|
||||
row = 80
|
||||
|
||||
# Some variables we use to control the data we work with
|
||||
body_len = 1024
|
||||
title_len = 1024
|
||||
|
||||
def __init__(self, tests=None, *args, **kwargs):
|
||||
"""
|
||||
Our initialization
|
||||
"""
|
||||
# Create a large body and title with random data
|
||||
self.body = ''.join(
|
||||
choice(str_alpha + str_num + ' ') for _ in range(self.body_len))
|
||||
self.body = '\r\n'.join(
|
||||
[self.body[i: i + self.row]
|
||||
for i in range(0, len(self.body), self.row)])
|
||||
|
||||
# Create our title using random data
|
||||
self.title = ''.join(
|
||||
choice(str_alpha + str_num) for _ in range(self.title_len))
|
||||
|
||||
if tests:
|
||||
self.__tests = tests
|
||||
|
||||
def add(self, url, meta):
|
||||
"""
|
||||
Adds a test suite to our object
|
||||
"""
|
||||
self.__tests.append({
|
||||
'url': url,
|
||||
'meta': meta,
|
||||
})
|
||||
|
||||
def run_all(self):
|
||||
"""
|
||||
Run all of our tests
|
||||
"""
|
||||
# iterate over our dictionary and test it out
|
||||
for (url, meta) in self.__tests:
|
||||
self.run(url, meta)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def run(self, url, meta, mock_post, mock_get):
|
||||
"""
|
||||
Run a specific test
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Our expected instance
|
||||
instance = meta.get('instance', None)
|
||||
|
||||
# Our expected server objects
|
||||
_self = meta.get('self', None)
|
||||
|
||||
# Our expected Query response (True, False, or exception type)
|
||||
response = meta.get('response', True)
|
||||
|
||||
# Our expected privacy url
|
||||
# Don't set this if don't need to check it's value
|
||||
privacy_url = meta.get('privacy_url')
|
||||
|
||||
# Our regular expression
|
||||
url_matches = meta.get('url_matches')
|
||||
|
||||
# Allow us to force the server response code to be something other then
|
||||
# the defaults
|
||||
requests_response_code = meta.get(
|
||||
'requests_response_code',
|
||||
requests.codes.ok if response else requests.codes.not_found,
|
||||
)
|
||||
|
||||
# Allow us to force the server response text to be something other then
|
||||
# the defaults
|
||||
requests_response_text = meta.get('requests_response_text')
|
||||
if not isinstance(requests_response_text, six.string_types):
|
||||
# Convert to string
|
||||
requests_response_text = dumps(requests_response_text)
|
||||
|
||||
# Whether or not we should include an image with our request; unless
|
||||
# otherwise specified, we assume that images are to be included
|
||||
include_image = meta.get('include_image', True)
|
||||
if include_image:
|
||||
# a default asset
|
||||
asset = AppriseAsset()
|
||||
|
||||
else:
|
||||
# Disable images
|
||||
asset = AppriseAsset(image_path_mask=False, image_url_mask=False)
|
||||
asset.image_url_logo = None
|
||||
|
||||
test_requests_exceptions = meta.get(
|
||||
'test_requests_exceptions', False)
|
||||
|
||||
# Mock our request object
|
||||
robj = mock.Mock()
|
||||
robj.content = u''
|
||||
mock_get.return_value = robj
|
||||
mock_post.return_value = robj
|
||||
|
||||
if test_requests_exceptions is False:
|
||||
# Handle our default response
|
||||
mock_post.return_value.status_code = requests_response_code
|
||||
mock_get.return_value.status_code = requests_response_code
|
||||
|
||||
# Handle our default text response
|
||||
mock_get.return_value.content = requests_response_text
|
||||
mock_post.return_value.content = requests_response_text
|
||||
mock_get.return_value.text = requests_response_text
|
||||
mock_post.return_value.text = requests_response_text
|
||||
|
||||
# Ensure there is no side effect set
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
else:
|
||||
# Handle exception testing; first we turn the boolean flag
|
||||
# into a list of exceptions
|
||||
test_requests_exceptions = self.req_exceptions
|
||||
|
||||
try:
|
||||
# We can now instantiate our object:
|
||||
obj = Apprise.instantiate(
|
||||
url, asset=asset, suppress_exceptions=False)
|
||||
|
||||
except Exception as e:
|
||||
# Handle our exception
|
||||
if instance is None:
|
||||
print('%s %s' % (url, str(e)))
|
||||
raise e
|
||||
|
||||
if not isinstance(e, instance):
|
||||
print('%s %s' % (url, str(e)))
|
||||
raise e
|
||||
|
||||
# We're okay if we get here
|
||||
return
|
||||
|
||||
if obj is None:
|
||||
if instance is not None:
|
||||
# We're done (assuming this is what we were
|
||||
# expecting)
|
||||
print("{} didn't instantiate itself "
|
||||
"(we expected it to be a {})".format(
|
||||
url, instance))
|
||||
assert False
|
||||
# We're done because we got the results we expected
|
||||
return
|
||||
|
||||
if instance is None:
|
||||
# Expected None but didn't get it
|
||||
print('%s instantiated %s (but expected None)' % (
|
||||
url, str(obj)))
|
||||
assert False
|
||||
|
||||
if not isinstance(obj, instance):
|
||||
print('%s instantiated %s (but expected %s)' % (
|
||||
url, type(instance), str(obj)))
|
||||
assert False
|
||||
|
||||
if isinstance(obj, plugins.NotifyBase):
|
||||
# We loaded okay; now lets make sure we can reverse
|
||||
# this url
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Test url() with privacy=True
|
||||
assert isinstance(
|
||||
obj.url(privacy=True), six.string_types) is True
|
||||
|
||||
# Some Simple Invalid Instance Testing
|
||||
assert instance.parse_url(None) is None
|
||||
assert instance.parse_url(object) is None
|
||||
assert instance.parse_url(42) is None
|
||||
|
||||
if privacy_url:
|
||||
# Assess that our privacy url is as expected
|
||||
assert obj.url(
|
||||
privacy=True).startswith(privacy_url)
|
||||
|
||||
if url_matches:
|
||||
# Assess that our URL matches a set regex
|
||||
assert re.search(url_matches, obj.url())
|
||||
|
||||
# Instantiate the exact same object again using the URL
|
||||
# from the one that was already created properly
|
||||
obj_cmp = Apprise.instantiate(obj.url())
|
||||
|
||||
# Our object should be the same instance as what we had
|
||||
# originally expected above.
|
||||
if not isinstance(obj_cmp, plugins.NotifyBase):
|
||||
# Assert messages are hard to trace back with the
|
||||
# way these tests work. Just printing before
|
||||
# throwing our assertion failure makes things
|
||||
# easier to debug later on
|
||||
print('TEST FAIL: {} regenerated as {}'.format(
|
||||
url, obj.url()))
|
||||
assert False
|
||||
|
||||
# Tidy our object
|
||||
del obj_cmp
|
||||
|
||||
if _self:
|
||||
# Iterate over our expected entries inside of our
|
||||
# object
|
||||
for key, val in self.items():
|
||||
# Test that our object has the desired key
|
||||
assert hasattr(key, obj) is True
|
||||
assert getattr(key, obj) == val
|
||||
|
||||
try:
|
||||
self.__notify(url, obj, meta, asset)
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
print('%s AssertionError' % url)
|
||||
raise
|
||||
|
||||
# Tidy our object and allow any possible defined deconstructors to
|
||||
# be executed.
|
||||
del obj
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def __notify(self, url, obj, meta, asset, mock_post, mock_get):
|
||||
"""
|
||||
Perform notification testing against object specified
|
||||
"""
|
||||
#
|
||||
# Prepare our options
|
||||
#
|
||||
|
||||
# Allow notification type override, otherwise default to INFO
|
||||
notify_type = meta.get('notify_type', NotifyType.INFO)
|
||||
|
||||
# Whether or not we're testing exceptions or not
|
||||
test_requests_exceptions = meta.get('test_requests_exceptions', False)
|
||||
|
||||
# Our expected Query response (True, False, or exception type)
|
||||
response = meta.get('response', True)
|
||||
|
||||
# Our expected Notify response (True or False)
|
||||
notify_response = meta.get('notify_response', response)
|
||||
|
||||
# Our expected Notify Attachment response (True or False)
|
||||
attach_response = meta.get('attach_response', notify_response)
|
||||
|
||||
# Test attachments
|
||||
# Don't set this if don't need to check it's value
|
||||
check_attachments = meta.get('check_attachments', True)
|
||||
|
||||
# Allow us to force the server response code to be something other then
|
||||
# the defaults
|
||||
requests_response_code = meta.get(
|
||||
'requests_response_code',
|
||||
requests.codes.ok if response else requests.codes.not_found,
|
||||
)
|
||||
|
||||
# Allow us to force the server response text to be something other then
|
||||
# the defaults
|
||||
requests_response_text = meta.get('requests_response_text')
|
||||
if not isinstance(requests_response_text, six.string_types):
|
||||
# Convert to string
|
||||
requests_response_text = dumps(requests_response_text)
|
||||
|
||||
# A request
|
||||
robj = mock.Mock()
|
||||
robj.content = u''
|
||||
mock_get.return_value = robj
|
||||
mock_post.return_value = robj
|
||||
|
||||
if test_requests_exceptions is False:
|
||||
# Handle our default response
|
||||
mock_post.return_value.status_code = requests_response_code
|
||||
mock_get.return_value.status_code = requests_response_code
|
||||
|
||||
# Handle our default text response
|
||||
mock_get.return_value.content = requests_response_text
|
||||
mock_post.return_value.content = requests_response_text
|
||||
mock_get.return_value.text = requests_response_text
|
||||
mock_post.return_value.text = requests_response_text
|
||||
|
||||
# Ensure there is no side effect set
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
else:
|
||||
# Handle exception testing; first we turn the boolean flag
|
||||
# into a list of exceptions
|
||||
test_requests_exceptions = self.req_exceptions
|
||||
|
||||
try:
|
||||
if test_requests_exceptions is False:
|
||||
# Disable throttling
|
||||
obj.request_rate_per_sec = 0
|
||||
|
||||
# check that we're as expected
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type) == notify_response
|
||||
|
||||
# check that this doesn't change using different overflow
|
||||
# methods
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type,
|
||||
overflow=OverflowMode.UPSTREAM) == notify_response
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type,
|
||||
overflow=OverflowMode.TRUNCATE) == notify_response
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type,
|
||||
overflow=OverflowMode.SPLIT) == notify_response
|
||||
|
||||
#
|
||||
# Handle varations of the Asset Object missing fields
|
||||
#
|
||||
|
||||
# First make a backup
|
||||
app_id = asset.app_id
|
||||
app_desc = asset.app_desc
|
||||
|
||||
# now clear records
|
||||
asset.app_id = None
|
||||
asset.app_desc = None
|
||||
|
||||
# Notify should still work
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type) == notify_response
|
||||
|
||||
# App ID only
|
||||
asset.app_id = app_id
|
||||
asset.app_desc = None
|
||||
|
||||
# Notify should still work
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type) == notify_response
|
||||
|
||||
# App Desc only
|
||||
asset.app_id = None
|
||||
asset.app_desc = app_desc
|
||||
|
||||
# Notify should still work
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type) == notify_response
|
||||
|
||||
# Restore
|
||||
asset.app_id = app_id
|
||||
asset.app_desc = app_desc
|
||||
|
||||
if check_attachments:
|
||||
# Test single attachment support; even if the service
|
||||
# doesn't support attachments, it should still
|
||||
# gracefully ignore the data
|
||||
attach = os.path.join(
|
||||
self.__test_var_dir, 'apprise-test.gif')
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type,
|
||||
attach=attach) == attach_response
|
||||
|
||||
# Same results should apply to a list of attachments
|
||||
attach = AppriseAttachment((
|
||||
os.path.join(self.__test_var_dir, 'apprise-test.gif'),
|
||||
os.path.join(self.__test_var_dir, 'apprise-test.png'),
|
||||
os.path.join(self.__test_var_dir, 'apprise-test.jpeg'),
|
||||
))
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=notify_type,
|
||||
attach=attach) == attach_response
|
||||
|
||||
else:
|
||||
# Disable throttling
|
||||
obj.request_rate_per_sec = 0
|
||||
|
||||
for _exception in self.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
|
||||
try:
|
||||
assert obj.notify(
|
||||
body=self.body, title=self.title,
|
||||
notify_type=NotifyType.INFO) is False
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
# We can't handle this exception type
|
||||
raise
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
# Check that we were expecting this exception to happen
|
||||
try:
|
||||
if not isinstance(e, response):
|
||||
raise e
|
||||
|
||||
except TypeError:
|
||||
print('%s Unhandled response %s' % (url, type(e)))
|
||||
raise e
|
||||
|
||||
#
|
||||
# Do the test again but without a title defined
|
||||
#
|
||||
try:
|
||||
if test_requests_exceptions is False:
|
||||
# check that we're as expected
|
||||
assert obj.notify(body='body', notify_type=notify_type) \
|
||||
== notify_response
|
||||
|
||||
else:
|
||||
for _exception in self.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
|
||||
try:
|
||||
assert obj.notify(
|
||||
body=self.body,
|
||||
notify_type=NotifyType.INFO) is False
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
# We can't handle this exception type
|
||||
raise
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
# Check that we were expecting this exception to happen
|
||||
if not isinstance(e, response):
|
||||
raise e
|
||||
|
||||
return True
|
297
test/test_api.py
297
test/test_api.py
@ -43,6 +43,7 @@ from apprise import NotifyImageSize
|
||||
from apprise import __version__
|
||||
from apprise import URLBase
|
||||
from apprise import PrivacyMode
|
||||
from apprise.AppriseLocale import LazyTranslation
|
||||
|
||||
from apprise.plugins import SCHEMA_MAP
|
||||
from apprise.plugins import __load_matrix
|
||||
@ -961,6 +962,143 @@ def test_apprise_asset(tmpdir):
|
||||
'http://localhost/default/info-256x256.test'
|
||||
|
||||
|
||||
def test_apprise_disabled_plugins():
|
||||
"""
|
||||
API: Apprise() Disabled Plugin States
|
||||
|
||||
"""
|
||||
# Reset our matrix
|
||||
__reset_matrix()
|
||||
|
||||
class TestDisabled01Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test a pre-disabled state
|
||||
"""
|
||||
|
||||
# Just flat out disable our service
|
||||
enabled = False
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'na01'
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def notify(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['na01'] = TestDisabled01Notification
|
||||
|
||||
class TestDisabled02Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test a post-disabled state
|
||||
"""
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'na02'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TestDisabled02Notification, self).__init__(**kwargs)
|
||||
|
||||
# enable state changes **AFTER** we initialize
|
||||
self.enabled = False
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def notify(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['na02'] = TestDisabled02Notification
|
||||
|
||||
# Create our Apprise instance
|
||||
a = Apprise()
|
||||
|
||||
result = a.details(lang='ca-en', show_disabled=True)
|
||||
assert isinstance(result, dict)
|
||||
assert 'schemas' in result
|
||||
assert len(result['schemas']) == 2
|
||||
|
||||
# our na01 is disabled right from the get-go
|
||||
entry = next((x for x in result['schemas']
|
||||
if x['service_name'] == 'na01'), None)
|
||||
assert entry is not None
|
||||
assert entry['enabled'] is False
|
||||
|
||||
plugin = a.instantiate('na01://localhost')
|
||||
# Object is just flat out disabled... nothing is instatiated
|
||||
assert plugin is None
|
||||
|
||||
# our na02 isn't however until it's initialized; as a result
|
||||
# it get's returned in our result set
|
||||
entry = next((x for x in result['schemas']
|
||||
if x['service_name'] == 'na02'), None)
|
||||
assert entry is not None
|
||||
assert entry['enabled'] is True
|
||||
|
||||
plugin = a.instantiate('na02://localhost')
|
||||
# Object isn't disabled until the __init__() call. But this is still
|
||||
# enough to not instantiate the object:
|
||||
assert plugin is None
|
||||
|
||||
# If we choose to filter our disabled, we can't unfortunately filter those
|
||||
# that go disabled after instantiation, but we do filter out any that are
|
||||
# already known to not be enabled:
|
||||
result = a.details(lang='ca-en', show_disabled=False)
|
||||
assert isinstance(result, dict)
|
||||
assert 'schemas' in result
|
||||
assert len(result['schemas']) == 1
|
||||
|
||||
# We'll add a good notification to our list
|
||||
class TesEnabled01Notification(NotifyBase):
|
||||
"""
|
||||
This class is just a simple enabled one
|
||||
"""
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'good'
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['good'] = TesEnabled01Notification
|
||||
|
||||
# The last thing we'll simulate is a case where the plugin is just
|
||||
# disabled at a later time long into it's life. this is just to allow
|
||||
# administrators to stop the flow of their notifications for their own
|
||||
# given reasons.
|
||||
plugin = a.instantiate('good://localhost')
|
||||
assert isinstance(plugin, NotifyBase)
|
||||
|
||||
# we'll toggle our state
|
||||
plugin.enabled = False
|
||||
|
||||
# As a result, we can now no longer send a notification:
|
||||
assert plugin.notify("My Message") is False
|
||||
|
||||
# As just a proof of how you can toggle the state back:
|
||||
plugin.enabled = True
|
||||
|
||||
# our notifications will go okay now
|
||||
assert plugin.notify("My Message") is True
|
||||
|
||||
# Reset our matrix
|
||||
__reset_matrix()
|
||||
__load_matrix()
|
||||
|
||||
|
||||
def test_apprise_details():
|
||||
"""
|
||||
API: Apprise() Details
|
||||
@ -1070,18 +1208,170 @@ def test_apprise_details():
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def notify(self, **kwargs):
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
# Store our good detail notification in our schema map
|
||||
SCHEMA_MAP['details'] = TestDetailNotification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq01Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
'packages_required': [
|
||||
'cryptography <= 3.4',
|
||||
'ultrasync',
|
||||
],
|
||||
'packages_recommended': 'django',
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req01'] = TestReq01Notification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq02Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Just not enabled at all
|
||||
enabled = False
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
# None and/or [] is implied, but jsut to show that the code won't
|
||||
# crash if explicitly set this way:
|
||||
'packages_required': None,
|
||||
|
||||
'packages_recommended': [
|
||||
'cryptography <= 3.4',
|
||||
]
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req02'] = TestReq02Notification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq03Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
# We can over-ride the default details assigned to our plugin if
|
||||
# specified
|
||||
'details': _('some specified requirement details'),
|
||||
|
||||
# We can set a string value as well (it does not have to be a list)
|
||||
'packages_recommended': 'cryptography <= 3.4'
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req03'] = TestReq03Notification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq04Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test a case where our requirements is fixed
|
||||
to a None
|
||||
"""
|
||||
|
||||
# This is the same as saying there are no requirements
|
||||
requirements = None
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req04'] = TestReq04Notification
|
||||
|
||||
# Create our Apprise instance
|
||||
a = Apprise()
|
||||
|
||||
# Dictionary response
|
||||
assert isinstance(a.details(), dict)
|
||||
result = a.details()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
# Test different variations of our call
|
||||
result = a.details(lang='ca-fr')
|
||||
assert isinstance(result, dict)
|
||||
for entry in result['schemas']:
|
||||
# Verify our key does not exist because we did not ask for it
|
||||
assert 'enabled' not in entry
|
||||
assert 'requirements' not in entry
|
||||
|
||||
result = a.details(lang='us-en', show_requirements=True)
|
||||
assert isinstance(result, dict)
|
||||
for entry in result['schemas']:
|
||||
# Verify our key does not exist because we did not ask for it
|
||||
assert 'enabled' not in entry
|
||||
|
||||
# Requirements are set for display
|
||||
assert 'requirements' in entry
|
||||
assert 'details' in entry['requirements']
|
||||
assert 'packages_required' in entry['requirements']
|
||||
assert 'packages_recommended' in entry['requirements']
|
||||
assert isinstance(entry['requirements']['details'], six.string_types)
|
||||
assert isinstance(entry['requirements']['packages_required'], list)
|
||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||
|
||||
result = a.details(lang='ca-en', show_disabled=True)
|
||||
assert isinstance(result, dict)
|
||||
for entry in result['schemas']:
|
||||
# Verify that our plugin state is available to us
|
||||
assert 'enabled' in entry
|
||||
assert isinstance(entry['enabled'], bool)
|
||||
|
||||
# Verify our key does not exist because we did not ask for it
|
||||
assert 'requirements' not in entry
|
||||
|
||||
result = a.details(
|
||||
lang='ca-fr', show_requirements=True, show_disabled=True)
|
||||
assert isinstance(result, dict)
|
||||
for entry in result['schemas']:
|
||||
# Plugin States are set for display
|
||||
assert 'enabled' in entry
|
||||
assert isinstance(entry['enabled'], bool)
|
||||
|
||||
# Requirements are set for display
|
||||
assert 'requirements' in entry
|
||||
assert 'details' in entry['requirements']
|
||||
assert 'packages_required' in entry['requirements']
|
||||
assert 'packages_recommended' in entry['requirements']
|
||||
assert isinstance(entry['requirements']['details'], six.string_types)
|
||||
assert isinstance(entry['requirements']['packages_required'], list)
|
||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||
|
||||
# Reset our matrix
|
||||
__reset_matrix()
|
||||
@ -1173,7 +1463,8 @@ def test_apprise_details_plugin_verification():
|
||||
|
||||
# A Service Name MUST be defined
|
||||
assert 'service_name' in entry
|
||||
assert isinstance(entry['service_name'], six.string_types)
|
||||
assert isinstance(
|
||||
entry['service_name'], (six.string_types, LazyTranslation))
|
||||
|
||||
# Acquire our protocols
|
||||
protocols = parse_list(
|
||||
|
191
test/test_cli.py
191
test/test_cli.py
@ -34,6 +34,8 @@ from apprise import NotifyBase
|
||||
from click.testing import CliRunner
|
||||
from apprise.plugins import SCHEMA_MAP
|
||||
from apprise.utils import environ
|
||||
from apprise.plugins import __load_matrix
|
||||
from apprise.plugins import __reset_matrix
|
||||
|
||||
|
||||
try:
|
||||
@ -594,6 +596,195 @@ def test_apprise_cli_nux_env(tmpdir):
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_apprise_cli_details(tmpdir):
|
||||
"""
|
||||
API: Apprise() Disabled Plugin States
|
||||
|
||||
"""
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
#
|
||||
# Testing the printout of our details
|
||||
# --details or -l
|
||||
#
|
||||
result = runner.invoke(cli.main, [
|
||||
'--details',
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
result = runner.invoke(cli.main, [
|
||||
'-l',
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Reset our matrix
|
||||
__reset_matrix()
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq01Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
'packages_required': [
|
||||
'cryptography <= 3.4',
|
||||
'ultrasync',
|
||||
],
|
||||
'packages_recommended': 'django',
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req01'] = TestReq01Notification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq02Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Just not enabled at all
|
||||
enabled = False
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
# None and/or [] is implied, but jsut to show that the code won't
|
||||
# crash if explicitly set this way:
|
||||
'packages_required': None,
|
||||
|
||||
'packages_recommended': [
|
||||
'cryptography <= 3.4',
|
||||
]
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req02'] = TestReq02Notification
|
||||
|
||||
# This is a made up class that is just used to verify
|
||||
class TestReq03Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test various requirement configurations
|
||||
"""
|
||||
|
||||
# Set some requirements
|
||||
requirements = {
|
||||
# We can over-ride the default details assigned to our plugin if
|
||||
# specified
|
||||
'details': _('some specified requirement details'),
|
||||
|
||||
# We can set a string value as well (it does not have to be a list)
|
||||
'packages_recommended': 'cryptography <= 3.4'
|
||||
}
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['req03'] = TestReq03Notification
|
||||
|
||||
class TestDisabled01Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test a pre-disabled state
|
||||
"""
|
||||
|
||||
# Just flat out disable our service
|
||||
enabled = False
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'na01'
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def notify(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['na01'] = TestDisabled01Notification
|
||||
|
||||
class TestDisabled02Notification(NotifyBase):
|
||||
"""
|
||||
This class is used to test a post-disabled state
|
||||
"""
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'na02'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TestDisabled02Notification, self).__init__(**kwargs)
|
||||
|
||||
# enable state changes **AFTER** we initialize
|
||||
self.enabled = False
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def notify(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['na02'] = TestDisabled02Notification
|
||||
|
||||
# We'll add a good notification to our list
|
||||
class TesEnabled01Notification(NotifyBase):
|
||||
"""
|
||||
This class is just a simple enabled one
|
||||
"""
|
||||
|
||||
# we'll use this as a key to make our service easier to find
|
||||
# in the next part of the testing
|
||||
service_name = 'good'
|
||||
|
||||
def url(self, **kwargs):
|
||||
# Support URL
|
||||
return ''
|
||||
|
||||
def send(self, **kwargs):
|
||||
# Pretend everything is okay (so we don't break other tests)
|
||||
return True
|
||||
|
||||
SCHEMA_MAP['good'] = TesEnabled01Notification
|
||||
|
||||
# Verify that we can pass through all of our different details
|
||||
result = runner.invoke(cli.main, [
|
||||
'--details',
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
result = runner.invoke(cli.main, [
|
||||
'-l',
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Reset our matrix
|
||||
__reset_matrix()
|
||||
__load_matrix()
|
||||
|
||||
|
||||
@mock.patch('platform.system')
|
||||
def test_apprise_cli_windows_env(mock_system):
|
||||
"""
|
||||
|
@ -43,7 +43,7 @@ TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_json_plugin_attachments(mock_post):
|
||||
"""
|
||||
API: NotifyJSON() Attachments
|
||||
NotifyJSON() Attachments
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
|
@ -43,7 +43,7 @@ TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_xml_plugin_attachments(mock_post):
|
||||
"""
|
||||
API: NotifyXML() Attachments
|
||||
NotifyXML() Attachments
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
|
166
test/test_plugin_apprise_api.py
Normal file
166
test/test_plugin_apprise_api.py
Normal file
@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from helpers import AppriseURLTester
|
||||
from apprise import plugins
|
||||
import requests
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('apprise://', {
|
||||
# invalid url (not complete)
|
||||
'instance': None,
|
||||
}),
|
||||
# A a bad url
|
||||
('apprise://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# No token specified
|
||||
('apprise://localhost', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# invalid token
|
||||
('apprise://localhost/!', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No token specified (whitespace is trimmed)
|
||||
('apprise://localhost/%%20', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# A valid URL with Token
|
||||
('apprise://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprise://localhost/a...a/',
|
||||
}),
|
||||
# A valid URL with Token (using port)
|
||||
('apprise://localhost:8080/%s' % ('b' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprise://localhost:8080/b...b/',
|
||||
}),
|
||||
# A secure (https://) reference
|
||||
('apprises://localhost/%s' % ('c' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprises://localhost/c...c/',
|
||||
}),
|
||||
# Native URL suport (https)
|
||||
('https://example.com/path/notify/%s' % ('d' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprises://example.com/path/d...d/',
|
||||
}),
|
||||
# Native URL suport (http)
|
||||
('http://example.com/notify/%s' % ('d' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprise://example.com/d...d/',
|
||||
}),
|
||||
# support to= keyword
|
||||
('apprises://localhost/?to=%s' % ('e' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprises://localhost/e...e/',
|
||||
}),
|
||||
# support token= keyword (even when passed with to=, token over-rides)
|
||||
('apprise://localhost/?token=%s&to=%s' % ('f' * 32, 'abcd'), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprise://localhost/f...f/',
|
||||
}),
|
||||
# Test tags
|
||||
('apprise://localhost/?token=%s&tags=admin,team' % ('abcd'), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprise://localhost/a...d/',
|
||||
}),
|
||||
# Test Format string
|
||||
('apprise://user@localhost/mytoken0/?format=markdown', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprise://user@localhost/m...0/',
|
||||
}),
|
||||
('apprise://user@localhost/mytoken1/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprise://user@localhost/m...1/',
|
||||
}),
|
||||
('apprise://localhost:8080/mytoken/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
}),
|
||||
('apprise://user:pass@localhost:8080/mytoken2/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
'privacy_url': 'apprise://user:****@localhost:8080/m...2/',
|
||||
}),
|
||||
('apprises://localhost/mytoken/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
}),
|
||||
('apprises://user:pass@localhost/mytoken3/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprises://user:****@localhost/m...3/',
|
||||
}),
|
||||
('apprises://localhost:8080/mytoken4/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprises://localhost:8080/m...4/',
|
||||
}),
|
||||
('apprises://user:password@localhost:8080/mytoken5/', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'apprises://user:****@localhost:8080/m...5/',
|
||||
}),
|
||||
('apprises://localhost:8080/path?-HeaderKey=HeaderValue', {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
}),
|
||||
('apprise://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('apprise://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('apprise://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyAppriseAPI,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_apprise_urls():
|
||||
"""
|
||||
NotifyAppriseAPI() General Checks
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
168
test/test_plugin_boxcar.py
Normal file
168
test/test_plugin_boxcar.py
Normal file
@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import mock
|
||||
from helpers import AppriseURLTester
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
import requests
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('boxcar://', {
|
||||
# invalid secret key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# A a bad url
|
||||
('boxcar://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No secret specified
|
||||
('boxcar://%s' % ('a' * 64), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No access specified (whitespace is trimmed)
|
||||
('boxcar://%%20/%s' % ('a' * 64), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No secret specified (whitespace is trimmed)
|
||||
('boxcar://%s/%%20' % ('a' * 64), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provide both an access and a secret
|
||||
('boxcar://%s/%s' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
'requests_response_code': requests.codes.created,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'boxcar://a...a/****/',
|
||||
}),
|
||||
# Test without image set
|
||||
('boxcar://%s/%s?image=True' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
'requests_response_code': requests.codes.created,
|
||||
# don't include an image in Asset by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('boxcar://%s/%s?image=False' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
'requests_response_code': requests.codes.created,
|
||||
}),
|
||||
# our access, secret and device are all 64 characters
|
||||
# which is what we're doing here
|
||||
('boxcar://%s/%s/@tag1/tag2///%s/?to=tag3' % (
|
||||
'a' * 64, 'b' * 64, 'd' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
'requests_response_code': requests.codes.created,
|
||||
}),
|
||||
# An invalid tag
|
||||
('boxcar://%s/%s/@%s' % ('a' * 64, 'b' * 64, 't' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
'requests_response_code': requests.codes.created,
|
||||
}),
|
||||
('boxcar://%s/%s/' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('boxcar://%s/%s/' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('boxcar://%s/%s/' % ('a' * 64, 'b' * 64), {
|
||||
'instance': plugins.NotifyBoxcar,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_boxcar_urls():
|
||||
"""
|
||||
NotifyBoxcar() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_boxcar_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyBoxcar() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Generate some generic message types
|
||||
device = 'A' * 64
|
||||
tag = '@B' * 63
|
||||
|
||||
access = '-' * 64
|
||||
secret = '_' * 64
|
||||
|
||||
# Initializes the plugin with recipients set to None
|
||||
plugins.NotifyBoxcar(access=access, secret=secret, targets=None)
|
||||
|
||||
# Initializes the plugin with a valid access, but invalid access key
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyBoxcar(access=None, secret=secret, targets=None)
|
||||
|
||||
# Initializes the plugin with a valid access, but invalid secret
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyBoxcar(access=access, secret=None, targets=None)
|
||||
|
||||
# Initializes the plugin with recipients list
|
||||
# the below also tests our the variation of recipient types
|
||||
plugins.NotifyBoxcar(
|
||||
access=access, secret=secret, targets=[device, tag])
|
||||
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.created
|
||||
mock_get.return_value.status_code = requests.codes.created
|
||||
|
||||
# Test notifications without a body or a title
|
||||
p = plugins.NotifyBoxcar(access=access, secret=secret, targets=None)
|
||||
|
||||
assert p.notify(body=None, title=None, notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Test comma, separate values
|
||||
device = 'a' * 64
|
||||
|
||||
p = plugins.NotifyBoxcar(
|
||||
access=access, secret=secret,
|
||||
targets=','.join([device, device, device]))
|
||||
assert len(p.device_tokens) == 3
|
83
test/test_plugin_clicksend.py
Normal file
83
test/test_plugin_clicksend.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('clicksend://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('clicksend://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('clicksend://user:pass@{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||
# invalid target numbers; we'll fail to notify anyone
|
||||
'instance': plugins.NotifyClickSend,
|
||||
'notify_response': False,
|
||||
}),
|
||||
('clicksend://user:pass@{}?batch=yes'.format('3' * 14), {
|
||||
# valid number
|
||||
'instance': plugins.NotifyClickSend,
|
||||
}),
|
||||
('clicksend://user:pass@{}?batch=yes&to={}'.format('3' * 14, '6' * 14), {
|
||||
# valid number but using the to= variable
|
||||
'instance': plugins.NotifyClickSend,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'clicksend://user:****',
|
||||
}),
|
||||
('clicksend://user:pass@{}?batch=no'.format('3' * 14), {
|
||||
# valid number - no batch
|
||||
'instance': plugins.NotifyClickSend,
|
||||
}),
|
||||
('clicksend://user:pass@{}'.format('3' * 14), {
|
||||
'instance': plugins.NotifyClickSend,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('clicksend://user:pass@{}'.format('3' * 14), {
|
||||
'instance': plugins.NotifyClickSend,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_clicksend_urls():
|
||||
"""
|
||||
NotifyClickSend() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
110
test/test_plugin_custom_json.py
Normal file
110
test/test_plugin_custom_json.py
Normal file
@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('json://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('json://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('jsons://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('json://localhost', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('json://user:pass@localhost', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'json://user:****@localhost',
|
||||
}),
|
||||
('json://user@localhost', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('json://localhost:8080', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('json://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('jsons://localhost', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('jsons://user:pass@localhost', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('jsons://localhost:8080/path/', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'jsons://localhost:8080/path/',
|
||||
}),
|
||||
('jsons://user:password@localhost:8080', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'jsons://user:****@localhost:8080',
|
||||
}),
|
||||
('json://localhost:8080/path?-HeaderKey=HeaderValue', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
}),
|
||||
('json://user:pass@localhost:8081', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('json://user:pass@localhost:8082', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('json://user:pass@localhost:8083', {
|
||||
'instance': plugins.NotifyJSON,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_custom_json_urls():
|
||||
"""
|
||||
NotifyJSON() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
110
test/test_plugin_custom_xml.py
Normal file
110
test/test_plugin_custom_xml.py
Normal file
@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('xml://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('xml://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('xmls://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('xml://localhost', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xml://user@localhost', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xml://user:pass@localhost', {
|
||||
'instance': plugins.NotifyXML,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'xml://user:****@localhost',
|
||||
}),
|
||||
('xml://localhost:8080', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xml://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xmls://localhost', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xmls://user:pass@localhost', {
|
||||
'instance': plugins.NotifyXML,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'xmls://user:****@localhost',
|
||||
}),
|
||||
('xmls://localhost:8080/path/', {
|
||||
'instance': plugins.NotifyXML,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'xmls://localhost:8080/path/',
|
||||
}),
|
||||
('xmls://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xml://localhost:8080/path?-HeaderKey=HeaderValue', {
|
||||
'instance': plugins.NotifyXML,
|
||||
}),
|
||||
('xml://user:pass@localhost:8081', {
|
||||
'instance': plugins.NotifyXML,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('xml://user:pass@localhost:8082', {
|
||||
'instance': plugins.NotifyXML,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('xml://user:pass@localhost:8083', {
|
||||
'instance': plugins.NotifyXML,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_custom_xml_urls():
|
||||
"""
|
||||
NotifyXML() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
117
test/test_plugin_d7networks.py
Normal file
117
test/test_plugin_d7networks.py
Normal file
@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('d7sms://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('d7sms://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('d7sms://user:pass@{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||
# No valid targets to notify
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes'.format('3' * 14), {
|
||||
# valid number
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'd7sms://user:****@',
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes'.format('7' * 14), {
|
||||
# valid number
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'data': {
|
||||
'messageCount': 0,
|
||||
},
|
||||
},
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes&to={}'.format('3' * 14, '6' * 14), {
|
||||
# valid number
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes&from=apprise'.format('3' * 14), {
|
||||
# valid number, utilizing the optional from= variable
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes&source=apprise'.format('3' * 14), {
|
||||
# valid number, utilizing the optional source= variable (same as from)
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?priority=invalid'.format('3' * 14), {
|
||||
# valid number; invalid priority
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?priority=3'.format('3' * 14), {
|
||||
# valid number; adjusted priority
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?priority=high'.format('3' * 14), {
|
||||
# valid number; adjusted priority (string supported)
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=no'.format('3' * 14), {
|
||||
# valid number - no batch
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
}),
|
||||
('d7sms://user:pass@{}'.format('3' * 14), {
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('d7sms://user:pass@{}'.format('3' * 14), {
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_d7networks_urls():
|
||||
"""
|
||||
NotifyD7Networks() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
105
test/test_plugin_dingtalk.py
Normal file
105
test/test_plugin_dingtalk.py
Normal file
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('dingtalk://', {
|
||||
# No Access Token specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('dingtalk://a_bd_/', {
|
||||
# invalid Access Token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('dingtalk://12345678', {
|
||||
# access token
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'dingtalk://1...8',
|
||||
}),
|
||||
('dingtalk://{}/{}'.format('a' * 8, '1' * 14), {
|
||||
# access token + phone number
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
}),
|
||||
('dingtalk://{}/{}/invalid'.format('a' * 8, '1' * 3), {
|
||||
# access token + 2 invalid phone numbers
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
}),
|
||||
('dingtalk://{}/?to={}'.format('a' * 8, '1' * 14), {
|
||||
# access token + phone number using 'to'
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
}),
|
||||
# Test secret via user@
|
||||
('dingtalk://secret@{}/?to={}'.format('a' * 8, '1' * 14), {
|
||||
# access token + phone number using 'to'
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'dingtalk://****@a...a',
|
||||
}),
|
||||
# Test secret via secret= and token=
|
||||
('dingtalk://?token={}&to={}&secret={}'.format(
|
||||
'b' * 8, '1' * 14, 'a' * 15), {
|
||||
# access token + phone number using 'to'
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
'privacy_url': 'dingtalk://****@b...b',
|
||||
}),
|
||||
# Invalid secret
|
||||
('dingtalk://{}/?to={}&secret=_'.format('a' * 8, '1' * 14), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('dingtalk://{}?format=markdown'.format('a' * 8), {
|
||||
# access token
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
}),
|
||||
('dingtalk://{}'.format('a' * 8), {
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('dingtalk://{}'.format('a' * 8), {
|
||||
'instance': plugins.NotifyDingTalk,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_dingtalk_urls():
|
||||
"""
|
||||
NotifyDingTalk() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -28,6 +28,7 @@ import six
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import plugins
|
||||
@ -41,11 +42,129 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('discord://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# An invalid url
|
||||
('discord://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No webhook_token specified
|
||||
('discord://%s' % ('i' * 24), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provide both an webhook id and a webhook token
|
||||
('discord://%s/%s' % ('i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
# Provide a temporary username
|
||||
('discord://l2g@%s/%s' % ('i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
# test image= field
|
||||
('discord://%s/%s?format=markdown&footer=Yes&image=Yes' % (
|
||||
'i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('discord://%s/%s?format=markdown&footer=Yes&image=No&fields=no' % (
|
||||
'i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('discord://%s/%s?format=markdown&footer=Yes&image=Yes' % (
|
||||
'i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('https://discord.com/api/webhooks/{}/{}'.format(
|
||||
'0' * 10, 'B' * 40), {
|
||||
# Native URL Support, support the provided discord URL from their
|
||||
# webpage.
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('https://discordapp.com/api/webhooks/{}/{}'.format(
|
||||
'0' * 10, 'B' * 40), {
|
||||
# Legacy Native URL Support, support the older URL (to be
|
||||
# decomissioned on Nov 7th 2020)
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('https://discordapp.com/api/webhooks/{}/{}?footer=yes'.format(
|
||||
'0' * 10, 'B' * 40), {
|
||||
# Native URL Support with arguments
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('discord://%s/%s?format=markdown&avatar=No&footer=No' % (
|
||||
'i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
# different format support
|
||||
('discord://%s/%s?format=markdown' % ('i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
('discord://%s/%s?format=text' % ('i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
# Test with avatar URL
|
||||
('discord://%s/%s?avatar_url=http://localhost/test.jpg' % (
|
||||
'i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
}),
|
||||
# Test without image set
|
||||
('discord://%s/%s' % ('i' * 24, 't' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
'requests_response_code': requests.codes.no_content,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('discord://%s/%s/' % ('a' * 24, 'b' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('discord://%s/%s/' % ('a' * 24, 'b' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('discord://%s/%s/' % ('a' * 24, 'b' * 64), {
|
||||
'instance': plugins.NotifyDiscord,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_discord_urls():
|
||||
"""
|
||||
NotifyDiscord() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_discord_plugin(mock_post):
|
||||
def test_plugin_discord_general(mock_post):
|
||||
"""
|
||||
API: NotifyDiscord() General Checks
|
||||
NotifyDiscord() General Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -239,9 +358,9 @@ def test_discord_plugin(mock_post):
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_discord_attachments(mock_post):
|
||||
def test_plugin_discord_attachments(mock_post):
|
||||
"""
|
||||
API: NotifyDiscord() Attachment Checks
|
||||
NotifyDiscord() Attachment Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
@ -243,9 +243,9 @@ TEST_URLS = (
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
@mock.patch('smtplib.SMTP_SSL')
|
||||
def test_email_plugin(mock_smtp, mock_smtpssl):
|
||||
def test_plugin_email(mock_smtp, mock_smtpssl):
|
||||
"""
|
||||
API: NotifyEmail Plugin()
|
||||
NotifyEmail() General Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -403,9 +403,9 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
@mock.patch('smtplib.SMTP_SSL')
|
||||
def test_webbase_lookup(mock_smtp, mock_smtpssl):
|
||||
def test_plugin_email_webbase_lookup(mock_smtp, mock_smtpssl):
|
||||
"""
|
||||
API: Web Based Lookup Tests
|
||||
NotifyEmail() Web Based Lookup Tests
|
||||
|
||||
"""
|
||||
|
||||
@ -445,9 +445,9 @@ def test_webbase_lookup(mock_smtp, mock_smtpssl):
|
||||
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_smtplib_init_fail(mock_smtplib):
|
||||
def test_plugin_email_smtplib_init_fail(mock_smtplib):
|
||||
"""
|
||||
API: Test exception handling when calling smtplib.SMTP()
|
||||
NotifyEmail() Test exception handling when calling smtplib.SMTP()
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -470,9 +470,9 @@ def test_smtplib_init_fail(mock_smtplib):
|
||||
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_smtplib_send_okay(mock_smtplib):
|
||||
def test_plugin_email_smtplib_send_okay(mock_smtplib):
|
||||
"""
|
||||
API: Test a successfully sent email
|
||||
NotifyEmail() Test a successfully sent email
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -538,9 +538,9 @@ def test_smtplib_send_okay(mock_smtplib):
|
||||
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_smtplib_internationalization(mock_smtp):
|
||||
def test_plugin_email_smtplib_internationalization(mock_smtp):
|
||||
"""
|
||||
API: Test email handling using internationalization
|
||||
NotifyEmail() Internationalization Handling
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -610,9 +610,9 @@ def test_smtplib_internationalization(mock_smtp):
|
||||
notify_type=NotifyType.INFO) is True
|
||||
|
||||
|
||||
def test_email_url_escaping():
|
||||
def test_plugin_email_url_escaping():
|
||||
"""
|
||||
API: Test that user/passwords are properly escaped from URL
|
||||
NotifyEmail() Test that user/passwords are properly escaped from URL
|
||||
|
||||
"""
|
||||
# quote(' %20')
|
||||
@ -641,9 +641,9 @@ def test_email_url_escaping():
|
||||
assert obj.password == ' %20'
|
||||
|
||||
|
||||
def test_email_url_variations():
|
||||
def test_plugin_email_url_variations():
|
||||
"""
|
||||
API: Test email variations to ensure parsing is correct
|
||||
NotifyEmail() Test URL variations to ensure parsing is correct
|
||||
|
||||
"""
|
||||
# Test variations of username required to be an email address
|
||||
@ -727,9 +727,9 @@ def test_email_url_variations():
|
||||
assert obj.from_addr == 'from@example.jp'
|
||||
|
||||
|
||||
def test_email_dict_variations():
|
||||
def test_plugin_email_dict_variations():
|
||||
"""
|
||||
API: Test email dictionary variations to ensure parsing is correct
|
||||
NotifyEmail() Test email dictionary variations to ensure parsing is correct
|
||||
|
||||
"""
|
||||
# Test variations of username required to be an email address
|
449
test/test_plugin_emby.py
Normal file
449
test/test_plugin_emby.py
Normal file
@ -0,0 +1,449 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
from json import dumps
|
||||
from apprise import Apprise
|
||||
from apprise import plugins
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
# Insecure Request; no hostname specified
|
||||
('emby://', {
|
||||
'instance': None,
|
||||
}),
|
||||
# Secure Emby Request; no hostname specified
|
||||
('embys://', {
|
||||
'instance': None,
|
||||
}),
|
||||
# No user specified
|
||||
('emby://localhost', {
|
||||
# Missing a username
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('emby://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# Valid Authentication
|
||||
('emby://l2g@localhost', {
|
||||
'instance': plugins.NotifyEmby,
|
||||
# our response will be False because our authentication can't be
|
||||
# tested very well using this matrix.
|
||||
'response': False,
|
||||
}),
|
||||
('embys://l2g:password@localhost', {
|
||||
'instance': plugins.NotifyEmby,
|
||||
# our response will be False because our authentication can't be
|
||||
# tested very well using this matrix.
|
||||
'response': False,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'embys://l2g:****@localhost',
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_template_urls():
|
||||
"""
|
||||
NotifyTemplate() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('apprise.plugins.NotifyEmby.sessions')
|
||||
@mock.patch('apprise.plugins.NotifyEmby.login')
|
||||
@mock.patch('apprise.plugins.NotifyEmby.logout')
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_emby_general(mock_post, mock_get, mock_logout,
|
||||
mock_login, mock_sessions):
|
||||
"""
|
||||
NotifyEmby General Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
req = requests.Request()
|
||||
req.status_code = requests.codes.ok
|
||||
req.content = ''
|
||||
mock_get.return_value = req
|
||||
mock_post.return_value = req
|
||||
|
||||
# This is done so we don't obstruct our access_token and user_id values
|
||||
mock_login.return_value = True
|
||||
mock_logout.return_value = True
|
||||
mock_sessions.return_value = {'abcd': {}}
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost?modal=False')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
assert obj.notify('title', 'body', 'info') is True
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Test Modal support
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost?modal=True')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
assert obj.notify('title', 'body', 'info') is True
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Test our exception handling
|
||||
for _exception in AppriseURLTester.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
# We'll fail to log in each time
|
||||
assert obj.notify('title', 'body', 'info') is False
|
||||
|
||||
# Disable Exceptions
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
# Our login flat out fails if we don't have proper parseable content
|
||||
mock_post.return_value.content = u''
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# KeyError handling
|
||||
mock_post.return_value.status_code = 999
|
||||
mock_get.return_value.status_code = 999
|
||||
assert obj.notify('title', 'body', 'info') is False
|
||||
|
||||
# General Internal Server Error
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||
assert obj.notify('title', 'body', 'info') is False
|
||||
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# Disable the port completely
|
||||
obj.port = None
|
||||
assert obj.notify('title', 'body', 'info') is True
|
||||
|
||||
# An Empty return set (no query is made, but notification will still
|
||||
# succeed
|
||||
mock_sessions.return_value = {}
|
||||
assert obj.notify('title', 'body', 'info') is True
|
||||
|
||||
# Tidy our object
|
||||
del obj
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_emby_login(mock_post, mock_get):
|
||||
"""
|
||||
NotifyEmby() login()
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
|
||||
# Test our exception handling
|
||||
for _exception in AppriseURLTester.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
# We'll fail to log in each time
|
||||
assert obj.login() is False
|
||||
|
||||
# Disable Exceptions
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
# Our login flat out fails if we don't have proper parseable content
|
||||
mock_post.return_value.content = u''
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# KeyError handling
|
||||
mock_post.return_value.status_code = 999
|
||||
mock_get.return_value.status_code = 999
|
||||
assert obj.login() is False
|
||||
|
||||
# General Internal Server Error
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||
assert obj.login() is False
|
||||
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost:1234')
|
||||
# Set a different port (outside of default)
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
assert obj.port == 1234
|
||||
|
||||
# The login will fail because '' is not a parseable JSON response
|
||||
assert obj.login() is False
|
||||
|
||||
# Disable the port completely
|
||||
obj.port = None
|
||||
assert obj.login() is False
|
||||
|
||||
# Default port assignments
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
assert obj.port == 8096
|
||||
|
||||
# The login will (still) fail because '' is not a parseable JSON response
|
||||
assert obj.login() is False
|
||||
|
||||
# Our login flat out fails if we don't have proper parseable content
|
||||
mock_post.return_value.content = dumps({
|
||||
u'AccessToken': u'0000-0000-0000-0000',
|
||||
})
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
|
||||
# The login will fail because the 'User' or 'Id' field wasn't parsed
|
||||
assert obj.login() is False
|
||||
|
||||
# Our text content (we intentionally reverse the 2 locations
|
||||
# that store the same thing; we do this so we can test which
|
||||
# one it defaults to if both are present
|
||||
mock_post.return_value.content = dumps({
|
||||
u'User': {
|
||||
u'Id': u'abcd123',
|
||||
},
|
||||
u'Id': u'123abc',
|
||||
u'AccessToken': u'0000-0000-0000-0000',
|
||||
})
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
|
||||
# Login
|
||||
assert obj.login() is True
|
||||
assert obj.user_id == '123abc'
|
||||
assert obj.access_token == '0000-0000-0000-0000'
|
||||
|
||||
# We're going to log in a second time which checks that we logout
|
||||
# first before logging in again. But this time we'll scrap the
|
||||
# 'Id' area and use the one found in the User area if detected
|
||||
mock_post.return_value.content = dumps({
|
||||
u'User': {
|
||||
u'Id': u'abcd123',
|
||||
},
|
||||
u'AccessToken': u'0000-0000-0000-0000',
|
||||
})
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# Login
|
||||
assert obj.login() is True
|
||||
assert obj.user_id == 'abcd123'
|
||||
assert obj.access_token == '0000-0000-0000-0000'
|
||||
|
||||
|
||||
@mock.patch('apprise.plugins.NotifyEmby.login')
|
||||
@mock.patch('apprise.plugins.NotifyEmby.logout')
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_emby_sessions(mock_post, mock_get, mock_logout, mock_login):
|
||||
"""
|
||||
NotifyEmby() sessions()
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
|
||||
# This is done so we don't obstruct our access_token and user_id values
|
||||
mock_login.return_value = True
|
||||
mock_logout.return_value = True
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Test our exception handling
|
||||
for _exception in AppriseURLTester.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
# We'll fail to log in each time
|
||||
sessions = obj.sessions()
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 0
|
||||
|
||||
# Disable Exceptions
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
# Our login flat out fails if we don't have proper parseable content
|
||||
mock_post.return_value.content = u''
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# KeyError handling
|
||||
mock_post.return_value.status_code = 999
|
||||
mock_get.return_value.status_code = 999
|
||||
sessions = obj.sessions()
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 0
|
||||
|
||||
# General Internal Server Error
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||
sessions = obj.sessions()
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 0
|
||||
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# Disable the port completely
|
||||
obj.port = None
|
||||
|
||||
sessions = obj.sessions()
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 0
|
||||
|
||||
# Let's get some results
|
||||
mock_post.return_value.content = dumps([
|
||||
{
|
||||
u'Id': u'abc123',
|
||||
},
|
||||
{
|
||||
u'Id': u'def456',
|
||||
},
|
||||
{
|
||||
u'InvalidEntry': None,
|
||||
},
|
||||
])
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
sessions = obj.sessions(user_controlled=True)
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 2
|
||||
|
||||
# Test it without setting user-controlled sessions
|
||||
sessions = obj.sessions(user_controlled=False)
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 2
|
||||
|
||||
# Triggers an authentication failure
|
||||
obj.user_id = None
|
||||
mock_login.return_value = False
|
||||
sessions = obj.sessions()
|
||||
assert isinstance(sessions, dict) is True
|
||||
assert len(sessions) == 0
|
||||
|
||||
|
||||
@mock.patch('apprise.plugins.NotifyEmby.login')
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_emby_logout(mock_post, mock_get, mock_login):
|
||||
"""
|
||||
NotifyEmby() logout()
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
|
||||
# This is done so we don't obstruct our access_token and user_id values
|
||||
mock_login.return_value = True
|
||||
|
||||
obj = Apprise.instantiate('emby://l2g:l2gpass@localhost')
|
||||
assert isinstance(obj, plugins.NotifyEmby)
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Test our exception handling
|
||||
for _exception in AppriseURLTester.req_exceptions:
|
||||
mock_post.side_effect = _exception
|
||||
mock_get.side_effect = _exception
|
||||
# We'll fail to log in each time
|
||||
obj.logout()
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Disable Exceptions
|
||||
mock_post.side_effect = None
|
||||
mock_get.side_effect = None
|
||||
|
||||
# Our login flat out fails if we don't have proper parseable content
|
||||
mock_post.return_value.content = u''
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# KeyError handling
|
||||
mock_post.return_value.status_code = 999
|
||||
mock_get.return_value.status_code = 999
|
||||
obj.logout()
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# General Internal Server Error
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||
obj.logout()
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.content = mock_post.return_value.content
|
||||
|
||||
# Disable the port completely
|
||||
obj.port = None
|
||||
|
||||
# Perform logout
|
||||
obj.logout()
|
||||
|
||||
# Calling logout on an object already logged out
|
||||
obj.logout()
|
||||
|
||||
# Test Python v3.5 LookupError Bug: https://bugs.python.org/issue29288
|
||||
mock_post.side_effect = LookupError()
|
||||
mock_get.side_effect = LookupError()
|
||||
obj.access_token = 'abc'
|
||||
obj.user_id = '123'
|
||||
|
||||
# Tidy object
|
||||
del obj
|
190
test/test_plugin_enigma2.py
Normal file
190
test/test_plugin_enigma2.py
Normal file
@ -0,0 +1,190 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('enigma2://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('enigma2://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('enigma2s://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('enigma2://localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
# This will fail because we're also expecting a server acknowledgement
|
||||
'notify_response': False,
|
||||
}),
|
||||
('enigma2://localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
# invalid JSON response
|
||||
'requests_response_text': '{',
|
||||
'notify_response': False,
|
||||
}),
|
||||
('enigma2://localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
# False is returned
|
||||
'requests_response_text': {
|
||||
'result': False
|
||||
},
|
||||
'notify_response': False,
|
||||
}),
|
||||
('enigma2://localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
# With the right content, this will succeed
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
}
|
||||
}),
|
||||
('enigma2://user@localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
}
|
||||
}),
|
||||
# Set timeout
|
||||
('enigma2://user@localhost?timeout=-1', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
}
|
||||
}),
|
||||
# Set timeout
|
||||
('enigma2://user@localhost?timeout=-1000', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
}
|
||||
}),
|
||||
# Set invalid timeout (defaults to a set value)
|
||||
('enigma2://user@localhost?timeout=invalid', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
}
|
||||
}),
|
||||
('enigma2://user:pass@localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'enigma2://user:****@localhost',
|
||||
}),
|
||||
('enigma2://localhost:8080', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
}),
|
||||
('enigma2://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
}),
|
||||
('enigma2s://localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
}),
|
||||
('enigma2s://user:pass@localhost', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'enigma2s://user:****@localhost',
|
||||
}),
|
||||
('enigma2s://localhost:8080/path/', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'enigma2s://localhost:8080/path/',
|
||||
}),
|
||||
('enigma2s://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
}),
|
||||
('enigma2://localhost:8080/path?-HeaderKey=HeaderValue', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
}),
|
||||
('enigma2://user:pass@localhost:8081', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('enigma2://user:pass@localhost:8082', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('enigma2://user:pass@localhost:8083', {
|
||||
'instance': plugins.NotifyEnigma2,
|
||||
'requests_response_text': {
|
||||
'result': True
|
||||
},
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_enigma2_urls():
|
||||
"""
|
||||
NotifyEnigma2() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
81
test/test_plugin_faast.py
Normal file
81
test/test_plugin_faast.py
Normal file
@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('faast://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('faast://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Auth Token specified
|
||||
('faast://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyFaast,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'faast://a...a',
|
||||
}),
|
||||
('faast://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyFaast,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('faast://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyFaast,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('faast://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyFaast,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('faast://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyFaast,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_faast_urls():
|
||||
"""
|
||||
NotifyFaast() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -24,13 +24,22 @@
|
||||
# THE SOFTWARE.
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
import json
|
||||
from apprise import Apprise
|
||||
from apprise import plugins
|
||||
from apprise.plugins.NotifyFCM.oauth import GoogleOAuth
|
||||
from cryptography.exceptions import UnsupportedAlgorithm
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
try:
|
||||
from apprise.plugins.NotifyFCM.oauth import GoogleOAuth
|
||||
from cryptography.exceptions import UnsupportedAlgorithm
|
||||
|
||||
except ImportError:
|
||||
# No problem; there is no cryptography support
|
||||
pass
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
@ -39,16 +48,140 @@ except ImportError:
|
||||
# Python v2.7 Backwards Compatibility support
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Test files for KeyFile Directory
|
||||
PRIVATE_KEYFILE_DIR = os.path.join(os.path.dirname(__file__), 'var', 'fcm')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('fcm://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://project@%20%20/', {
|
||||
# invalid apikey
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://apikey/', {
|
||||
# no project id specified so we operate in legacy mode
|
||||
'instance': plugins.NotifyFCM,
|
||||
# but there are no targets specified so we return False
|
||||
'notify_response': False,
|
||||
}),
|
||||
('fcm://apikey/device', {
|
||||
# Valid device
|
||||
'instance': plugins.NotifyFCM,
|
||||
'privacy_url': 'fcm://a...y/device',
|
||||
}),
|
||||
('fcm://apikey/#topic', {
|
||||
# Valid topic
|
||||
'instance': plugins.NotifyFCM,
|
||||
'privacy_url': 'fcm://a...y/%23topic',
|
||||
}),
|
||||
('fcm://apikey/device?mode=invalid', {
|
||||
# Valid device, invalid mode
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://apikey/#topic1/device/%20/', {
|
||||
# Valid topic, valid device, and invalid entry
|
||||
'instance': plugins.NotifyFCM,
|
||||
}),
|
||||
('fcm://apikey?to=#topic1,device', {
|
||||
# Test to=
|
||||
'instance': plugins.NotifyFCM,
|
||||
}),
|
||||
('fcm://?apikey=abc123&to=device', {
|
||||
# Test apikey= to=
|
||||
'instance': plugins.NotifyFCM,
|
||||
}),
|
||||
('fcm://%20?to=device&keyfile=/invalid/path', {
|
||||
# invalid Project ID
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://project_id?to=device&keyfile=/invalid/path', {
|
||||
# Test to= and auto detection of oauth mode
|
||||
'instance': plugins.NotifyFCM,
|
||||
# we'll fail to send our notification as a result
|
||||
'response': False,
|
||||
}),
|
||||
('fcm://?to=device&project=project_id&keyfile=/invalid/path', {
|
||||
# Test project= & to= and auto detection of oauth mode
|
||||
'instance': plugins.NotifyFCM,
|
||||
# we'll fail to send our notification as a result
|
||||
'response': False,
|
||||
}),
|
||||
('fcm://project_id?to=device&mode=oauth2', {
|
||||
# no keyfile was specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('fcm://project_id?to=device&mode=oauth2&keyfile=/invalid/path', {
|
||||
# Same test as above except we explicitly set our oauth2 mode
|
||||
# Test to= and auto detection of oauth mode
|
||||
'instance': plugins.NotifyFCM,
|
||||
# we'll fail to send our notification as a result
|
||||
'response': False,
|
||||
}),
|
||||
('fcm://apikey/#topic1/device/?mode=legacy', {
|
||||
'instance': plugins.NotifyFCM,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('fcm://apikey/#topic1/device/?mode=legacy', {
|
||||
'instance': plugins.NotifyFCM,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('fcm://project/#topic1/device/?mode=oauth2&keyfile=file://{}'.format(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__), 'var', 'fcm',
|
||||
'service_account.json')), {
|
||||
'instance': plugins.NotifyFCM,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('fcm://projectid/#topic1/device/?mode=oauth2&keyfile=file://{}'.format(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__), 'var', 'fcm',
|
||||
'service_account.json')), {
|
||||
'instance': plugins.NotifyFCM,
|
||||
# Throws a series of connection and transfer exceptions when
|
||||
# this flag is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
def test_plugin_fcm_urls():
|
||||
"""
|
||||
NotifyFCM() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
@mock.patch('requests.post')
|
||||
def test_fcm_plugin(mock_post):
|
||||
def test_plugin_fcm_general(mock_post):
|
||||
"""
|
||||
API: NotifyFCM() General Checks
|
||||
NotifyFCM() General Checks
|
||||
|
||||
"""
|
||||
|
||||
# Valid Keyfile
|
||||
path = os.path.join(PRIVATE_KEYFILE_DIR, 'service_account.json')
|
||||
|
||||
@ -102,10 +235,12 @@ def test_fcm_plugin(mock_post):
|
||||
'https://fcm.googleapis.com/v1/projects/mock-project-id/messages:send'
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
@mock.patch('requests.post')
|
||||
def test_fcm_keyfile_parse(mock_post):
|
||||
def test_plugin_fcm_keyfile_parse(mock_post):
|
||||
"""
|
||||
API: NotifyFCM() KeyFile Tests
|
||||
NotifyFCM() KeyFile Tests
|
||||
"""
|
||||
|
||||
# Prepare a good response
|
||||
@ -256,9 +391,11 @@ def test_fcm_keyfile_parse(mock_post):
|
||||
assert oauth.access_token is None
|
||||
|
||||
|
||||
def test_fcm_bad_keyfile_parse():
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
def test_plugin_fcm_bad_keyfile_parse():
|
||||
"""
|
||||
API: NotifyFCM() KeyFile Bad Service Account Type Tests
|
||||
NotifyFCM() KeyFile Bad Service Account Type Tests
|
||||
"""
|
||||
|
||||
path = os.path.join(PRIVATE_KEYFILE_DIR, 'service_account-bad-type.json')
|
||||
@ -266,9 +403,11 @@ def test_fcm_bad_keyfile_parse():
|
||||
assert oauth.load(path) is False
|
||||
|
||||
|
||||
def test_fcm_keyfile_missing_entries_parse(tmpdir):
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
def test_plugin_fcm_keyfile_missing_entries_parse(tmpdir):
|
||||
"""
|
||||
API: NotifyFCM() KeyFile Missing Entries Test
|
||||
NotifyFCM() KeyFile Missing Entries Test
|
||||
"""
|
||||
|
||||
# Prepare a base keyfile reference to use
|
||||
@ -302,3 +441,22 @@ def test_fcm_keyfile_missing_entries_parse(tmpdir):
|
||||
path.write('{')
|
||||
oauth = GoogleOAuth()
|
||||
assert oauth.load(str(path)) is False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' in sys.modules,
|
||||
reason="Requires that cryptography NOT be installed")
|
||||
def test_plugin_fcm_cryptography_import_error():
|
||||
"""
|
||||
NotifyFCM Cryptography loading failure
|
||||
"""
|
||||
|
||||
# Prepare a base keyfile reference to use
|
||||
path = os.path.join(PRIVATE_KEYFILE_DIR, 'service_account.json')
|
||||
|
||||
# Attempt to instantiate our object
|
||||
obj = Apprise.instantiate(
|
||||
'fcm://mock-project-id/device/#topic/?keyfile={}'.format(str(path)))
|
||||
|
||||
# It's not possible because our cryptography depedancy is missing
|
||||
assert obj is None
|
178
test/test_plugin_flock.py
Normal file
178
test/test_plugin_flock.py
Normal file
@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('flock://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# An invalid url
|
||||
('flock://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provide a token
|
||||
('flock://%s' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Image handling
|
||||
('flock://%s?image=True' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'flock://t...t',
|
||||
}),
|
||||
('flock://%s?image=False' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
('flock://%s?image=True' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
# Run test when image is set to True, but one couldn't actually be
|
||||
# loaded from the Asset Object.
|
||||
'include_image': False,
|
||||
}),
|
||||
# Test to=
|
||||
('flock://%s?to=u:%s&format=markdown' % ('i' * 24, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Provide markdown format
|
||||
('flock://%s?format=markdown' % ('i' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Provide text format
|
||||
('flock://%s?format=text' % ('i' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Native URL Support, take the slack URL and still build from it
|
||||
('https://api.flock.com/hooks/sendMessage/{}/'.format('i' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Native URL Support with arguments
|
||||
('https://api.flock.com/hooks/sendMessage/{}/?format=markdown'.format(
|
||||
'i' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# Provide markdown format
|
||||
('flock://%s/u:%s?format=markdown' % ('i' * 24, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# Provide text format
|
||||
('flock://%s/u:%s?format=html' % ('i' * 24, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# u: is optional
|
||||
('flock://%s/%s?format=text' % ('i' * 24, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# Multi-entries
|
||||
('flock://%s/g:%s/u:%s?format=text' % ('i' * 24, 'g' * 12, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# Multi-entries using @ for user and # for channel
|
||||
('flock://%s/#%s/@%s?format=text' % ('i' * 24, 'g' * 12, 'u' * 12), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Bot API presumed if one or more targets are specified
|
||||
# has bad entry
|
||||
('flock://%s/g:%s/u:%s?format=text' % ('i' * 24, 'g' * 12, 'u' * 10), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Invalid user/group defined
|
||||
('flock://%s/g:/u:?format=text' % ('i' * 24), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# we don't focus on the invalid length of the user/group fields.
|
||||
# As a result, the following will load and pass the data upstream
|
||||
('flock://%s/g:%s/u:%s?format=text' % ('i' * 24, 'g' * 14, 'u' * 10), {
|
||||
# We will still instantiate the object
|
||||
'instance': plugins.NotifyFlock,
|
||||
}),
|
||||
# Error Testing
|
||||
('flock://%s/g:%s/u:%s?format=text' % ('i' * 24, 'g' * 12, 'u' * 10), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('flock://%s/' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('flock://%s/' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('flock://%s/' % ('t' * 24), {
|
||||
'instance': plugins.NotifyFlock,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_flock_urls():
|
||||
"""
|
||||
NotifyFlock() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_flock_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyFlock() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Initializes the plugin with an invalid token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyFlock(token=None)
|
||||
# Whitespace also acts as an invalid token value
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyFlock(token=" ")
|
@ -24,8 +24,10 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import pytest
|
||||
import mock
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
from apprise import plugins
|
||||
|
||||
from json import dumps
|
||||
@ -35,12 +37,86 @@ from datetime import datetime
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyGitter
|
||||
##################################
|
||||
('gitter://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('gitter://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid Token Length
|
||||
('gitter://%s' % ('a' * 12), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Token specified but no channel
|
||||
('gitter://%s' % ('a' * 40), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Token + channel
|
||||
('gitter://%s/apprise' % ('b' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
'response': False,
|
||||
}),
|
||||
# include image in post
|
||||
('gitter://%s/apprise?image=Yes' % ('c' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
'response': False,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'gitter://c...c/apprise',
|
||||
}),
|
||||
# Don't include image in post (this is the default anyway)
|
||||
('gitter://%s/apprise?image=Yes' % ('d' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
'response': False,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Don't include image in post (this is the default anyway)
|
||||
('gitter://%s/apprise?image=No' % ('e' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
'response': False,
|
||||
}),
|
||||
('gitter://%s/apprise' % ('f' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('gitter://%s/apprise' % ('g' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('gitter://%s/apprise' % ('h' * 40), {
|
||||
'instance': plugins.NotifyGitter,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_gitter_urls():
|
||||
"""
|
||||
NotifyGitter() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_gitter_plugin_general(mock_post, mock_get):
|
||||
def test_plugin_gitter_general(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyGitter() General Tests
|
||||
NotifyGitter() General Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -191,3 +267,19 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
|
||||
}
|
||||
}
|
||||
assert obj.send(body='test body', title='test title') is False
|
||||
|
||||
|
||||
def test_plugin_gitter_edge_cases():
|
||||
"""
|
||||
NotifyGitter() Edge Cases
|
||||
|
||||
"""
|
||||
# Define our channels
|
||||
targets = ['apprise']
|
||||
|
||||
# Initializes the plugin with an invalid token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyGitter(token=None, targets=targets)
|
||||
# Whitespace also acts as an invalid token value
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyGitter(token=" ", targets=targets)
|
@ -58,11 +58,10 @@ from dbus import DBusException # noqa E402
|
||||
@mock.patch('dbus.ByteArray')
|
||||
@mock.patch('dbus.Byte')
|
||||
@mock.patch('dbus.mainloop')
|
||||
def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
||||
mock_interface, mock_sessionbus):
|
||||
def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray,
|
||||
mock_interface, mock_sessionbus):
|
||||
"""
|
||||
API: NotifyDBus Plugin()
|
||||
|
||||
NotifyDBus() General Tests
|
||||
"""
|
||||
|
||||
# Our module base
|
||||
@ -156,9 +155,6 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
||||
assert obj.url().startswith('glib://_/')
|
||||
obj.duration = 0
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert obj._enabled is True
|
||||
|
||||
# Test our class loading using a series of arguments
|
||||
with pytest.raises(TypeError):
|
||||
apprise.plugins.NotifyDBus(**{'schema': 'invalid'})
|
||||
@ -299,7 +295,7 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
||||
# 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(
|
||||
title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
@ -390,21 +386,14 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Create our instance
|
||||
# We can no longer instantiate an instance because dbus has been
|
||||
# officialy marked unavailable and thus the module is marked
|
||||
# as such
|
||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Test url() call
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Our notification fail because the dbus library wasn't present
|
||||
assert obj.notify(
|
||||
title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
assert obj is None
|
||||
|
||||
# Since playing with the sys.modules is not such a good idea,
|
||||
# let's just put it back now :)
|
||||
# let's just put our old configuration back:
|
||||
sys.modules['dbus'] = _session_bus
|
||||
# Reload our modules
|
||||
reload(sys.modules['apprise.plugins.NotifyDBus'])
|
@ -27,7 +27,7 @@ import six
|
||||
import mock
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
import apprise
|
||||
|
||||
try:
|
||||
@ -46,9 +46,10 @@ import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
def test_gnome_plugin():
|
||||
@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+")
|
||||
def test_plugin_gnome_general():
|
||||
"""
|
||||
API: NotifyGnome Plugin()
|
||||
NotifyGnome() General Checks
|
||||
|
||||
"""
|
||||
|
||||
@ -113,10 +114,13 @@ def test_gnome_plugin():
|
||||
|
||||
# Create our instance
|
||||
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
||||
assert obj is not None
|
||||
|
||||
# Set our duration to 0 to speed up timeouts (for testing)
|
||||
obj.duration = 0
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert obj._enabled is True
|
||||
assert obj.enabled is True
|
||||
|
||||
# Test url() call
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
@ -207,7 +211,7 @@ def test_gnome_plugin():
|
||||
|
||||
# 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',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
@ -232,12 +236,7 @@ def test_gnome_plugin():
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Create our instance
|
||||
# We can now no longer load our instance
|
||||
# The object internally is marked disabled
|
||||
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Our notifications can not work without our gi library having been
|
||||
# loaded.
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
assert obj is None
|
93
test/test_plugin_google_chat.py
Normal file
93
test/test_plugin_google_chat.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('gchat://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('gchat://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Workspace, but not Key or Token
|
||||
('gchat://workspace', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Workspace and key, but no Token
|
||||
('gchat://workspace/key/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Credentials are good
|
||||
('gchat://workspace/key/token', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
'privacy_url': 'gchat://w...e/k...y/t...n',
|
||||
}),
|
||||
# Test arguments
|
||||
('gchat://?workspace=ws&key=mykey&token=mytoken', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
'privacy_url': 'gchat://w...s/m...y/m...n',
|
||||
}),
|
||||
# Google Native Webhohok URL
|
||||
('https://chat.googleapis.com/v1/spaces/myworkspace/messages'
|
||||
'?key=mykey&token=mytoken', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
'privacy_url': 'gchat://m...e/m...y/m...n'}),
|
||||
|
||||
('gchat://workspace/key/token', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('gchat://workspace/key/token', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('gchat://workspace/key/token', {
|
||||
'instance': plugins.NotifyGoogleChat,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_google_chat_urls():
|
||||
"""
|
||||
NotifyGoogleChat() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
121
test/test_plugin_gotify.py
Normal file
121
test/test_plugin_gotify.py
Normal file
@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('gotify://', {
|
||||
'instance': None,
|
||||
}),
|
||||
# No token specified
|
||||
('gotify://hostname', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provide a hostname and token
|
||||
('gotify://hostname/%s' % ('t' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'gotify://hostname/t...t',
|
||||
}),
|
||||
# Provide a hostname, path, and token
|
||||
('gotify://hostname/a/path/ending/in/a/slash/%s' % ('u' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'gotify://hostname/a/path/ending/in/a/slash/u...u/',
|
||||
}),
|
||||
# Markdown test
|
||||
('gotify://hostname/%s?format=markdown' % ('t' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
}),
|
||||
# Provide a hostname, path, and token
|
||||
('gotify://hostname/a/path/not/ending/in/a/slash/%s' % ('v' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'gotify://hostname/a/path/not/ending/in/a/slash/v...v/',
|
||||
}),
|
||||
# Provide a priority
|
||||
('gotify://hostname/%s?priority=high' % ('i' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
}),
|
||||
# Provide an invalid priority
|
||||
('gotify://hostname:8008/%s?priority=invalid' % ('i' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
}),
|
||||
# An invalid url
|
||||
('gotify://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('gotify://hostname/%s/' % ('t' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('gotifys://localhost/%s/' % ('t' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('gotify://localhost/%s/' % ('t' * 16), {
|
||||
'instance': plugins.NotifyGotify,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_gotify_urls():
|
||||
"""
|
||||
NotifyGotify() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_gotify_edge_cases():
|
||||
"""
|
||||
NotifyGotify() Edge Cases
|
||||
|
||||
"""
|
||||
# Initializes the plugin with an invalid token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyGotify(token=None)
|
||||
# Whitespace also acts as an invalid token value
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyGotify(token=" ")
|
@ -23,29 +23,12 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import mock
|
||||
import six
|
||||
import pytest
|
||||
import apprise
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
try:
|
||||
# Python v3.4+
|
||||
from importlib import reload
|
||||
except ImportError:
|
||||
try:
|
||||
# Python v3.0-v3.3
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
# Python v2.7
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from gntp import errors
|
||||
|
||||
@ -59,95 +42,36 @@ try:
|
||||
errors.UnsupportedError(
|
||||
0, 'gntp.UnsupportedError() not handled'),
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
# no problem; these tests will be skipped at this point
|
||||
TEST_GROWL_EXCEPTIONS = tuple()
|
||||
# no problem; gntp isn't available to us
|
||||
pass
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@pytest.mark.skipif('gntp' not in sys.modules, reason="requires gntp")
|
||||
def test_growl_plugin_import_error(tmpdir):
|
||||
@pytest.mark.skipif(
|
||||
'gntp' in sys.modules,
|
||||
reason="Requires that gntp NOT be installed")
|
||||
def test_plugin_growl_gntp_import_error():
|
||||
"""
|
||||
API: NotifyGrowl Plugin() Import Error
|
||||
NotifyGrowl() Import Error
|
||||
|
||||
"""
|
||||
# This is a really confusing test case; it can probably be done better,
|
||||
# but this was all I could come up with. Effectively Apprise is will
|
||||
# still work flawlessly without the gntp dependancy. Since
|
||||
# gntp is actually required to be installed to run these unit tests
|
||||
# we need to do some hacky tricks into fooling our test cases that the
|
||||
# package isn't available.
|
||||
|
||||
# So we create a temporary directory called gntp (simulating the
|
||||
# library itself) and writing an __init__.py in it that does nothing
|
||||
# but throw an ImportError exception (simulating that the library
|
||||
# isn't found).
|
||||
suite = tmpdir.mkdir("gntp")
|
||||
suite.join("__init__.py").write('')
|
||||
module_name = 'gntp'
|
||||
suite.join("{}.py".format(module_name)).write('raise ImportError()')
|
||||
|
||||
# The second part of the test is to update our PYTHON_PATH to look
|
||||
# into this new directory first (before looking where the actual
|
||||
# valid paths are). This will allow us to override 'JUST' the sleekxmpp
|
||||
# path.
|
||||
|
||||
# Update our path to point to our new test suite
|
||||
sys.path.insert(0, str(suite))
|
||||
|
||||
# We need to remove the gntp modules that have already been loaded
|
||||
# in memory otherwise they'll just be used instead. Python is smart and
|
||||
# won't go try and reload everything again if it doesn't have to.
|
||||
for name in list(sys.modules.keys()):
|
||||
if name.startswith('{}.'.format(module_name)):
|
||||
del sys.modules[name]
|
||||
del sys.modules[module_name]
|
||||
|
||||
# The following libraries need to be reloaded to prevent
|
||||
# TypeError: super(type, obj): obj must be an instance or subtype of type
|
||||
# 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
|
||||
#
|
||||
reload(sys.modules['apprise.plugins.NotifyGrowl'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# This tests that Apprise still works without gntp.
|
||||
# If the object is disabled, then it can't be instantiated
|
||||
obj = apprise.Apprise.instantiate('growl://growl.server')
|
||||
|
||||
# Growl objects can still be instantiated however
|
||||
assert obj is not None
|
||||
|
||||
# Notifications won't work because gntp did not load
|
||||
assert obj.notify(
|
||||
title='test', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
# Tidy-up / restore things to how they were
|
||||
# Remove our garbage library
|
||||
os.unlink(str(suite.join("{}.py".format(module_name))))
|
||||
|
||||
# Remove our custom entry into the path
|
||||
sys.path.remove(str(suite))
|
||||
|
||||
# Reload the libraries we care about
|
||||
reload(sys.modules['apprise.plugins.NotifyGrowl'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
assert obj is None
|
||||
|
||||
|
||||
@pytest.mark.skipif('gntp' not in sys.modules, reason="requires gntp")
|
||||
@pytest.mark.skipif(
|
||||
'gntp' not in sys.modules, reason="Requires gntp")
|
||||
@mock.patch('gntp.notifier.GrowlNotifier')
|
||||
def test_growl_exception_handling(mock_gntp):
|
||||
def test_plugin_growl_exception_handling(mock_gntp):
|
||||
"""
|
||||
API: NotifyGrowl Exception Handling
|
||||
NotifyGrowl() Exception Handling
|
||||
"""
|
||||
|
||||
from gntp import errors
|
||||
|
||||
TEST_GROWL_EXCEPTIONS = (
|
||||
errors.NetworkError(
|
||||
0, 'gntp.ParseError() not handled'),
|
||||
@ -200,11 +124,11 @@ def test_growl_exception_handling(mock_gntp):
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'gntp' not in sys.modules, reason="requires gntp")
|
||||
'gntp' not in sys.modules, reason="Requires gntp")
|
||||
@mock.patch('gntp.notifier.GrowlNotifier')
|
||||
def test_growl_plugin(mock_gntp):
|
||||
def test_plugin_growl_general(mock_gntp):
|
||||
"""
|
||||
API: NotifyGrowl Plugin()
|
||||
NotifyGrowl() General Checks
|
||||
|
||||
"""
|
||||
|
152
test/test_plugin_homeassistant.py
Normal file
152
test/test_plugin_homeassistant.py
Normal file
@ -0,0 +1,152 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('hassio://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('hassio://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('hassios://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No Long Lived Access Token specified
|
||||
('hassio://user@localhost', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('hassio://localhost/long-lived-access-token', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
}),
|
||||
('hassio://user:pass@localhost/long-lived-access-token/', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'hassio://user:****@localhost/l...n',
|
||||
}),
|
||||
('hassio://localhost:80/long-lived-access-token', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
}),
|
||||
('hassio://user@localhost:8123/llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
'privacy_url': 'hassio://user@localhost/l...t',
|
||||
}),
|
||||
('hassios://localhost/llat?nid=!%', {
|
||||
# Invalid notification_id
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('hassios://localhost/llat?nid=abcd', {
|
||||
# Valid notification_id
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
}),
|
||||
('hassios://user:pass@localhost/llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
'privacy_url': 'hassios://user:****@localhost/l...t',
|
||||
}),
|
||||
('hassios://localhost:8443/path/llat/', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
'privacy_url': 'hassios://localhost:8443/path/l...t',
|
||||
}),
|
||||
('hassio://localhost:8123/a/path?accesstoken=llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
# Default port; so it's stripped off
|
||||
# accesstoken was specified as kwarg
|
||||
'privacy_url': 'hassio://localhost/a/path/l...t',
|
||||
}),
|
||||
('hassios://user:password@localhost:80/llat/', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
|
||||
'privacy_url': 'hassios://user:****@localhost:80',
|
||||
}),
|
||||
('hassio://user:pass@localhost:8123/llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('hassio://user:pass@localhost/llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('hassio://user:pass@localhost/llat', {
|
||||
'instance': plugins.NotifyHomeAssistant,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_homeassistant_urls():
|
||||
"""
|
||||
NotifyHomeAssistant() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_homeassistant_general(mock_post):
|
||||
"""
|
||||
NotifyHomeAssistant() General Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
response = mock.Mock()
|
||||
response.content = ''
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Variation Initializations
|
||||
obj = Apprise.instantiate('hassio://localhost/accesstoken')
|
||||
assert isinstance(obj, plugins.NotifyHomeAssistant) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body="test") is True
|
||||
|
||||
assert mock_post.call_count == 1
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'http://localhost:8123/api/services/persistent_notification/create'
|
206
test/test_plugin_ifttt.py
Normal file
206
test/test_plugin_ifttt.py
Normal file
@ -0,0 +1,206 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('ifttt://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ifttt://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No User
|
||||
('ifttt://EventID/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# A nicely formed ifttt url with 1 event and a new key/value store
|
||||
('ifttt://WebHookID@EventID/?+TemplateKey=TemplateVal', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'ifttt://W...D',
|
||||
}),
|
||||
# Test to= in which case we set the host to the webhook id
|
||||
('ifttt://WebHookID?to=EventID,EventID2', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
}),
|
||||
# Removing certain keys:
|
||||
('ifttt://WebHookID@EventID/?-Value1=&-Value2', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
}),
|
||||
# A nicely formed ifttt url with 2 events defined:
|
||||
('ifttt://WebHookID@EventID/EventID2/', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
}),
|
||||
# Support Native URL references
|
||||
('https://maker.ifttt.com/use/WebHookID/', {
|
||||
# No EventID specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('https://maker.ifttt.com/use/WebHookID/EventID/', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
}),
|
||||
# Native URL with arguments
|
||||
('https://maker.ifttt.com/use/WebHookID/EventID/?-Value1=', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
}),
|
||||
# Test website connection failures
|
||||
('ifttt://WebHookID@EventID', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('ifttt://WebHookID@EventID', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('ifttt://WebHookID@EventID', {
|
||||
'instance': plugins.NotifyIFTTT,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_ifttt_urls():
|
||||
"""
|
||||
NotifyIFTTT() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_ifttt_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyIFTTT() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
webhook_id = 'webhook_id'
|
||||
events = ['event1', 'event2']
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.content = '{}'
|
||||
mock_post.return_value.content = '{}'
|
||||
|
||||
# No webhook_id specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyIFTTT(webhook_id=None, events=None)
|
||||
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Initializes the plugin with an invalid webhook id
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyIFTTT(webhook_id=None, events=events)
|
||||
|
||||
# Whitespace also acts as an invalid webhook id
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyIFTTT(webhook_id=" ", events=events)
|
||||
|
||||
# No events specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyIFTTT(webhook_id=webhook_id, events=None)
|
||||
|
||||
obj = plugins.NotifyIFTTT(webhook_id=webhook_id, events=events)
|
||||
assert isinstance(obj, plugins.NotifyIFTTT) is True
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Test the addition of tokens
|
||||
obj = plugins.NotifyIFTTT(
|
||||
webhook_id=webhook_id, events=events,
|
||||
add_tokens={'Test': 'ValueA', 'Test2': 'ValueB'})
|
||||
|
||||
assert isinstance(obj, plugins.NotifyIFTTT) is True
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Invalid del_tokens entry
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyIFTTT(
|
||||
webhook_id=webhook_id, events=events,
|
||||
del_tokens=plugins.NotifyIFTTT.ifttt_default_title_key)
|
||||
|
||||
assert isinstance(obj, plugins.NotifyIFTTT) is True
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Test removal of tokens by a list
|
||||
obj = plugins.NotifyIFTTT(
|
||||
webhook_id=webhook_id, events=events,
|
||||
add_tokens={
|
||||
'MyKey': 'MyValue'
|
||||
},
|
||||
del_tokens=(
|
||||
plugins.NotifyIFTTT.ifttt_default_title_key,
|
||||
plugins.NotifyIFTTT.ifttt_default_body_key,
|
||||
plugins.NotifyIFTTT.ifttt_default_type_key))
|
||||
|
||||
assert isinstance(obj, plugins.NotifyIFTTT) is True
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Test removal of tokens as dict
|
||||
obj = plugins.NotifyIFTTT(
|
||||
webhook_id=webhook_id, events=events,
|
||||
add_tokens={
|
||||
'MyKey': 'MyValue'
|
||||
},
|
||||
del_tokens={
|
||||
plugins.NotifyIFTTT.ifttt_default_title_key: None,
|
||||
plugins.NotifyIFTTT.ifttt_default_body_key: None,
|
||||
plugins.NotifyIFTTT.ifttt_default_type_key: None})
|
||||
|
||||
assert isinstance(obj, plugins.NotifyIFTTT) is True
|
168
test/test_plugin_join.py
Normal file
168
test/test_plugin_join.py
Normal file
@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('join://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# API Key + bad url
|
||||
('join://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey; no device
|
||||
('join://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + device (using to=)
|
||||
('join://%s?to=%s' % ('a' * 32, 'd' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'join://a...a/',
|
||||
}),
|
||||
# API Key + priority setting
|
||||
('join://%s?priority=high' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + invalid priority setting
|
||||
('join://%s?priority=invalid' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + priority setting (empty)
|
||||
('join://%s?priority=' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + device
|
||||
('join://%s@%s?image=True' % ('a' * 32, 'd' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# No image
|
||||
('join://%s@%s?image=False' % ('a' * 32, 'd' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + Device Name
|
||||
('join://%s/%s' % ('a' * 32, 'My Device'), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
# API Key + device
|
||||
('join://%s/%s' % ('a' * 32, 'd' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# API Key + 2 devices
|
||||
('join://%s/%s/%s' % ('a' * 32, 'd' * 32, 'e' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# API Key + 1 device and 1 group
|
||||
('join://%s/%s/%s' % ('a' * 32, 'd' * 32, 'group.chrome'), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
}),
|
||||
('join://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('join://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('join://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyJoin,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_join_urls():
|
||||
"""
|
||||
NotifyJoin() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_join_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyJoin() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Generate some generic message types
|
||||
device = 'A' * 32
|
||||
group = 'group.chrome'
|
||||
apikey = 'a' * 32
|
||||
|
||||
# Initializes the plugin with devices set to a string
|
||||
plugins.NotifyJoin(apikey=apikey, targets=group)
|
||||
|
||||
# Initializes the plugin with devices set to None
|
||||
plugins.NotifyJoin(apikey=apikey, targets=None)
|
||||
|
||||
# Initializes the plugin with an invalid apikey
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyJoin(apikey=None)
|
||||
|
||||
# Whitespace also acts as an invalid apikey
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyJoin(apikey=" ")
|
||||
|
||||
# Initializes the plugin with devices set to a set
|
||||
p = plugins.NotifyJoin(apikey=apikey, targets=[group, device])
|
||||
|
||||
# Prepare our mock responses
|
||||
req = requests.Request()
|
||||
req.status_code = requests.codes.created
|
||||
req.content = ''
|
||||
mock_get.return_value = req
|
||||
mock_post.return_value = req
|
||||
|
||||
# Test notifications without a body or a title; nothing to send
|
||||
# so we return False
|
||||
p.notify(body=None, title=None, notify_type=NotifyType.INFO) is False
|
103
test/test_plugin_kavenegar.py
Normal file
103
test/test_plugin_kavenegar.py
Normal file
@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('kavenegar://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kavenegar://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kavenegar://{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||
# valid api key and valid authentication
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('kavenegar://{}/{}'.format('a' * 24, '3' * 14), {
|
||||
# valid api key and valid number
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kavenegar://a...a/',
|
||||
}),
|
||||
('kavenegar://{}?to={}'.format('a' * 24, '3' * 14), {
|
||||
# valid api key and valid number
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kavenegar://a...a/',
|
||||
}),
|
||||
('kavenegar://{}@{}/{}'.format('1' * 14, 'b' * 24, '3' * 14), {
|
||||
# valid api key and valid number
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kavenegar://{}@b...b/'.format('1' * 14),
|
||||
}),
|
||||
('kavenegar://{}@{}/{}'.format('a' * 14, 'b' * 24, '3' * 14), {
|
||||
# invalid from number
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kavenegar://{}@{}/{}'.format('3' * 4, 'b' * 24, '3' * 14), {
|
||||
# invalid from number
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kavenegar://{}/{}?from={}'.format('b' * 24, '3' * 14, '1' * 14), {
|
||||
# valid api key and valid number
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kavenegar://{}@b...b/'.format('1' * 14),
|
||||
}),
|
||||
('kavenegar://{}/{}'.format('b' * 24, '4' * 14), {
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('kavenegar://{}/{}'.format('c' * 24, '5' * 14), {
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_kavenegar_urls():
|
||||
"""
|
||||
NotifyKavenegar() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
116
test/test_plugin_kumulos.py
Normal file
116
test/test_plugin_kumulos.py
Normal file
@ -0,0 +1,116 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('kumulos://', {
|
||||
# No API or Server Key specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kumulos://:@/', {
|
||||
# No API or Server Key specified
|
||||
# We don't have strict host checking on for kumulos, so this URL
|
||||
# actually becomes parseable and :@ becomes a hostname.
|
||||
# The below errors because a second token wasn't found
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kumulos://{}/'.format(UUID4), {
|
||||
# No server key was specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kumulos://{}/{}/'.format(UUID4, 'w' * 36), {
|
||||
# Everything is okay
|
||||
'instance': plugins.NotifyKumulos,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kumulos://8...2/w...w/',
|
||||
}),
|
||||
('kumulos://{}/{}/'.format(UUID4, 'x' * 36), {
|
||||
'instance': plugins.NotifyKumulos,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kumulos://8...2/x...x/',
|
||||
}),
|
||||
('kumulos://{}/{}/'.format(UUID4, 'y' * 36), {
|
||||
'instance': plugins.NotifyKumulos,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kumulos://8...2/y...y/',
|
||||
}),
|
||||
('kumulos://{}/{}/'.format(UUID4, 'z' * 36), {
|
||||
'instance': plugins.NotifyKumulos,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_kumulos_urls():
|
||||
"""
|
||||
NotifyKumulos() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_kumulos_edge_cases():
|
||||
"""
|
||||
NotifyKumulos() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Invalid API Key
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyKumulos(None, None)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyKumulos(" ", None)
|
||||
|
||||
# Invalid Server Key
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyKumulos("abcd", None)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyKumulos("abcd", " ")
|
248
test/test_plugin_lametric.py
Normal file
248
test/test_plugin_lametric.py
Normal file
@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('lametric://', {
|
||||
# No APIKey or App ID specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lametric://:@/', {
|
||||
# No APIKey or App ID specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lametric://{}/'.format(
|
||||
'com.lametric.941c51dff3135bd87aa72db9d855dd50'), {
|
||||
# No APIKey specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lametric://root:{}@192.168.0.5:8080/'.format(UUID4), {
|
||||
# Everything is okay; this would be picked up in Device Mode
|
||||
# We're using a default port and enforcing a special user
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://root:8...2@192.168.0.5/',
|
||||
}),
|
||||
('lametric://{}@192.168.0.4:8000/'.format(UUID4), {
|
||||
# Everything is okay; this would be picked up in Device Mode
|
||||
# Port is enforced
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://8...2@192.168.0.4:8000/',
|
||||
}),
|
||||
('lametric://{}@192.168.0.5/'.format(UUID4), {
|
||||
# Everything is okay; this would be picked up in Device Mode
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://8...2@192.168.0.5/',
|
||||
}),
|
||||
('lametrics://{}@192.168.0.6/?mode=device'.format(UUID4), {
|
||||
# Everything is okay; Device mode forced
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametrics://8...2@192.168.0.6/',
|
||||
}),
|
||||
# Support Native URL (with Access Token Argument)
|
||||
('https://developer.lametric.com/api/v1/dev/widget/update/'
|
||||
'com.lametric.ABCD123/1?token={}=='.format('D' * 88), {
|
||||
# Everything is okay; Device mode forced
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://D...=@A...3/1/',
|
||||
}),
|
||||
|
||||
('lametric://192.168.2.8/?mode=device&apikey=abc123', {
|
||||
# Everything is okay; Device mode forced
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://a...3@192.168.2.8/',
|
||||
}),
|
||||
('lametrics://{}==@com.lametric.941c51dff3135bd87aa72db9d855dd50/'
|
||||
'?mode=cloud&app_ver=2'.format('A' * 88), {
|
||||
# Everything is okay; Cloud mode forced
|
||||
# We gracefully strip off the com.lametric. part as well
|
||||
# We also set an application version of 2
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://A...=@9...0/',
|
||||
}),
|
||||
('lametrics://{}==@com.lametric.941c51dff3135bd87aa72db9d855dd50/'
|
||||
'?app_ver=invalid'.format('A' * 88), {
|
||||
# We set invalid app version
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# our lametric object initialized via argument
|
||||
('lametric://?app=com.lametric.941c51dff3135bd87aa72db9d855dd50&token={}=='
|
||||
'&mode=cloud'.format('B' * 88), {
|
||||
# Everything is okay; Cloud mode forced
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://B...=@9...0/',
|
||||
}),
|
||||
('lametrics://{}==@abcd/?mode=cloud&sound=knock&icon_type=info'
|
||||
'&priority=critical&cycles=10'.format('C' * 88), {
|
||||
# Cloud mode forced, sound, icon_type, and priority not supported
|
||||
# with cloud mode so warnings are created
|
||||
'instance': plugins.NotifyLametric,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://C...=@a...d/',
|
||||
}),
|
||||
('lametrics://{}@192.168.0.7/?mode=invalid'.format(UUID4), {
|
||||
# Invalid Mode
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lametrics://{}@192.168.0.6/?sound=alarm1'.format(UUID4), {
|
||||
# Device mode with sound set to alarm1
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametrics://{}@192.168.0.7/?sound=bike'.format(UUID4), {
|
||||
# Device mode with sound set to bicycle using alias
|
||||
'instance': plugins.NotifyLametric,
|
||||
# Bike is an alias,
|
||||
'url_matches': r'sound=bicycle',
|
||||
}),
|
||||
('lametrics://{}@192.168.0.8/?sound=invalid!'.format(UUID4), {
|
||||
# Invalid sounds just produce warnings... object still loads
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametrics://{}@192.168.0.9/?icon_type=alert'.format(UUID4), {
|
||||
# Icon Type Changed
|
||||
'instance': plugins.NotifyLametric,
|
||||
# icon=alert exists somewhere on our generated URL
|
||||
'url_matches': r'icon_type=alert',
|
||||
}),
|
||||
('lametrics://{}@192.168.0.10/?icon_type=invalid'.format(UUID4), {
|
||||
# Invalid icon types just produce warnings... object still loads
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.1/?priority=warning'.format(UUID4), {
|
||||
# Priority changed
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametrics://{}@192.168.1.2/?priority=invalid'.format(UUID4), {
|
||||
# Invalid priority just produce warnings... object still loads
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.2/?icon=230'.format(UUID4), {
|
||||
# Our custom icon by it's ID
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametrics://{}@192.168.1.2/?icon=#230'.format(UUID4), {
|
||||
# Our custom icon by it's ID; the hashtag at the front is ignored
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.2/?icon=Heart'.format(UUID4), {
|
||||
# Our custom icon; the hashtag at the front is ignored
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.2/?icon=#'.format(UUID4), {
|
||||
# a hashtag and nothing else
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.2/?icon=#%20%20%20'.format(UUID4), {
|
||||
# a hashtag and some spaces
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.3/?cycles=2'.format(UUID4), {
|
||||
# Cycles changed
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@192.168.1.4/?cycles=-1'.format(UUID4), {
|
||||
# Cycles changed (out of range)
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametrics://{}@192.168.1.5/?cycles=invalid'.format(UUID4), {
|
||||
# Invalid priority just produce warnings... object still loads
|
||||
'instance': plugins.NotifyLametric,
|
||||
}),
|
||||
('lametric://{}@example.com/'.format(UUID4), {
|
||||
'instance': plugins.NotifyLametric,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametric://8...2@example.com/',
|
||||
}),
|
||||
('lametrics://{}@example.ca/'.format(UUID4), {
|
||||
'instance': plugins.NotifyLametric,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lametrics://8...2@example.ca/',
|
||||
}),
|
||||
('lametrics://{}@example.net/'.format(UUID4), {
|
||||
'instance': plugins.NotifyLametric,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_lametric_urls():
|
||||
"""
|
||||
NotifyLametric() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_lametric_edge_cases():
|
||||
"""
|
||||
NotifyLametric() Edge Cases
|
||||
|
||||
"""
|
||||
# Initializes the plugin with an invalid API Key
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyLametric(apikey=None, mode="device")
|
||||
|
||||
# Initializes the plugin with an invalid Client Secret
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyLametric(client_id='valid', secret=None, mode="cloud")
|
@ -25,10 +25,22 @@
|
||||
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
import mock
|
||||
|
||||
import apprise
|
||||
|
||||
try:
|
||||
# Python v3.4+
|
||||
from importlib import reload
|
||||
except ImportError:
|
||||
try:
|
||||
# Python v3.0-v3.3
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
# Python v2.7
|
||||
pass
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
@ -37,9 +49,9 @@ logging.disable(logging.CRITICAL)
|
||||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('platform.system')
|
||||
@mock.patch('platform.mac_ver')
|
||||
def test_macosx_plugin(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
"""
|
||||
API: NotifyMacOSX Plugin()
|
||||
NotifyMacOSX() General Checks
|
||||
|
||||
"""
|
||||
|
||||
@ -48,8 +60,6 @@ def test_macosx_plugin(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
script.write('')
|
||||
# Give execute bit
|
||||
os.chmod(str(script), 0o755)
|
||||
# Point our object to our new temporary existing file
|
||||
apprise.plugins.NotifyMacOSX.notify_path = str(script)
|
||||
mock_cmd_response = mock.Mock()
|
||||
|
||||
# Set a successful response
|
||||
@ -60,10 +70,18 @@ def test_macosx_plugin(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
mock_macver.return_value = ('10.8', ('', '', ''), '')
|
||||
mock_popen.return_value = mock_cmd_response
|
||||
|
||||
# Ensure our enviroment is loaded with this configuration
|
||||
reload(sys.modules['apprise.plugins.NotifyMacOSX'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Point our object to our new temporary existing file
|
||||
apprise.plugins.NotifyMacOSX.notify_path = str(script)
|
||||
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?image=True', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is True
|
||||
|
||||
# Test url() call
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
@ -79,14 +97,12 @@ def test_macosx_plugin(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?image=True', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is True
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?image=False', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
@ -95,64 +111,82 @@ def test_macosx_plugin(mock_macver, mock_system, mock_popen, tmpdir):
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is True
|
||||
assert obj.sound == 'default'
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
|
||||
# Now test cases where our environment isn't set up correctly
|
||||
# In this case we'll simulate a situation where our binary isn't
|
||||
# executable
|
||||
# If our binary is inacccessible (or not executable), we can
|
||||
# no longer send our notifications
|
||||
os.chmod(str(script), 0o644)
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is False
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
# Restore permission
|
||||
os.chmod(str(script), 0o755)
|
||||
|
||||
# Test case where we simply aren't on a mac
|
||||
mock_system.return_value = 'Linux'
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is False
|
||||
# But now let's disrupt the path location
|
||||
obj.notify_path = 'invalid_missing-file'
|
||||
assert not os.path.isfile(obj.notify_path)
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
# Restore mac environment
|
||||
mock_system.return_value = 'Darwin'
|
||||
|
||||
# Now we must be Mac OS v10.8 or higher... Test cases where we aren't
|
||||
mock_macver.return_value = ('10.7', ('', '', ''), '')
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is False
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
# Restore valid mac environment
|
||||
mock_macver.return_value = ('10.8', ('', '', ''), '')
|
||||
|
||||
mock_macver.return_value = ('9.12', ('', '', ''), '')
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is False
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
# Restore valid mac environment
|
||||
mock_macver.return_value = ('10.8', ('', '', ''), '')
|
||||
|
||||
# Test cases where the script just flat out fails
|
||||
mock_cmd_response.returncode = 1
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://', suppress_exceptions=False)
|
||||
assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True
|
||||
assert obj._enabled is True
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
# Restore script return value
|
||||
mock_cmd_response.returncode = 1
|
||||
mock_cmd_response.returncode = 0
|
||||
|
||||
# Test case where we simply aren't on a mac
|
||||
mock_system.return_value = 'Linux'
|
||||
reload(sys.modules['apprise.plugins.NotifyMacOSX'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Point our object to our new temporary existing file
|
||||
apprise.plugins.NotifyMacOSX.notify_path = str(script)
|
||||
|
||||
# Our object is disabled
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'macosx://_/?sound=default', suppress_exceptions=False)
|
||||
assert obj is None
|
||||
|
||||
# Restore mac environment
|
||||
mock_system.return_value = 'Darwin'
|
||||
|
||||
# Now we must be Mac OS v10.8 or higher...
|
||||
mock_macver.return_value = ('10.7', ('', '', ''), '')
|
||||
reload(sys.modules['apprise.plugins.NotifyMacOSX'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Point our object to our new temporary existing file
|
||||
apprise.plugins.NotifyMacOSX.notify_path = str(script)
|
||||
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'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(sys.modules['apprise.plugins.NotifyMacOSX'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# Point our object to our new temporary existing file
|
||||
apprise.plugins.NotifyMacOSX.notify_path = 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
|
@ -27,6 +27,7 @@ import os
|
||||
import sys
|
||||
import mock
|
||||
import requests
|
||||
from helpers import AppriseURLTester
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAttachment
|
||||
@ -39,11 +40,145 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('mailgun://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('mailgun://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No Token specified
|
||||
('mailgun://user@localhost.localdomain', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Token is valid, but no user name specified
|
||||
('mailgun://localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid from email address
|
||||
('mailgun://!@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No To email address, but everything else is valid
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?format=markdown'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?format=html'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?format=text'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# valid url with region specified (case insensitve)
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?region=uS'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# valid url with region specified (case insensitve)
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?region=EU'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# invalid url with region specified (case insensitve)
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}?region=invalid'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# headers
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'
|
||||
'?+X-Customer-Campaign-ID=Apprise'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# template tokens
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'
|
||||
'?:name=Chris&:status=admin'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# bcc and cc
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'
|
||||
'?bcc=user@example.com&cc=user2@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
# One To Email address
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}/test@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/'
|
||||
'{}-{}-{}?to=test@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun}),
|
||||
# One To Email address, a from name specified too
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}/'
|
||||
'test@example.com?name="Frodo"'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun}),
|
||||
# Invalid 'To' Email address
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}/invalid'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
# Multiple 'To', 'Cc', and 'Bcc' addresses (with invalid ones)
|
||||
('mailgun://user@example.com/{}-{}-{}/{}?bcc={}&cc={}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8,
|
||||
'/'.join(('user1@example.com', 'invalid', 'User2:user2@example.com')),
|
||||
','.join(('user3@example.com', 'i@v', 'User1:user1@example.com')),
|
||||
','.join(('user4@example.com', 'g@r@b', 'Da:user5@example.com'))), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('mailgun://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifyMailgun,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_mailgun_urls():
|
||||
"""
|
||||
NotifyMailgun() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_mailgun_plugin_attachments(mock_post):
|
||||
def test_plugin_mailgun_attachments(mock_post):
|
||||
"""
|
||||
API: NotifyMailgun() Attachments
|
||||
NotifyMailgun() Attachments
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
@ -30,17 +30,161 @@ import pytest
|
||||
from apprise import plugins
|
||||
from apprise import AppriseAsset
|
||||
from json import dumps
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyMatrix
|
||||
##################################
|
||||
('matrix://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('matrixs://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('matrix://localhost?mode=off', {
|
||||
# treats it as a anonymous user to register
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# response is false because we have nothing to notify
|
||||
'response': False,
|
||||
}),
|
||||
('matrix://localhost', {
|
||||
# response is TypeError because we'll try to initialize as
|
||||
# a t2bot and fail (localhost is too short of a api key)
|
||||
'instance': TypeError
|
||||
}),
|
||||
('matrix://user:pass@localhost/#room1/#room2/#room3', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('matrix://user:pass@localhost/#room1/#room2/!room1', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('matrix://user:pass@localhost:1234/#room', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'matrix://user:****@localhost:1234/',
|
||||
}),
|
||||
|
||||
# Matrix supports webhooks too; the following tests this now:
|
||||
('matrix://user:token@localhost?mode=matrix&format=text', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
'response': False,
|
||||
}),
|
||||
('matrix://user:token@localhost?mode=matrix&format=html', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user:token@localhost?mode=slack&format=text', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrixs://user:token@localhost?mode=SLACK&format=markdown', {
|
||||
# user and token specified; slack webhook still detected
|
||||
# despite uppercase characters
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user@localhost?mode=SLACK&format=markdown&token=mytoken', {
|
||||
# user and token specified; slack webhook still detected
|
||||
# despite uppercase characters; token also set on URL as arg
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://_?mode=t2bot&token={}'.format('b' * 64), {
|
||||
# Testing t2bot initialization and setting the password using the
|
||||
# token directive
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'matrix://b...b/',
|
||||
}),
|
||||
# Image Reference
|
||||
('matrixs://user:token@localhost?mode=slack&format=markdown&image=True', {
|
||||
# user and token specified; image set to True
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrixs://user:token@localhost?mode=slack&format=markdown&image=False', {
|
||||
# user and token specified; image set to True
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrixs://user@{}?mode=t2bot&format=markdown&image=True'
|
||||
.format('a' * 64), {
|
||||
# user and token specified; image set to True
|
||||
'instance': plugins.NotifyMatrix}),
|
||||
('matrix://user@{}?mode=t2bot&format=html&image=False'
|
||||
.format('z' * 64), {
|
||||
# user and token specified; image set to True
|
||||
'instance': plugins.NotifyMatrix}),
|
||||
# This will default to t2bot because no targets were specified and no
|
||||
# password
|
||||
('matrixs://{}'.format('c' * 64), {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
# Test Native URL
|
||||
('https://webhooks.t2bot.io/api/v1/matrix/hook/{}/'.format('d' * 64), {
|
||||
# user and token specified; image set to True
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user:token@localhost?mode=On', {
|
||||
# invalid webhook specified (unexpected boolean)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('matrix://token@localhost/?mode=Matrix', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('matrix://user:token@localhost/mode=matrix', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('matrix://token@localhost:8080/?mode=slack', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('matrix://{}/?mode=t2bot'.format('b' * 64), {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_matrix_urls():
|
||||
"""
|
||||
NotifyMatrix() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_matrix_plugin_general(mock_post, mock_get):
|
||||
def test_plugin_matrix_general(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyMatrix() General Tests
|
||||
NotifyMatrix() General Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -193,9 +337,9 @@ def test_notify_matrix_plugin_general(mock_post, mock_get):
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_matrix_plugin_fetch(mock_post, mock_get):
|
||||
def test_plugin_matrix_fetch(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyMatrix() Server Fetch/API Tests
|
||||
NotifyMatrix() Server Fetch/API Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -299,9 +443,9 @@ def test_notify_matrix_plugin_fetch(mock_post, mock_get):
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_matrix_plugin_auth(mock_post, mock_get):
|
||||
def test_plugin_matrix_auth(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyMatrix() Server Authentication
|
||||
NotifyMatrix() Server Authentication
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -395,9 +539,9 @@ def test_notify_matrix_plugin_auth(mock_post, mock_get):
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_matrix_plugin_rooms(mock_post, mock_get):
|
||||
def test_plugin_matrix_rooms(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyMatrix() Room Testing
|
||||
NotifyMatrix() Room Testing
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -582,9 +726,9 @@ def test_notify_matrix_plugin_rooms(mock_post, mock_get):
|
||||
assert obj._room_id('#abc123:localhost') is None
|
||||
|
||||
|
||||
def test_notify_matrix_url_parsing():
|
||||
def test_plugin_matrix_url_parsing():
|
||||
"""
|
||||
API: NotifyMatrix() URL Testing
|
||||
NotifyMatrix() URL Testing
|
||||
|
||||
"""
|
||||
result = plugins.NotifyMatrix.parse_url(
|
||||
@ -604,9 +748,9 @@ def test_notify_matrix_url_parsing():
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_matrix_plugin_image_errors(mock_post, mock_get):
|
||||
def test_plugin_matrix_image_errors(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyMatrix() Image Error Handling
|
||||
NotifyMatrix() Image Error Handling
|
||||
|
||||
"""
|
||||
|
137
test/test_plugin_mattermost.py
Normal file
137
test/test_plugin_mattermost.py
Normal file
@ -0,0 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('mmost://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('mmosts://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('mmost://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('mmosts://localhost', {
|
||||
# Thrown because there was no webhook id specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?channel=test', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'mmost://user@localhost/3...4/',
|
||||
}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4'
|
||||
'?to=test&image=True', {
|
||||
'instance': plugins.NotifyMattermost}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
||||
'?to=test&image=False', {
|
||||
'instance': plugins.NotifyMattermost}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
||||
'?to=test&image=True', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
# don't include an image by default
|
||||
'include_image': False}),
|
||||
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'mmost://localhost:8080/3...4/',
|
||||
}),
|
||||
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
('mmost://localhost:invalid-port/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': None,
|
||||
}),
|
||||
('mmosts://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
# Test our paths
|
||||
('mmosts://localhost/a/path/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
('mmosts://localhost/////3ccdd113474722377935511fc85d3dd4///', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||
'instance': plugins.NotifyMattermost,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_mattermost_urls():
|
||||
"""
|
||||
NotifyMattermost() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_mattermost_edge_cases():
|
||||
"""
|
||||
NotifyMattermost() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Invalid Authorization Token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMattermost(None)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMattermost(" ")
|
128
test/test_plugin_messagebird.py
Normal file
128
test/test_plugin_messagebird.py
Normal file
@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('msgbird://', {
|
||||
# No hostname/apikey specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msgbird://{}/abcd'.format('a' * 25), {
|
||||
# invalid characters in source phone number
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msgbird://{}/123'.format('a' * 25), {
|
||||
# invalid source phone number
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msgbird://{}/15551232000'.format('a' * 25), {
|
||||
# target phone number becomes who we text too; all is good
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msgbird://a...a/15551232000',
|
||||
}),
|
||||
('msgbird://{}/15551232000/abcd'.format('a' * 25), {
|
||||
# valid credentials
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msgbird://{}/15551232000/123'.format('a' * 25), {
|
||||
# valid credentials
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msgbird://{}/?from=15551233000&to=15551232000'.format('a' * 25), {
|
||||
# reference to to= and from=
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
}),
|
||||
('msgbird://{}/15551232000'.format('a' * 25), {
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('msgbird://{}/15551232000'.format('a' * 25), {
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('msgbird://{}/15551232000'.format('a' * 25), {
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_messagebird_urls():
|
||||
"""
|
||||
NotifyTemplate() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_messagebird_edge_cases(mock_post):
|
||||
"""
|
||||
NotifyMessageBird() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare our response
|
||||
response = requests.Request()
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
# authkey = '{}'.format('a' * 24)
|
||||
source = '+1 (555) 123-3456'
|
||||
|
||||
# No apikey specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMessageBird(apikey=None, source=source)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMessageBird(apikey=" ", source=source)
|
@ -28,107 +28,35 @@ import re
|
||||
import sys
|
||||
import ssl
|
||||
import six
|
||||
import os
|
||||
import pytest
|
||||
|
||||
# Rebuild our Apprise environment
|
||||
import apprise
|
||||
|
||||
try:
|
||||
# Python v3.4+
|
||||
from importlib import reload
|
||||
except ImportError:
|
||||
try:
|
||||
# Python v3.0-v3.3
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
# Python v2.7
|
||||
pass
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@pytest.mark.skipif('paho' not in sys.modules, reason="requires paho-mqtt")
|
||||
def test_paho_mqtt_plugin_import_error(tmpdir):
|
||||
@pytest.mark.skipif(
|
||||
'paho' in sys.modules,
|
||||
reason="Requires that cryptography NOT be installed")
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_mqtt_paho_import_error(mock_post):
|
||||
"""
|
||||
API: NotifyMQTT Plugin() Import Error
|
||||
|
||||
NotifyFCM Cryptography loading failure
|
||||
"""
|
||||
# This is a really confusing test case; it can probably be done better,
|
||||
# but this was all I could come up with. Effectively Apprise is will
|
||||
# still work flawlessly without the paho dependancy. Since
|
||||
# paho is actually required to be installed to run these unit tests
|
||||
# we need to do some hacky tricks into fooling our test cases that the
|
||||
# package isn't available.
|
||||
|
||||
# So we create a temporary directory called paho (simulating the
|
||||
# library itself) and writing an __init__.py in it that does nothing
|
||||
# but throw an ImportError exception (simulating that the library
|
||||
# isn't found).
|
||||
suite = tmpdir.mkdir("paho")
|
||||
suite.join("__init__.py").write('')
|
||||
module_name = 'paho'
|
||||
suite.join("{}.py".format(module_name)).write('raise ImportError()')
|
||||
|
||||
# The second part of the test is to update our PYTHON_PATH to look
|
||||
# into this new directory first (before looking where the actual
|
||||
# valid paths are). This will allow us to override 'JUST' the sleekxmpp
|
||||
# path.
|
||||
|
||||
# Update our path to point to our new test suite
|
||||
sys.path.insert(0, str(suite))
|
||||
|
||||
# We need to remove the sleekxmpp modules that have already been loaded
|
||||
# in memory otherwise they'll just be used instead. Python is smart and
|
||||
# won't go try and reload everything again if it doesn't have to.
|
||||
for name in list(sys.modules.keys()):
|
||||
if name.startswith('{}.'.format(module_name)):
|
||||
del sys.modules[name]
|
||||
del sys.modules[module_name]
|
||||
|
||||
# The following libraries need to be reloaded to prevent
|
||||
# TypeError: super(type, obj): obj must be an instance or subtype of type
|
||||
# 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
|
||||
#
|
||||
reload(sys.modules['apprise.plugins.NotifyMQTT'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
|
||||
# This tests that Apprise still works without sleekxmpp.
|
||||
# XMPP objects can still be instantiated in these cases.
|
||||
obj = apprise.Apprise.instantiate('mqtt://user:pass@localhost/my/topic')
|
||||
assert isinstance(obj, apprise.plugins.NotifyMQTT)
|
||||
# We can still retrieve our url back to us
|
||||
assert obj.url().startswith('mqtt://user:pass@localhost/my/topic')
|
||||
# Notifications are not possible
|
||||
assert obj.notify(body="test") is False
|
||||
|
||||
# Tidy-up / restore things to how they were
|
||||
# Remove our garbage library
|
||||
os.unlink(str(suite.join("{}.py".format(module_name))))
|
||||
|
||||
# Remove our custom entry into the path
|
||||
sys.path.remove(str(suite))
|
||||
|
||||
# Reload the libraries we care about
|
||||
reload(sys.modules['apprise.plugins.NotifyMQTT'])
|
||||
reload(sys.modules['apprise.plugins'])
|
||||
reload(sys.modules['apprise.Apprise'])
|
||||
reload(sys.modules['apprise'])
|
||||
# without the library, the object can't be instantiated
|
||||
obj = apprise.Apprise.instantiate(
|
||||
'mqtt://user:pass@localhost/my/topic')
|
||||
assert obj is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'paho' not in sys.modules, reason="requires paho-mqtt")
|
||||
'paho' not in sys.modules, reason="Requires paho-mqtt")
|
||||
@mock.patch('paho.mqtt.client.Client')
|
||||
def test_mqtt_plugin(mock_client):
|
||||
def test_plugin_mqtt_general(mock_client):
|
||||
"""
|
||||
API: NotifyMQTT Plugin()
|
||||
NotifyMQTT() General Checks
|
||||
|
||||
"""
|
||||
# Speed up request rate for testing
|
151
test/test_plugin_msg91.py
Normal file
151
test/test_plugin_msg91.py
Normal file
@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('msg91://', {
|
||||
# No hostname/authkey specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://-', {
|
||||
# Invalid AuthKey
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}'.format('a' * 23), {
|
||||
# valid AuthKey
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/123'.format('a' * 23), {
|
||||
# invalid phone number
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/abcd'.format('a' * 23), {
|
||||
# No number to notify
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/15551232000/?country=invalid'.format('a' * 23), {
|
||||
# invalid country
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}/15551232000/?country=99'.format('a' * 23), {
|
||||
# invalid country
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}/15551232000/?route=invalid'.format('a' * 23), {
|
||||
# invalid route
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}/15551232000/?route=99'.format('a' * 23), {
|
||||
# invalid route
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}/15551232000'.format('a' * 23), {
|
||||
# a valid message
|
||||
'instance': plugins.NotifyMSG91,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msg91://a...a/15551232000',
|
||||
}),
|
||||
('msg91://{}/?to=15551232000'.format('a' * 23), {
|
||||
# a valid message
|
||||
'instance': plugins.NotifyMSG91,
|
||||
}),
|
||||
('msg91://{}/15551232000?country=91&route=1'.format('a' * 23), {
|
||||
# using phone no with no target - we text ourselves in
|
||||
# this case
|
||||
'instance': plugins.NotifyMSG91,
|
||||
}),
|
||||
('msg91://{}/15551232000'.format('a' * 23), {
|
||||
# use get args to acomplish the same thing
|
||||
'instance': plugins.NotifyMSG91,
|
||||
}),
|
||||
('msg91://{}/15551232000'.format('a' * 23), {
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('msg91://{}/15551232000'.format('a' * 23), {
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_msg91_urls():
|
||||
"""
|
||||
NotifyMSG91() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_msg91_edge_cases(mock_post):
|
||||
"""
|
||||
NotifyMSG91() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare our response
|
||||
response = requests.Request()
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
# authkey = '{}'.format('a' * 24)
|
||||
target = '+1 (555) 123-3456'
|
||||
|
||||
# No authkey specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMSG91(authkey=None, targets=target)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyMSG91(authkey=" ", targets=target)
|
@ -31,16 +31,152 @@ from apprise import Apprise
|
||||
from apprise import AppriseConfig
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyMSTeams
|
||||
##################################
|
||||
('msteams://', {
|
||||
# First API Token not specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://:@/', {
|
||||
# We don't have strict host checking on for msteams, so this URL
|
||||
# actually becomes parseable and :@ becomes a hostname.
|
||||
# The below errors because a second token wasn't found
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://{}'.format(UUID4), {
|
||||
# Just half of one token 1 provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://{}@{}/'.format(UUID4, UUID4), {
|
||||
# Just 1 tokens provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://{}@{}/{}'.format(UUID4, UUID4, 'a' * 32), {
|
||||
# Just 2 tokens provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://{}@{}/{}/{}?t1'.format(UUID4, UUID4, 'b' * 32, UUID4), {
|
||||
# All tokens provided - we're good
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
}),
|
||||
# Support native URLs
|
||||
('https://outlook.office.com/webhook/{}@{}/IncomingWebhook/{}/{}'
|
||||
.format(UUID4, UUID4, 'k' * 32, UUID4), {
|
||||
# All tokens provided - we're good
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response (v1 format)
|
||||
'privacy_url': 'msteams://8...2/k...k/8...2/'}),
|
||||
|
||||
# Support New Native URLs
|
||||
('https://myteam.webhook.office.com/webhookb2/{}@{}/IncomingWebhook/{}/{}'
|
||||
.format(UUID4, UUID4, 'm' * 32, UUID4), {
|
||||
# All tokens provided - we're good
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response (v2 format):
|
||||
'privacy_url': 'msteams://myteam/8...2/m...m/8...2/'}),
|
||||
|
||||
# Legacy URL Formatting
|
||||
('msteams://{}@{}/{}/{}?t2'.format(UUID4, UUID4, 'c' * 32, UUID4), {
|
||||
# All tokens provided - we're good
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Legacy URL Formatting
|
||||
('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'd' * 32, UUID4), {
|
||||
# All tokens provided - we're good no image
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msteams://8...2/d...d/8...2/',
|
||||
}),
|
||||
# New 2021 URL formatting
|
||||
('msteams://apprise/{}@{}/{}/{}'.format(
|
||||
UUID4, UUID4, 'e' * 32, UUID4), {
|
||||
# All tokens provided - we're good no image
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msteams://apprise/8...2/e...e/8...2/',
|
||||
}),
|
||||
# New 2021 URL formatting; support team= argument
|
||||
('msteams://{}@{}/{}/{}?team=teamname'.format(
|
||||
UUID4, UUID4, 'f' * 32, UUID4), {
|
||||
# All tokens provided - we're good no image
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msteams://teamname/8...2/f...f/8...2/',
|
||||
}),
|
||||
# New 2021 URL formatting (forcing v1)
|
||||
('msteams://apprise/{}@{}/{}/{}?version=1'.format(
|
||||
UUID4, UUID4, 'e' * 32, UUID4), {
|
||||
# All tokens provided - we're good
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'msteams://8...2/e...e/8...2/',
|
||||
}),
|
||||
# Invalid versioning
|
||||
('msteams://apprise/{}@{}/{}/{}?version=999'.format(
|
||||
UUID4, UUID4, 'e' * 32, UUID4), {
|
||||
# invalid version
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://apprise/{}@{}/{}/{}?version=invalid'.format(
|
||||
UUID4, UUID4, 'e' * 32, UUID4), {
|
||||
# invalid version
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msteams://{}@{}/{}/{}?tx'.format(UUID4, UUID4, 'x' * 32, UUID4), {
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('msteams://{}@{}/{}/{}?ty'.format(UUID4, UUID4, 'y' * 32, UUID4), {
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('msteams://{}@{}/{}/{}?tz'.format(UUID4, UUID4, 'z' * 32, UUID4), {
|
||||
'instance': plugins.NotifyMSTeams,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_msteams_urls():
|
||||
"""
|
||||
NotifyMSTeams() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_msteams_templating(mock_post, tmpdir):
|
||||
def test_plugin_msteams_templating(mock_post, tmpdir):
|
||||
"""
|
||||
API: NotifyMSTeams() Templating
|
||||
NotifyMSTeams() Templating
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -249,7 +385,7 @@ def test_msteams_templating(mock_post, tmpdir):
|
||||
@mock.patch('requests.post')
|
||||
def test_msteams_yaml_config(mock_post, tmpdir):
|
||||
"""
|
||||
API: NotifyMSTeams() YAML Configuration Entries
|
||||
NotifyMSTeams() YAML Configuration Entries
|
||||
|
||||
"""
|
||||
|
||||
@ -515,9 +651,9 @@ def test_msteams_yaml_config(mock_post, tmpdir):
|
||||
assert len(cfg[0]) == 0
|
||||
|
||||
|
||||
def test_notify_msteams_plugin():
|
||||
def test_plugin_msteams_edge_cases():
|
||||
"""
|
||||
API: NotifyMSTeams() Extra Checks
|
||||
NotifyMSTeams() Edge Cases
|
||||
|
||||
"""
|
||||
# Initializes the plugin with an invalid token
|
163
test/test_plugin_nexmo.py
Normal file
163
test/test_plugin_nexmo.py
Normal file
@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from json import dumps
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('nexmo://', {
|
||||
# No API Key specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://:@/', {
|
||||
# invalid Auth key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://AC{}@12345678'.format('a' * 8), {
|
||||
# Just a key provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}'.format('a' * 8, 'b' * 16, '3' * 9), {
|
||||
# key and secret provided and from but invalid from no
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}/?ttl=0'.format('b' * 8, 'c' * 16, '3' * 11), {
|
||||
# Invalid ttl defined
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}'.format('d' * 8, 'e' * 16, 'a' * 11), {
|
||||
# Invalid source number
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}/123/{}/abcd/'.format(
|
||||
'f' * 8, 'g' * 16, '3' * 11, '9' * 15), {
|
||||
# valid everything but target numbers
|
||||
'instance': plugins.NotifyNexmo,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'nexmo://A...f:****@',
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}'.format('h' * 8, 'i' * 16, '5' * 11), {
|
||||
# using phone no with no target - we text ourselves in
|
||||
# this case
|
||||
'instance': plugins.NotifyNexmo,
|
||||
}),
|
||||
('nexmo://_?key=AC{}&secret={}&from={}'.format(
|
||||
'a' * 8, 'b' * 16, '5' * 11), {
|
||||
# use get args to acomplish the same thing
|
||||
'instance': plugins.NotifyNexmo,
|
||||
}),
|
||||
('nexmo://_?key=AC{}&secret={}&source={}'.format(
|
||||
'a' * 8, 'b' * 16, '5' * 11), {
|
||||
# use get args to acomplish the same thing (use source instead of from)
|
||||
'instance': plugins.NotifyNexmo,
|
||||
}),
|
||||
('nexmo://_?key=AC{}&secret={}&from={}&to={}'.format(
|
||||
'a' * 8, 'b' * 16, '5' * 11, '7' * 13), {
|
||||
# use to=
|
||||
'instance': plugins.NotifyNexmo,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), {
|
||||
'instance': plugins.NotifyNexmo,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('nexmo://AC{}:{}@{}'.format('a' * 8, 'b' * 16, '6' * 11), {
|
||||
'instance': plugins.NotifyNexmo,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_nexmo_urls():
|
||||
"""
|
||||
NotifyNexmo() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_nexmo_edge_cases(mock_post):
|
||||
"""
|
||||
NotifyNexmo() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare our response
|
||||
response = requests.Request()
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
apikey = 'AC{}'.format('b' * 8)
|
||||
secret = '{}'.format('b' * 16)
|
||||
source = '+1 (555) 123-3456'
|
||||
|
||||
# No apikey specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyNexmo(apikey=None, secret=secret, source=source)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyNexmo(apikey=" ", secret=secret, source=source)
|
||||
|
||||
# No secret specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyNexmo(apikey=apikey, secret=None, source=source)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyNexmo(apikey=apikey, secret=" ", source=source)
|
||||
|
||||
# a error response
|
||||
response.status_code = 400
|
||||
response.content = dumps({
|
||||
'code': 21211,
|
||||
'message': "The 'To' number +1234567 is not a valid phone number.",
|
||||
})
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize our object
|
||||
obj = plugins.NotifyNexmo(
|
||||
apikey=apikey, secret=secret, source=source)
|
||||
|
||||
# We will fail with the above error code
|
||||
assert obj.notify('title', 'body', 'info') is False
|
161
test/test_plugin_nextcloud.py
Normal file
161
test/test_plugin_nextcloud.py
Normal file
@ -0,0 +1,161 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyNextcloud
|
||||
##################################
|
||||
('ncloud://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('ncloud://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('nclouds://', {
|
||||
# No hostname
|
||||
'instance': None,
|
||||
}),
|
||||
('ncloud://localhost', {
|
||||
# No user specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2&version=invalid', {
|
||||
# An invalid version was specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2&version=0', {
|
||||
# An invalid version was specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2&version=-23', {
|
||||
# An invalid version was specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ncloud://localhost/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user@localhost/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2&version=20', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user@localhost?to=user1,user2&version=21', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user:pass@localhost/user1/user2', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'ncloud://user:****@localhost/user1/user2',
|
||||
}),
|
||||
('ncloud://user:pass@localhost:8080/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('nclouds://user:pass@localhost/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'nclouds://user:****@localhost/admin',
|
||||
}),
|
||||
('nclouds://user:pass@localhost:8080/admin/', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://localhost:8080/admin?-HeaderKey=HeaderValue', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
}),
|
||||
('ncloud://user:pass@localhost:8081/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('ncloud://user:pass@localhost:8082/admin', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('ncloud://user:pass@localhost:8083/user1/user2/user3', {
|
||||
'instance': plugins.NotifyNextcloud,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_nextcloud_urls():
|
||||
"""
|
||||
NotifyNextcloud() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_nextcloud_edge_cases(mock_post):
|
||||
"""
|
||||
NotifyNextcloud() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# A response
|
||||
robj = mock.Mock()
|
||||
robj.content = ''
|
||||
robj.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = robj
|
||||
|
||||
# Variation Initializations
|
||||
obj = plugins.NotifyNextcloud(
|
||||
host="localhost", user="admin", password="pass", targets="user")
|
||||
assert isinstance(obj, plugins.NotifyNextcloud) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# An empty body
|
||||
assert obj.send(body="") is True
|
||||
assert 'data' in mock_post.call_args_list[0][1]
|
||||
assert 'shortMessage' in mock_post.call_args_list[0][1]['data']
|
||||
# The longMessage argument is not set
|
||||
assert 'longMessage' not in mock_post.call_args_list[0][1]['data']
|
145
test/test_plugin_notica.py
Normal file
145
test/test_plugin_notica.py
Normal file
@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('notica://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('notica://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Native URL
|
||||
('https://notica.us/?%s' % ('z' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notica://z...z/',
|
||||
}),
|
||||
# Native URL with additional arguments
|
||||
('https://notica.us/?%s&overflow=upstream' % ('z' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notica://z...z/',
|
||||
}),
|
||||
# Token specified
|
||||
('notica://%s' % ('a' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notica://a...a/',
|
||||
}),
|
||||
# Self-Hosted configuration
|
||||
('notica://localhost/%s' % ('b' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('notica://user@localhost/%s' % ('c' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('notica://user:pass@localhost/%s/' % ('d' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notica://user:****@localhost/d...d',
|
||||
}),
|
||||
('notica://user:pass@localhost/a/path/%s/' % ('r' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notica://user:****@localhost/a/path/r...r',
|
||||
}),
|
||||
('notica://localhost:8080/%s' % ('a' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('notica://user:pass@localhost:8080/%s' % ('b' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('noticas://localhost/%s' % ('j' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
'privacy_url': 'noticas://localhost/j...j',
|
||||
}),
|
||||
('noticas://user:pass@localhost/%s' % ('e' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'noticas://user:****@localhost/e...e',
|
||||
}),
|
||||
('noticas://localhost:8080/path/%s' % ('5' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
'privacy_url': 'noticas://localhost:8080/path/5...5',
|
||||
}),
|
||||
('noticas://user:pass@localhost:8080/%s' % ('6' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('notica://%s' % ('b' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Test Header overrides
|
||||
('notica://localhost:8080//%s/?+HeaderKey=HeaderValue' % ('7' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
# Test Depricated Header overrides
|
||||
('notica://localhost:8080//%s/?-HeaderKey=HeaderValue' % ('7' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
}),
|
||||
('notica://%s' % ('c' * 6), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('notica://%s' % ('d' * 7), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('notica://%s' % ('e' * 8), {
|
||||
'instance': plugins.NotifyNotica,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_notica_urls():
|
||||
"""
|
||||
NotifyNotica() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
121
test/test_plugin_notifico.py
Normal file
121
test/test_plugin_notifico.py
Normal file
@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('notifico://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('notifico://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('notifico://1234', {
|
||||
# Just a project id provided (no message token)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('notifico://abcd/ckhrjW8w672m6HG', {
|
||||
# an invalid project id provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG', {
|
||||
# A project id and message hook provided
|
||||
'instance': plugins.NotifyNotifico,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?prefix=no', {
|
||||
# Disable our prefix
|
||||
'instance': plugins.NotifyNotifico,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=yes', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
'notify_type': 'info',
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=yes', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
'notify_type': 'success',
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=yes', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
'notify_type': 'warning',
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=yes', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
'notify_type': 'failure',
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=yes', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
'notify_type': 'invalid',
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG?color=no', {
|
||||
# Test our color flag by having it set to off
|
||||
'instance': plugins.NotifyNotifico,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'notifico://1...4/c...G',
|
||||
}),
|
||||
# Support Native URLs
|
||||
('https://n.tkte.ch/h/2144/uJmKaBW9WFk42miB146ci3Kj', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('notifico://1234/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyNotifico,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_notifico_urls():
|
||||
"""
|
||||
NotifyNotifico() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -32,6 +32,7 @@ from datetime import datetime
|
||||
from json import dumps
|
||||
from apprise import Apprise
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
@ -40,11 +41,151 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyOffice365
|
||||
##################################
|
||||
('o365://', {
|
||||
# Missing tenant, client_id, secret, and targets!
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('o365://:@/', {
|
||||
# invalid url
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
# invalid tenant
|
||||
tenant=',',
|
||||
cid='ab-cd-ef-gh',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
|
||||
# We're valid and good to go
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
tenant='tenant',
|
||||
# invalid client id
|
||||
cid='ab.',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
|
||||
# We're valid and good to go
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
tenant='tenant',
|
||||
cid='ab-cd-ef-gh',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
|
||||
# We're valid and good to go
|
||||
'instance': plugins.NotifyOffice365,
|
||||
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'expires_in': 2000,
|
||||
'access_token': 'abcd1234',
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'o365://t...t:user@example.com/a...h/' \
|
||||
'****/email1%40test.ca/'}),
|
||||
# test our arguments
|
||||
('o365://_/?oauth_id={cid}&oauth_secret={secret}&tenant={tenant}'
|
||||
'&to={targets}&from={aid}'.format(
|
||||
tenant='tenant',
|
||||
cid='ab-cd-ef-gh',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test',
|
||||
targets='email1@test.ca'),
|
||||
{
|
||||
# We're valid and good to go
|
||||
'instance': plugins.NotifyOffice365,
|
||||
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'expires_in': 2000,
|
||||
'access_token': 'abcd1234',
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'o365://t...t:user@example.com/a...h/' \
|
||||
'****/email1%40test.ca/'}),
|
||||
# Test invalid JSON (no tenant defaults to email domain)
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
tenant='tenant',
|
||||
cid='ab-cd-ef-gh',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
|
||||
# We're valid and good to go
|
||||
'instance': plugins.NotifyOffice365,
|
||||
|
||||
# invalid JSON response
|
||||
'requests_response_text': '{',
|
||||
'notify_response': False,
|
||||
}),
|
||||
# No Targets specified
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}'.format(
|
||||
tenant='tenant',
|
||||
cid='ab-cd-ef-gh',
|
||||
aid='user@example.com',
|
||||
secret='abcd/123/3343/@jack/test'), {
|
||||
|
||||
# We're valid and good to go
|
||||
'instance': plugins.NotifyOffice365,
|
||||
|
||||
# There were no targets to notify; so we use our own email
|
||||
'requests_response_text': {
|
||||
'expires_in': 2000,
|
||||
'access_token': 'abcd1234',
|
||||
},
|
||||
}),
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
tenant='tenant',
|
||||
cid='zz-zz-zz-zz',
|
||||
aid='user@example.com',
|
||||
secret='abcd/abc/dcba/@john/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
'instance': plugins.NotifyOffice365,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('o365://{tenant}:{aid}/{cid}/{secret}/{targets}'.format(
|
||||
tenant='tenant',
|
||||
cid='01-12-23-34',
|
||||
aid='user@example.com',
|
||||
secret='abcd/321/4321/@test/test',
|
||||
targets='/'.join(['email1@test.ca'])), {
|
||||
'instance': plugins.NotifyOffice365,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_office365_urls():
|
||||
"""
|
||||
NotifyOffice365() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_office365_general(mock_post):
|
||||
def test_plugin_office365_general(mock_post):
|
||||
"""
|
||||
API: NotifyOffice365 Testing
|
||||
NotifyOffice365() General Testing
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -160,9 +301,9 @@ def test_office365_general(mock_post):
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_office365_authentication(mock_post):
|
||||
def test_plugin_office365_authentication(mock_post):
|
||||
"""
|
||||
API: NotifyOffice365 Authentication Testing
|
||||
NotifyOffice365() Authentication Testing
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
125
test/test_plugin_onesignal.py
Normal file
125
test/test_plugin_onesignal.py
Normal file
@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('onesignal://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('onesignal://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('onesignal://apikey/', {
|
||||
# no app id specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('onesignal://appid@%20%20/', {
|
||||
# invalid apikey
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('onesignal://appid@apikey/playerid/?lang=X', {
|
||||
# invalid language id (must be 2 characters)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('onesignal://appid@apikey/', {
|
||||
# No targets specified; we will initialize but not notify anything
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
'notify_response': False,
|
||||
}),
|
||||
('onesignal://appid@apikey/playerid', {
|
||||
# Valid playerid
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
'privacy_url': 'onesignal://a...d@a...y/playerid',
|
||||
}),
|
||||
('onesignal://appid@apikey/player', {
|
||||
# Valid player id
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('onesignal://appid@apikey/@user?image=no', {
|
||||
# Valid userid, no image
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey/user@email.com/#seg/player/@user/%20/a', {
|
||||
# Valid email, valid playerid, valid user, invalid entry (%20),
|
||||
# and too short of an entry (a)
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey?to=#segment,playerid', {
|
||||
# Test to=
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey/#segment/@user/?batch=yes', {
|
||||
# Test batch=
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey/#segment/@user/?batch=no', {
|
||||
# Test batch=
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://templateid:appid@apikey/playerid', {
|
||||
# Test Template ID
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey/playerid/?lang=es&subtitle=Sub', {
|
||||
# Test Language and Subtitle Over-ride
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://?apikey=abc&template=tp&app=123&to=playerid', {
|
||||
# Test Kwargs
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
}),
|
||||
('onesignal://appid@apikey/#segment/playerid/', {
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('onesignal://appid@apikey/#segment/playerid/', {
|
||||
'instance': plugins.NotifyOneSignal,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_onesignal_urls():
|
||||
"""
|
||||
NotifyOneSignal() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
135
test/test_plugin_opsgenie.py
Normal file
135
test/test_plugin_opsgenie.py
Normal file
@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('opsgenie://', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('opsgenie://:@/', {
|
||||
# We failed to identify any valid authentication
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('opsgenie://%20%20/', {
|
||||
# invalid apikey specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('opsgenie://apikey/user/?region=xx', {
|
||||
# invalid region id
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('opsgenie://apikey/', {
|
||||
# No targets specified; this is allowed
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/user', {
|
||||
# Valid user
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
'privacy_url': 'opsgenie://a...y/%40user',
|
||||
}),
|
||||
('opsgenie://apikey/@user?region=eu', {
|
||||
# European Region
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/@user?entity=A%20Entity', {
|
||||
# Assign an entity
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/@user?alias=An%20Alias', {
|
||||
# Assign an alias
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/@user?priority=p3', {
|
||||
# Assign our priority
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/?tags=comma,separated', {
|
||||
# Test our our 'tags' (tag is reserved in Apprise) but not 'tags'
|
||||
# Also test the fact we do not need to define a target
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/@user?priority=invalid', {
|
||||
# Invalid priority (loads using default)
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/user@email.com/#team/*sche/^esc/%20/a', {
|
||||
# Valid user (email), valid schedule, Escalated ID,
|
||||
# an invalid entry (%20), and too short of an entry (a)
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/{}/@{}/#{}/*{}/^{}/'.format(
|
||||
UUID4, UUID4, UUID4, UUID4, UUID4), {
|
||||
# similar to the above, except we use the UUID's
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey?to=#team,user&+key=value&+type=override', {
|
||||
# Test to= and details (key/value pair) also override 'type'
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/#team/@user/?batch=yes', {
|
||||
# Test batch=
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/#team/@user/?batch=no', {
|
||||
# Test batch=
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://?apikey=abc&to=user', {
|
||||
# Test Kwargs
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
}),
|
||||
('opsgenie://apikey/#team/user/', {
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('opsgenie://apikey/#topic1/device/', {
|
||||
'instance': plugins.NotifyOpsgenie,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_opsgenie_urls():
|
||||
"""
|
||||
NotifyOpsgenie() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
101
test/test_plugin_parse_platform.py
Normal file
101
test/test_plugin_parse_platform.py
Normal file
@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('parsep://', {
|
||||
'instance': None,
|
||||
}),
|
||||
# API Key + bad url
|
||||
('parsep://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# APIkey; no app_id or master_key
|
||||
('parsep://%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey; no master_key
|
||||
('parsep://app_id@%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey; no app_id
|
||||
('parseps://:master_key@%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# app_id + master_key (using arguments=)
|
||||
('parseps://localhost?app_id=%s&master_key=%s' % ('a' * 32, 'd' * 32), {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'parseps://a...a:d...d@localhost',
|
||||
}),
|
||||
# Set a device id + custom port
|
||||
('parsep://app_id:master_key@localhost:8080?device=ios', {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
}),
|
||||
# invalid device id
|
||||
('parsep://app_id:master_key@localhost?device=invalid', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Normal Query
|
||||
('parseps://app_id:master_key@localhost', {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
}),
|
||||
('parseps://app_id:master_key@localhost', {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('parseps://app_id:master_key@localhost', {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('parseps://app_id:master_key@localhost', {
|
||||
'instance': plugins.NotifyParsePlatform,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_parse_platform_urls():
|
||||
"""
|
||||
NotifyParsePlatform() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
93
test/test_plugin_popcorn_notify.py
Normal file
93
test/test_plugin_popcorn_notify.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('popcorn://', {
|
||||
# No hostname/apikey specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('popcorn://{}/18001231234'.format('_' * 9), {
|
||||
# invalid apikey
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('popcorn://{}/1232348923489234923489234289-32423'.format('a' * 9), {
|
||||
# invalid phone number
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
'notify_response': False,
|
||||
}),
|
||||
('popcorn://{}/abc'.format('b' * 9), {
|
||||
# invalid email
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
'notify_response': False,
|
||||
}),
|
||||
('popcorn://{}/15551232000/user@example.com'.format('c' * 9), {
|
||||
# value phone and email
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
}),
|
||||
('popcorn://{}/15551232000/user@example.com?batch=yes'.format('w' * 9), {
|
||||
# value phone and email with batch mode set
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
}),
|
||||
('popcorn://{}/?to=15551232000'.format('w' * 9), {
|
||||
# reference to to=
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
}),
|
||||
('popcorn://{}/15551232000'.format('x' * 9), {
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('popcorn://{}/15551232000'.format('y' * 9), {
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('popcorn://{}/15551232000'.format('z' * 9), {
|
||||
'instance': plugins.NotifyPopcornNotify,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_popcorn_notify_urls():
|
||||
"""
|
||||
NotifyPopcornNotify() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
142
test/test_plugin_prowl.py
Normal file
142
test/test_plugin_prowl.py
Normal file
@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('prowl://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# bad url
|
||||
('prowl://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid API Key
|
||||
('prowl://%s' % ('a' * 20), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provider Key
|
||||
('prowl://%s/%s' % ('a' * 40, 'b' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
# Invalid Provider Key
|
||||
('prowl://%s/%s' % ('a' * 40, 'b' * 20), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey; no device
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
# API Key
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# API Key + priority setting
|
||||
('prowl://%s?priority=high' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
# API Key + invalid priority setting
|
||||
('prowl://%s?priority=invalid' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
# API Key + priority setting (empty)
|
||||
('prowl://%s?priority=' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
# API Key + No Provider Key (empty)
|
||||
('prowl://%s///' % ('w' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'prowl://w...w/',
|
||||
}),
|
||||
# API Key + Provider Key
|
||||
('prowl://%s/%s' % ('a' * 40, 'b' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'prowl://a...a/b...b',
|
||||
}),
|
||||
# API Key + with image
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
}),
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('prowl://%s' % ('a' * 40), {
|
||||
'instance': plugins.NotifyProwl,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_prowl():
|
||||
"""
|
||||
NotifyProwl() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_prowl_edge_cases():
|
||||
"""
|
||||
NotifyProwl() Edge Cases
|
||||
|
||||
"""
|
||||
# Initializes the plugin with an invalid apikey
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyProwl(apikey=None)
|
||||
# Whitespace also acts as an invalid apikey value
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyProwl(apikey=' ')
|
||||
|
||||
# Whitespace also acts as an invalid provider key
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyProwl(apikey='abcd', providerkey=object())
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyProwl(apikey='abcd', providerkey=' ')
|
378
test/test_plugin_pushbullet.py
Normal file
378
test/test_plugin_pushbullet.py
Normal file
@ -0,0 +1,378 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from json import dumps
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('pbul://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('pbul://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# APIkey; but support attachment response
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'file_name': 'cat.jpeg',
|
||||
'file_type': 'image/jpeg',
|
||||
'file_url': 'http://file_url',
|
||||
'upload_url': 'http://upload_url',
|
||||
},
|
||||
}),
|
||||
# APIkey; attachment testing that isn't an image type
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'file_name': 'test.pdf',
|
||||
'file_type': 'application/pdf',
|
||||
'file_url': 'http://file_url',
|
||||
'upload_url': 'http://upload_url',
|
||||
},
|
||||
}),
|
||||
# APIkey; attachment testing were expected entry in payload is missing
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
|
||||
# Test what happens if a batch send fails to return a messageCount
|
||||
'requests_response_text': {
|
||||
'file_name': 'test.pdf',
|
||||
'file_type': 'application/pdf',
|
||||
'file_url': 'http://file_url',
|
||||
# upload_url missing
|
||||
},
|
||||
# Our Notification calls associated with attachments will fail:
|
||||
'attach_response': False,
|
||||
}),
|
||||
# API Key + channel
|
||||
('pbul://%s/#channel/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + channel (via to=
|
||||
('pbul://%s/?to=#channel' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + 2 channels
|
||||
('pbul://%s/#channel1/#channel2' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'pbul://a...a/',
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + device
|
||||
('pbul://%s/device/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + 2 devices
|
||||
('pbul://%s/device1/device2/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + email
|
||||
('pbul://%s/user@example.com/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + 2 emails
|
||||
('pbul://%s/user@example.com/abc@def.com/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# API Key + Combo
|
||||
('pbul://%s/device/#channel/user@example.com/' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
# ,
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
('pbul://%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushBullet,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
'check_attachments': False,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_pushbullet_urls():
|
||||
"""
|
||||
NotifyPushBullet() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushbullet_attachments(mock_post):
|
||||
"""
|
||||
NotifyPushBullet() Attachment Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyPushBullet.request_rate_per_sec = 0
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
access_token = 't' * 32
|
||||
|
||||
# Prepare Mock return object
|
||||
response = mock.Mock()
|
||||
response.content = dumps({
|
||||
"file_name": "cat.jpg",
|
||||
"file_type": "image/jpeg",
|
||||
"file_url": "https://dl.pushb.com/abc/cat.jpg",
|
||||
"upload_url": "https://upload.pushbullet.com/abcd123",
|
||||
})
|
||||
response.status_code = requests.codes.ok
|
||||
mock_post.return_value = response
|
||||
|
||||
# prepare our attachment
|
||||
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
|
||||
# Test our markdown
|
||||
obj = Apprise.instantiate(
|
||||
'pbul://{}/?format=markdown'.format(access_token))
|
||||
|
||||
# Send a good attachment
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 4
|
||||
# Image Prep
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'https://api.pushbullet.com/v2/upload-request'
|
||||
assert mock_post.call_args_list[1][0][0] == \
|
||||
'https://upload.pushbullet.com/abcd123'
|
||||
# Message
|
||||
assert mock_post.call_args_list[2][0][0] == \
|
||||
'https://api.pushbullet.com/v2/pushes'
|
||||
# Image Send
|
||||
assert mock_post.call_args_list[3][0][0] == \
|
||||
'https://api.pushbullet.com/v2/pushes'
|
||||
|
||||
# Reset our mock object
|
||||
mock_post.reset_mock()
|
||||
|
||||
# Add another attachment so we drop into the area of the PushBullet code
|
||||
# that sends remaining attachments (if more detected)
|
||||
attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
|
||||
# Send our attachments
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 7
|
||||
# Image Prep
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'https://api.pushbullet.com/v2/upload-request'
|
||||
assert mock_post.call_args_list[1][0][0] == \
|
||||
'https://upload.pushbullet.com/abcd123'
|
||||
assert mock_post.call_args_list[2][0][0] == \
|
||||
'https://api.pushbullet.com/v2/upload-request'
|
||||
assert mock_post.call_args_list[3][0][0] == \
|
||||
'https://upload.pushbullet.com/abcd123'
|
||||
# Message
|
||||
assert mock_post.call_args_list[4][0][0] == \
|
||||
'https://api.pushbullet.com/v2/pushes'
|
||||
# Image Send
|
||||
assert mock_post.call_args_list[5][0][0] == \
|
||||
'https://api.pushbullet.com/v2/pushes'
|
||||
assert mock_post.call_args_list[6][0][0] == \
|
||||
'https://api.pushbullet.com/v2/pushes'
|
||||
|
||||
# Reset our mock object
|
||||
mock_post.reset_mock()
|
||||
|
||||
# An invalid attachment will cause a failure
|
||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||
attach = AppriseAttachment(path)
|
||||
assert obj.notify(body="test", attach=attach) is False
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 0
|
||||
|
||||
# prepare our attachment
|
||||
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
|
||||
# Prepare a bad response
|
||||
bad_response = mock.Mock()
|
||||
bad_response.content = dumps({
|
||||
"file_name": "cat.jpg",
|
||||
"file_type": "image/jpeg",
|
||||
"file_url": "https://dl.pushb.com/abc/cat.jpg",
|
||||
"upload_url": "https://upload.pushbullet.com/abcd123",
|
||||
})
|
||||
bad_response.status_code = requests.codes.internal_server_error
|
||||
|
||||
# Prepare a bad response
|
||||
bad_json_response = mock.Mock()
|
||||
bad_json_response.content = '}'
|
||||
bad_json_response.status_code = requests.codes.ok
|
||||
|
||||
# Throw an exception on the first call to requests.post()
|
||||
mock_post.return_value = None
|
||||
for side_effect in (requests.RequestException(), OSError(), bad_response):
|
||||
mock_post.side_effect = side_effect
|
||||
|
||||
# We'll fail now because of our error handling
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
# Throw an exception on the second call to requests.post()
|
||||
for side_effect in (requests.RequestException(), OSError(), bad_response):
|
||||
mock_post.side_effect = [response, side_effect]
|
||||
|
||||
# We'll fail now because of our error handling
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
# Throw an exception on the third call to requests.post()
|
||||
for side_effect in (requests.RequestException(), OSError(), bad_response):
|
||||
mock_post.side_effect = [response, response, side_effect]
|
||||
|
||||
# We'll fail now because of our error handling
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
# Throw an exception on the forth call to requests.post()
|
||||
for side_effect in (requests.RequestException(), OSError(), bad_response):
|
||||
mock_post.side_effect = [response, response, response, side_effect]
|
||||
|
||||
# We'll fail now because of our error handling
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
# Test case where we don't get a valid response back
|
||||
mock_post.side_effect = bad_json_response
|
||||
|
||||
# We'll fail because of an invalid json object
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushbullet_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyPushBullet() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
accesstoken = 'a' * 32
|
||||
|
||||
# Support strings
|
||||
recipients = '#chan1,#chan2,device,user@example.com,,,'
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
|
||||
# Invalid Access Token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushBullet(accesstoken=None)
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushBullet(accesstoken=" ")
|
||||
|
||||
obj = plugins.NotifyPushBullet(
|
||||
accesstoken=accesstoken, targets=recipients)
|
||||
assert isinstance(obj, plugins.NotifyPushBullet) is True
|
||||
assert len(obj.targets) == 4
|
||||
|
||||
obj = plugins.NotifyPushBullet(accesstoken=accesstoken)
|
||||
assert isinstance(obj, plugins.NotifyPushBullet) is True
|
||||
# Default is to send to all devices, so there will be a
|
||||
# recipient here
|
||||
assert len(obj.targets) == 1
|
||||
|
||||
obj = plugins.NotifyPushBullet(accesstoken=accesstoken, targets=set())
|
||||
assert isinstance(obj, plugins.NotifyPushBullet) is True
|
||||
# Default is to send to all devices, so there will be a
|
||||
# recipient here
|
||||
assert len(obj.targets) == 1
|
218
test/test_plugin_pushed.py
Normal file
218
test/test_plugin_pushed.py
Normal file
@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('pushed://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Application Key Only
|
||||
('pushed://%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid URL
|
||||
('pushed://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Application Key+Secret
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# Application Key+Secret + channel
|
||||
('pushed://%s/%s/#channel/' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# Application Key+Secret + channel (via to=)
|
||||
('pushed://%s/%s?to=channel' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'pushed://a...a/****/',
|
||||
}),
|
||||
# Application Key+Secret + dropped entry
|
||||
('pushed://%s/%s/dropped_value/' % ('a' * 32, 'a' * 64), {
|
||||
# No entries validated is a fail
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Application Key+Secret + 2 channels
|
||||
('pushed://%s/%s/#channel1/#channel2' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# Application Key+Secret + User Pushed ID
|
||||
('pushed://%s/%s/@ABCD/' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# Application Key+Secret + 2 devices
|
||||
('pushed://%s/%s/@ABCD/@DEFG/' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# Application Key+Secret + Combo
|
||||
('pushed://%s/%s/@ABCD/#channel' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
}),
|
||||
# ,
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pushed://%s/%s/#channel' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pushed://%s/%s/@user' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
|
||||
'instance': plugins.NotifyPushed,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_pushed_urls():
|
||||
"""
|
||||
NotifyPushed() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushed_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyPushed() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Chat ID
|
||||
recipients = '@ABCDEFG, @DEFGHIJ, #channel, #channel2'
|
||||
|
||||
# Some required input
|
||||
app_key = 'ABCDEFG'
|
||||
app_secret = 'ABCDEFG'
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
|
||||
# No application Key specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushed(
|
||||
app_key=None,
|
||||
app_secret=app_secret,
|
||||
recipients=None,
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushed(
|
||||
app_key=" ",
|
||||
app_secret=app_secret,
|
||||
recipients=None,
|
||||
)
|
||||
# No application Secret specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushed(
|
||||
app_key=app_key,
|
||||
app_secret=None,
|
||||
recipients=None,
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushed(
|
||||
app_key=app_key,
|
||||
app_secret=" ",
|
||||
)
|
||||
|
||||
# recipients list set to (None) is perfectly fine; in this case it will
|
||||
# notify the App
|
||||
obj = plugins.NotifyPushed(
|
||||
app_key=app_key,
|
||||
app_secret=app_secret,
|
||||
recipients=None,
|
||||
)
|
||||
assert isinstance(obj, plugins.NotifyPushed) is True
|
||||
assert len(obj.channels) == 0
|
||||
assert len(obj.users) == 0
|
||||
|
||||
obj = plugins.NotifyPushed(
|
||||
app_key=app_key,
|
||||
app_secret=app_secret,
|
||||
targets=recipients,
|
||||
)
|
||||
assert isinstance(obj, plugins.NotifyPushed) is True
|
||||
assert len(obj.channels) == 2
|
||||
assert len(obj.users) == 2
|
||||
|
||||
# Prepare Mock to fail
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
112
test/test_plugin_pushjet.py
Normal file
112
test/test_plugin_pushjet.py
Normal file
@ -0,0 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('pjet://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjets://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# You must specify a secret key
|
||||
('pjet://%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# The proper way to log in
|
||||
('pjet://user:pass@localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# The proper way to log in
|
||||
('pjets://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# Specify your own server with login (secret= MUST be provided)
|
||||
('pjet://user:pass@localhost?secret=%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'pjet://user:****@localhost',
|
||||
}),
|
||||
# Specify your own server with port
|
||||
('pjets://localhost:8080/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
('pjets://localhost:8080/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('pjets://localhost:4343/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pjet://localhost:8081/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_pushjet_urls():
|
||||
"""
|
||||
NotifyPushjet() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_pushjet_edge_cases():
|
||||
"""
|
||||
NotifyPushjet() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# No application Key specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushjet(secret_key=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushjet(secret_key=" ")
|
370
test/test_plugin_pushover.py
Normal file
370
test/test_plugin_pushover.py
Normal file
@ -0,0 +1,370 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import mock
|
||||
import requests
|
||||
import pytest
|
||||
from json import dumps
|
||||
from apprise import Apprise
|
||||
from apprise import NotifyType
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('pover://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# bad url
|
||||
('pover://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey; no user
|
||||
('pover://%s' % ('a' * 30), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# API Key + invalid sound setting
|
||||
('pover://%s@%s?sound=invalid' % ('u' * 30, 'a' * 30), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# API Key + valid alternate sound picked
|
||||
('pover://%s@%s?sound=spacealarm' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + valid url_title with url
|
||||
('pover://%s@%s?url=my-url&url_title=title' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + Valid User
|
||||
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# API Key + Valid User + 1 Device
|
||||
('pover://%s@%s/DEVICE' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + Valid User + 1 Device (via to=)
|
||||
('pover://%s@%s?to=DEVICE' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + Valid User + 2 Devices
|
||||
('pover://%s@%s/DEVICE1/DEVICE2/' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'pover://u...u@a...a',
|
||||
}),
|
||||
# API Key + Valid User + invalid device
|
||||
('pover://%s@%s/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# Notify will return False since there is a bad device in our list
|
||||
'response': False,
|
||||
}),
|
||||
# API Key + Valid User + device + invalid device
|
||||
('pover://%s@%s/DEVICE1/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# Notify will return False since there is a bad device in our list
|
||||
'response': False,
|
||||
}),
|
||||
# API Key + priority setting
|
||||
('pover://%s@%s?priority=high' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + priority setting + html mode
|
||||
('pover://%s@%s?priority=high&format=html' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + invalid priority setting
|
||||
('pover://%s@%s?priority=invalid' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + emergency(2) priority setting
|
||||
('pover://%s@%s?priority=emergency' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + emergency priority setting with retry and expire
|
||||
('pover://%s@%s?priority=emergency&%s&%s' % ('u' * 30,
|
||||
'a' * 30,
|
||||
'retry=30',
|
||||
'expire=300'), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + emergency priority setting with text retry
|
||||
('pover://%s@%s?priority=emergency&%s&%s' % ('u' * 30,
|
||||
'a' * 30,
|
||||
'retry=invalid',
|
||||
'expire=300'), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + emergency priority setting with text expire
|
||||
('pover://%s@%s?priority=emergency&%s&%s' % ('u' * 30,
|
||||
'a' * 30,
|
||||
'retry=30',
|
||||
'expire=invalid'), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
# API Key + emergency priority setting with invalid expire
|
||||
('pover://%s@%s?priority=emergency&%s' % ('u' * 30,
|
||||
'a' * 30,
|
||||
'expire=100000'), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# API Key + emergency priority setting with invalid retry
|
||||
('pover://%s@%s?priority=emergency&%s' % ('u' * 30,
|
||||
'a' * 30,
|
||||
'retry=15'), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# API Key + priority setting (empty)
|
||||
('pover://%s@%s?priority=' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
}),
|
||||
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
||||
'instance': plugins.NotifyPushover,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_pushover_urls():
|
||||
"""
|
||||
NotifyPushover() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushover_attachments(mock_post, tmpdir):
|
||||
"""
|
||||
NotifyPushover() Attachment Checks
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
user_key = 'u' * 30
|
||||
api_token = 'a' * 30
|
||||
|
||||
# Prepare a good response
|
||||
response = mock.Mock()
|
||||
response.content = dumps(
|
||||
{"status": 1, "request": "647d2300-702c-4b38-8b2f-d56326ae460b"})
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare a bad response
|
||||
bad_response = mock.Mock()
|
||||
response.content = dumps(
|
||||
{"status": 1, "request": "647d2300-702c-4b38-8b2f-d56326ae460b"})
|
||||
bad_response.status_code = requests.codes.internal_server_error
|
||||
|
||||
# Assign our good response
|
||||
mock_post.return_value = response
|
||||
|
||||
# prepare our attachment
|
||||
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
|
||||
# Instantiate our object
|
||||
obj = Apprise.instantiate(
|
||||
'pover://{}@{}/'.format(user_key, api_token))
|
||||
assert isinstance(obj, plugins.NotifyPushover)
|
||||
|
||||
# Test our attachment
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 1
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'https://api.pushover.net/1/messages.json'
|
||||
|
||||
# Reset our mock object for multiple tests
|
||||
mock_post.reset_mock()
|
||||
|
||||
# Test multiple attachments
|
||||
assert attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 2
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'https://api.pushover.net/1/messages.json'
|
||||
assert mock_post.call_args_list[1][0][0] == \
|
||||
'https://api.pushover.net/1/messages.json'
|
||||
|
||||
# Reset our mock object for multiple tests
|
||||
mock_post.reset_mock()
|
||||
|
||||
image = tmpdir.mkdir("pover_image").join("test.jpg")
|
||||
image.write('a' * plugins.NotifyPushover.attach_max_size_bytes)
|
||||
|
||||
attach = AppriseAttachment.instantiate(str(image))
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 1
|
||||
assert mock_post.call_args_list[0][0][0] == \
|
||||
'https://api.pushover.net/1/messages.json'
|
||||
|
||||
# Reset our mock object for multiple tests
|
||||
mock_post.reset_mock()
|
||||
|
||||
# Add 1 more byte to the file (putting it over the limit)
|
||||
image.write('a' * (plugins.NotifyPushover.attach_max_size_bytes + 1))
|
||||
|
||||
attach = AppriseAttachment.instantiate(str(image))
|
||||
assert obj.notify(body="test", attach=attach) is False
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 0
|
||||
|
||||
# Test case when file is missing
|
||||
attach = AppriseAttachment.instantiate(
|
||||
'file://{}?cache=False'.format(str(image)))
|
||||
os.unlink(str(image))
|
||||
assert obj.notify(
|
||||
body='body', title='title', attach=attach) is False
|
||||
|
||||
# Test our call count
|
||||
assert mock_post.call_count == 0
|
||||
|
||||
# Test unsuported files:
|
||||
image = tmpdir.mkdir("pover_unsupported").join("test.doc")
|
||||
image.write('a' * 256)
|
||||
attach = AppriseAttachment.instantiate(str(image))
|
||||
|
||||
# Content is silently ignored
|
||||
assert obj.notify(body="test", attach=attach) is True
|
||||
|
||||
# prepare our attachment
|
||||
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
|
||||
|
||||
# Throw an exception on the first call to requests.post()
|
||||
for side_effect in (requests.RequestException(), OSError(), bad_response):
|
||||
mock_post.side_effect = [side_effect, side_effect]
|
||||
|
||||
# We'll fail now because of our error handling
|
||||
assert obj.send(body="test", attach=attach) is False
|
||||
|
||||
# Same case without an attachment
|
||||
assert obj.send(body="test") is False
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushover_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyPushover() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# No token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushover(token=None)
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
token = 'a' * 30
|
||||
user_key = 'u' * 30
|
||||
|
||||
invalid_device = 'd' * 35
|
||||
|
||||
# Support strings
|
||||
devices = 'device1,device2,,,,%s' % invalid_device
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
|
||||
# No webhook id specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushover(user_key=user_key, webhook_id=None)
|
||||
|
||||
obj = plugins.NotifyPushover(
|
||||
user_key=user_key, token=token, targets=devices)
|
||||
assert isinstance(obj, plugins.NotifyPushover) is True
|
||||
assert len(obj.targets) == 3
|
||||
|
||||
# This call fails because there is 1 invalid device
|
||||
assert obj.notify(
|
||||
body='body', title='title',
|
||||
notify_type=NotifyType.INFO) is False
|
||||
|
||||
obj = plugins.NotifyPushover(user_key=user_key, token=token)
|
||||
assert isinstance(obj, plugins.NotifyPushover) is True
|
||||
# Default is to send to all devices, so there will be a
|
||||
# device defined here
|
||||
assert len(obj.targets) == 1
|
||||
|
||||
# This call succeeds because all of the devices are valid
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||
|
||||
obj = plugins.NotifyPushover(user_key=user_key, token=token, targets=set())
|
||||
assert isinstance(obj, plugins.NotifyPushover) is True
|
||||
# Default is to send to all devices, so there will be a
|
||||
# device defined here
|
||||
assert len(obj.targets) == 1
|
||||
|
||||
# No User Key specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushover(user_key=None, token="abcd")
|
||||
|
||||
# No Access Token specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushover(user_key="abcd", token=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushover(user_key="abcd", token=" ")
|
286
test/test_plugin_pushsafer.py
Normal file
286
test/test_plugin_pushsafer.py
Normal file
@ -0,0 +1,286 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import mock
|
||||
import requests
|
||||
from json import dumps
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import NotifyType
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('psafer://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('psafer://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('psafers://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('psafer://{}'.format('a' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# This will fail because we're also expecting a server acknowledgement
|
||||
'notify_response': False,
|
||||
}),
|
||||
('psafer://{}'.format('b' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# invalid JSON response
|
||||
'requests_response_text': '{',
|
||||
'notify_response': False,
|
||||
}),
|
||||
('psafer://{}'.format('c' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A failure has status set to zero
|
||||
# We also expect an 'error' flag to be set
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
'error': 'we failed'
|
||||
},
|
||||
'notify_response': False,
|
||||
}),
|
||||
('psafers://{}'.format('d' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
'notify_response': False,
|
||||
}),
|
||||
# This will notify all users ('a')
|
||||
('psafer://{}'.format('e' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A status of 1 is a success
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# This will notify a selected set of devices
|
||||
('psafer://{}/12/24/53'.format('e' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A status of 1 is a success
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# Same as above, but exercises the to= argument
|
||||
('psafer://{}?to=12,24,53'.format('e' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A status of 1 is a success
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# Set priority
|
||||
('psafer://{}?priority=emergency'.format('f' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# Support integer value too
|
||||
('psafer://{}?priority=-1'.format('f' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# Invalid priority
|
||||
('psafer://{}?priority=invalid'.format('f' * 20), {
|
||||
# Invalid Priority
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid priority
|
||||
('psafer://{}?priority=25'.format('f' * 20), {
|
||||
# Invalid Priority
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Set sound
|
||||
('psafer://{}?sound=ok'.format('g' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
}
|
||||
}),
|
||||
# Support integer value too
|
||||
('psafers://{}?sound=14'.format('g' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
},
|
||||
'privacy_url': 'psafers://g...g',
|
||||
}),
|
||||
# Invalid sound
|
||||
('psafer://{}?sound=invalid'.format('h' * 20), {
|
||||
# Invalid Sound
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('psafer://{}?sound=94000'.format('h' * 20), {
|
||||
# Invalid Sound
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Set vibration (integer only)
|
||||
('psafers://{}?vibration=1'.format('h' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
'requests_response_text': {
|
||||
'status': 1,
|
||||
},
|
||||
'privacy_url': 'psafers://h...h',
|
||||
}),
|
||||
# Invalid sound
|
||||
('psafer://{}?vibration=invalid'.format('h' * 20), {
|
||||
# Invalid Vibration
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid vibration
|
||||
('psafer://{}?vibration=25000'.format('h' * 20), {
|
||||
# Invalid Vibration
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('psafers://{}'.format('d' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('psafer://{}'.format('d' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('psafers://{}'.format('d' * 20), {
|
||||
'instance': plugins.NotifyPushSafer,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
# Throws a series of connection and transfer exceptions when this
|
||||
# flag is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_pushsafer_urls():
|
||||
"""
|
||||
NotifyPushSafer() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_pushsafer_general(mock_post):
|
||||
"""
|
||||
NotifyPushSafer() General Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyPushSafer.request_rate_per_sec = 0
|
||||
|
||||
# Private Key
|
||||
privatekey = 'abc123'
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_post.return_value.content = dumps({
|
||||
'status': 1,
|
||||
'success': "okay",
|
||||
})
|
||||
|
||||
# Exception should be thrown about the fact no private key was specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyPushSafer(privatekey=None)
|
||||
|
||||
# Multiple Attachment Support
|
||||
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||
attach = AppriseAttachment()
|
||||
for _ in range(0, 4):
|
||||
attach.add(path)
|
||||
|
||||
obj = plugins.NotifyPushSafer(privatekey=privatekey)
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is True
|
||||
|
||||
# Test error reading attachment from disk
|
||||
with mock.patch('io.open', side_effect=OSError):
|
||||
obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach)
|
||||
|
||||
# Test unsupported mime type
|
||||
attach = AppriseAttachment(path)
|
||||
|
||||
attach[0]._mimetype = 'application/octet-stream'
|
||||
|
||||
# We gracefully just don't send the attachment in these cases;
|
||||
# The notify itself will still be successful
|
||||
mock_post.reset_mock()
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is True
|
||||
|
||||
# the 'p', 'p2', and 'p3' are the data variables used when including an
|
||||
# image.
|
||||
assert 'data' in mock_post.call_args[1]
|
||||
assert 'p' not in mock_post.call_args[1]['data']
|
||||
assert 'p2' not in mock_post.call_args[1]['data']
|
||||
assert 'p3' not in mock_post.call_args[1]['data']
|
||||
|
||||
# Invalid file path
|
||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=path) is False
|
@ -27,6 +27,7 @@ import six
|
||||
import requests
|
||||
import mock
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
from json import dumps
|
||||
from datetime import datetime
|
||||
@ -36,11 +37,198 @@ from datetime import timedelta
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('reddit://', {
|
||||
# Missing all credentials
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user@app_id/app_secret/', {
|
||||
# No password
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user:password@app_id/', {
|
||||
# No app secret
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user:password@app_id/appsecret/apprise', {
|
||||
# No invalid app_id (has underscore)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user:password@app-id/app_secret/apprise', {
|
||||
# No invalid app_secret (has underscore)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret/apprise?kind=invalid', {
|
||||
# An Invalid Kind
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret/apprise', {
|
||||
# Login failed
|
||||
'instance': plugins.NotifyReddit,
|
||||
# Expected notify() response is False because internally we would
|
||||
# have failed to login
|
||||
'notify_response': False,
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret', {
|
||||
# Login successful, but there was no subreddit to notify
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Expected notify() response is False
|
||||
'notify_response': False,
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret/apprise', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# Identify an error
|
||||
"errors": [('KEY', 'DESC', 'INFO'), ],
|
||||
},
|
||||
},
|
||||
# Expected notify() response is False because the
|
||||
# reddit server provided us errors
|
||||
'notify_response': False,
|
||||
}),
|
||||
|
||||
('reddit://user:password@app-id/app-secret/apprise', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
# Test case where 'expires_in' entry is missing
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'reddit://user:****@****/****/apprise',
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret/apprise/subreddit2', {
|
||||
# password:login acceptable
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url':
|
||||
'reddit://user:****@****/****/apprise/subreddit2',
|
||||
}),
|
||||
# Pass in some arguments to over-ride defaults
|
||||
('reddit://user:pass@id/secret/sub/'
|
||||
'?ad=yes&nsfw=yes&replies=no&resubmit=yes&spoiler=yes&kind=self', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'reddit://user:****@****/****/sub'}),
|
||||
# Pass in more arguments
|
||||
('reddit://'
|
||||
'?user=l2g&pass=pass&app_secret=abc123&app_id=54321&to=sub1,sub2', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'reddit://l2g:****@****/****/sub1/sub2'}),
|
||||
# More arguments ...
|
||||
('reddit://user:pass@id/secret/sub7/sub6/sub5/'
|
||||
'?flair_id=wonder&flair_text=not%20for%20you', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
'requests_response_text': {
|
||||
"access_token": 'abc123',
|
||||
"token_type": "bearer",
|
||||
"expires_in": 100000,
|
||||
"scope": '*',
|
||||
"refresh_token": 'def456',
|
||||
# The below is used in the response:
|
||||
"json": {
|
||||
# No errors during post
|
||||
"errors": [],
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'reddit://user:****@****/****/sub'}),
|
||||
('reddit://user:password@app-id/app-secret/apprise', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('reddit://user:password@app-id/app-secret/apprise', {
|
||||
'instance': plugins.NotifyReddit,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_reddit_urls():
|
||||
"""
|
||||
NotifyReddit() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_reddit_plugin_general(mock_post):
|
||||
def test_plugin_reddit_general(mock_post):
|
||||
"""
|
||||
API: NotifyReddit() General Tests
|
||||
NotifyReddit() General Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
327
test/test_plugin_rocket_chat.py
Normal file
327
test/test_plugin_rocket_chat.py
Normal file
@ -0,0 +1,327 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('rocket://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('rockets://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('rocket://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# No username or pass
|
||||
('rocket://localhost', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No room or channel
|
||||
('rocket://user:pass@localhost', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No valid rooms or channels
|
||||
('rocket://user:pass@localhost/#/!/@', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No user/pass combo
|
||||
('rocket://user@localhost/room/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No user/pass combo
|
||||
('rocket://localhost/room/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# A room and port identifier
|
||||
('rocket://user:pass@localhost:8080/room/', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# A channel (using the to=)
|
||||
('rockets://user:pass@localhost?to=#channel', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# A channel
|
||||
('rockets://user:pass@localhost/#channel', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# Several channels
|
||||
('rocket://user:pass@localhost/#channel1/#channel2/?avatar=Yes', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# Several Rooms
|
||||
('rocket://user:pass@localhost/room1/room2', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# A room and channel
|
||||
('rocket://user:pass@localhost/room/#channel?mode=basic&avatar=Yes', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'rocket://user:****@localhost',
|
||||
}),
|
||||
# A user/pass where the pass matches a webtoken
|
||||
# to ensure we get the right mode, we enforce basic mode
|
||||
# so that web/token gets interpreted as a password
|
||||
('rockets://user:pass%2Fwithslash@localhost/#channel/?mode=basic', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_text': {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'authToken': 'abcd',
|
||||
'userId': 'user',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# A room and channel
|
||||
('rockets://user:pass@localhost/rooma/#channela', {
|
||||
# The response text is expected to be the following on a success
|
||||
'requests_response_code': requests.codes.ok,
|
||||
'requests_response_text': {
|
||||
# return something other then a success message type
|
||||
'status': 'failure',
|
||||
},
|
||||
# Exception is thrown in this case
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# Notifications will fail in this event
|
||||
'response': False,
|
||||
}),
|
||||
# A web token
|
||||
('rockets://web/token@localhost/@user/#channel/roomid', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
}),
|
||||
('rockets://user:web/token@localhost/@user/?mode=webhook', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
}),
|
||||
('rockets://user:web/token@localhost?to=@user2,#channel2', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
}),
|
||||
('rockets://web/token@localhost/?avatar=No', {
|
||||
# a simple webhook token with default values
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'rockets://w...n@localhost',
|
||||
}),
|
||||
('rockets://localhost/@user/?mode=webhook&webhook=web/token', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
}),
|
||||
('rockets://user:web/token@localhost/@user/?mode=invalid', {
|
||||
# invalid mode
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('rocket://user:pass@localhost:8081/room1/room2', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# force a failure using basic mode
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('rockets://user:web/token@localhost?to=@user3,#channel3', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# force a failure using webhook mode
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('rocket://user:pass@localhost:8082/#channel', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('rocket://user:pass@localhost:8083/#chan1/#chan2/room', {
|
||||
'instance': plugins.NotifyRocketChat,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_rocket_chat_urls():
|
||||
"""
|
||||
NotifyRocketChat() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_rocketchat_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifyRocketChat() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Chat ID
|
||||
recipients = 'AbcD1245, @l2g, @lead2gold, #channel, #channel2'
|
||||
|
||||
# Authentication
|
||||
user = 'myuser'
|
||||
password = 'mypass'
|
||||
|
||||
# Prepare Mock
|
||||
mock_get.return_value = requests.Request()
|
||||
mock_post.return_value = requests.Request()
|
||||
mock_post.return_value.status_code = requests.codes.ok
|
||||
mock_get.return_value.status_code = requests.codes.ok
|
||||
mock_post.return_value.content = ''
|
||||
mock_get.return_value.content = ''
|
||||
|
||||
obj = plugins.NotifyRocketChat(
|
||||
user=user, password=password, targets=recipients)
|
||||
assert isinstance(obj, plugins.NotifyRocketChat) is True
|
||||
assert len(obj.channels) == 2
|
||||
assert len(obj.users) == 2
|
||||
assert len(obj.rooms) == 1
|
||||
|
||||
# No Webhook specified
|
||||
with pytest.raises(TypeError):
|
||||
obj = plugins.NotifyRocketChat(webhook=None, mode='webhook')
|
||||
|
||||
#
|
||||
# Logout
|
||||
#
|
||||
assert obj.logout() is True
|
||||
|
||||
# Invalid JSON during Login
|
||||
mock_post.return_value.content = '{'
|
||||
mock_get.return_value.content = '}'
|
||||
assert obj.login() is False
|
||||
|
||||
# Prepare Mock to fail
|
||||
mock_post.return_value.content = ''
|
||||
mock_get.return_value.content = ''
|
||||
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||
|
||||
#
|
||||
# Send Notification
|
||||
#
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||
assert obj._send(payload='test', notify_type=NotifyType.INFO) is False
|
||||
|
||||
#
|
||||
# Logout
|
||||
#
|
||||
assert obj.logout() is False
|
||||
|
||||
# KeyError handling
|
||||
mock_post.return_value.status_code = 999
|
||||
mock_get.return_value.status_code = 999
|
||||
|
||||
#
|
||||
# Send Notification
|
||||
#
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||
assert obj._send(payload='test', notify_type=NotifyType.INFO) is False
|
||||
|
||||
#
|
||||
# Logout
|
||||
#
|
||||
assert obj.logout() is False
|
||||
|
||||
# Generate exceptions
|
||||
mock_get.side_effect = requests.ConnectionError(
|
||||
0, 'requests.ConnectionError() not handled')
|
||||
mock_post.side_effect = mock_get.side_effect
|
||||
|
||||
#
|
||||
# Send Notification
|
||||
#
|
||||
assert obj._send(payload='test', notify_type=NotifyType.INFO) is False
|
||||
|
||||
# Attempt the check again but fake a successful login
|
||||
obj.login = mock.Mock()
|
||||
obj.login.return_value = True
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||
#
|
||||
# Logout
|
||||
#
|
||||
assert obj.logout() is False
|
145
test/test_plugin_ryver.py
Normal file
145
test/test_plugin_ryver.py
Normal file
@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('ryver://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ryver://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ryver://apprise', {
|
||||
# Just org provided (no token)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG?mode=invalid', {
|
||||
# invalid mode provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ryver://x/ckhrjW8w672m6HG?mode=slack', {
|
||||
# Invalid org
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG?mode=slack', {
|
||||
# No username specified; this is still okay as we use whatever
|
||||
# the user told the webhook to use; set our slack mode
|
||||
'instance': plugins.NotifyRyver,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG?mode=ryver', {
|
||||
# No username specified; this is still okay as we use whatever
|
||||
# the user told the webhook to use; set our ryver mode
|
||||
'instance': plugins.NotifyRyver,
|
||||
}),
|
||||
# Legacy webhook mode setting:
|
||||
# Legacy webhook mode setting:
|
||||
('ryver://apprise/ckhrjW8w672m6HG?webhook=slack', {
|
||||
# No username specified; this is still okay as we use whatever
|
||||
# the user told the webhook to use; set our slack mode
|
||||
'instance': plugins.NotifyRyver,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG?webhook=ryver', {
|
||||
# No username specified; this is still okay as we use whatever
|
||||
# the user told the webhook to use; set our ryver mode
|
||||
'instance': plugins.NotifyRyver,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'ryver://apprise/c...G',
|
||||
}),
|
||||
# Support Native URLs
|
||||
('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyRyver,
|
||||
}),
|
||||
# Support Native URLs with arguments
|
||||
('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG'
|
||||
'?webhook=ryver',
|
||||
{
|
||||
'instance': plugins.NotifyRyver,
|
||||
}),
|
||||
('ryver://caronc@apprise/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyRyver,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyRyver,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyRyver,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('ryver://apprise/ckhrjW8w672m6HG', {
|
||||
'instance': plugins.NotifyRyver,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_ryver_urls():
|
||||
"""
|
||||
NotifyRyver() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_ryver_edge_cases():
|
||||
"""
|
||||
NotifyRyver() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# No token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyRyver(organization="abc", token=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyRyver(organization="abc", token=" ")
|
||||
|
||||
# No organization
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyRyver(organization=None, token="abc")
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyRyver(organization=" ", token="abc")
|
159
test/test_plugin_sendgrid.py
Normal file
159
test/test_plugin_sendgrid.py
Normal file
@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('sendgrid://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('sendgrid://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('sendgrid://abcd', {
|
||||
# Just an broken email (no api key or email)
|
||||
'instance': None,
|
||||
}),
|
||||
('sendgrid://abcd@host', {
|
||||
# Just an Email specified, no API Key
|
||||
'instance': None,
|
||||
}),
|
||||
('sendgrid://invalid-api-key+*-d:user@example.com', {
|
||||
# An invalid API Key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com', {
|
||||
# No To/Target Address(es) specified; so we sub in the same From
|
||||
# address
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com', {
|
||||
# A good email
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com'
|
||||
'?bcc=l2g@nuxref.com', {
|
||||
# A good email with Blind Carbon Copy
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com'
|
||||
'?cc=l2g@nuxref.com', {
|
||||
# A good email with Carbon Copy
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com'
|
||||
'?to=l2g@nuxref.com', {
|
||||
# A good email with Carbon Copy
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com'
|
||||
'?template={}'.format(UUID4), {
|
||||
# A good email with a template + no substitutions
|
||||
'instance': plugins.NotifySendGrid,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.com/newuser@example.com'
|
||||
'?template={}&+sub=value&+sub2=value2'.format(UUID4), {
|
||||
# A good email with a template + substitutions
|
||||
'instance': plugins.NotifySendGrid,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'sendgrid://a...d:user@example.com/',
|
||||
}),
|
||||
('sendgrid://abcd:user@example.ca/newuser@example.ca', {
|
||||
'instance': plugins.NotifySendGrid,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.uk/newuser@example.uk', {
|
||||
'instance': plugins.NotifySendGrid,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('sendgrid://abcd:user@example.au/newuser@example.au', {
|
||||
'instance': plugins.NotifySendGrid,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_sendgrid_urls():
|
||||
"""
|
||||
NotifySendGrid() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_sendgrid_edge_cases(mock_post, mock_get):
|
||||
"""
|
||||
NotifySendGrid() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# no apikey
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySendGrid(
|
||||
apikey=None, from_email='user@example.com')
|
||||
|
||||
# invalid from email
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySendGrid(
|
||||
apikey='abcd', from_email='!invalid')
|
||||
|
||||
# no email
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySendGrid(apikey='abcd', from_email=None)
|
||||
|
||||
# Invalid To email address
|
||||
plugins.NotifySendGrid(
|
||||
apikey='abcd', from_email='user@example.com', targets="!invalid")
|
||||
|
||||
# Test invalid bcc/cc entries mixed with good ones
|
||||
assert isinstance(plugins.NotifySendGrid(
|
||||
apikey='abcd',
|
||||
from_email='l2g@example.com',
|
||||
bcc=('abc@def.com', '!invalid'),
|
||||
cc=('abc@test.org', '!invalid')), plugins.NotifySendGrid)
|
176
test/test_plugin_simplepush.py
Normal file
176
test/test_plugin_simplepush.py
Normal file
@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import sys
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
import json
|
||||
from apprise import Apprise
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('spush://', {
|
||||
# No api key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('spush://{}'.format('A' * 14), {
|
||||
# API Key specified however expected server response
|
||||
# didn't have 'OK' in JSON response
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
('spush://{}'.format('Y' * 14), {
|
||||
# API Key valid and expected response was valid
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# Set our response to OK
|
||||
'requests_response_text': {
|
||||
'status': 'OK',
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'spush://Y...Y/',
|
||||
}),
|
||||
('spush://{}?event=Not%20So%20Good'.format('X' * 14), {
|
||||
# API Key valid and expected response was valid
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# Set our response to something that is not okay
|
||||
'requests_response_text': {
|
||||
'status': 'NOT-OK',
|
||||
},
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
('spush://salt:pass@{}'.format('X' * 14), {
|
||||
# Now we'll test encrypted messages with new salt
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# Set our response to OK
|
||||
'requests_response_text': {
|
||||
'status': 'OK',
|
||||
},
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'spush://****:****@X...X/',
|
||||
}),
|
||||
('spush://{}'.format('Y' * 14), {
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
# Set a failing message too
|
||||
'requests_response_text': {
|
||||
'status': 'BadRequest',
|
||||
'message': 'Title or message too long',
|
||||
},
|
||||
}),
|
||||
('spush://{}'.format('Z' * 14), {
|
||||
'instance': plugins.NotifySimplePush,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
def test_plugin_simplepush_urls():
|
||||
"""
|
||||
NotifySimplePush() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' in sys.modules,
|
||||
reason="Requires that cryptography NOT be installed")
|
||||
def test_plugin_fcm_cryptography_import_error():
|
||||
"""
|
||||
NotifySimplePush() Cryptography loading failure
|
||||
"""
|
||||
|
||||
# Attempt to instantiate our object
|
||||
obj = Apprise.instantiate('spush://{}'.format('Y' * 14))
|
||||
|
||||
# It's not possible because our cryptography depedancy is missing
|
||||
assert obj is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
def test_plugin_simplepush_edge_cases():
|
||||
"""
|
||||
NotifySimplePush() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# No token
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySimplePush(apikey=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySimplePush(apikey=" ")
|
||||
|
||||
# Bad event
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySimplePush(apikey="abc", event=object)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySimplePush(apikey="abc", event=" ")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'cryptography' not in sys.modules, reason="Requires cryptography")
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_simplepush_general(mock_post):
|
||||
"""
|
||||
NotifySimplePush() General Tests
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare a good response
|
||||
response = mock.Mock()
|
||||
response.content = json.dumps({
|
||||
'status': 'OK',
|
||||
})
|
||||
response.status_code = requests.codes.ok
|
||||
mock_post.return_value = response
|
||||
|
||||
obj = Apprise.instantiate('spush://{}'.format('Y' * 14))
|
||||
|
||||
# Verify our content works as expected
|
||||
assert obj.notify(title="test", body="test") is True
|
178
test/test_plugin_sinch.py
Normal file
178
test/test_plugin_sinch.py
Normal file
@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from json import dumps
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('sinch://', {
|
||||
# No Account SID specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://:@/', {
|
||||
# invalid Auth token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://{}@12345678'.format('a' * 32), {
|
||||
# Just spi provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://{}:{}@_'.format('a' * 32, 'b' * 32), {
|
||||
# spi and token provided but invalid from
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||
# using short-code (5 characters) without a target
|
||||
# We can still instantiate ourselves with a valid short code
|
||||
'instance': plugins.NotifySinch,
|
||||
# Expected notify() response because we have no one to notify
|
||||
'notify_response': False,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||
# spi and token provided and from but invalid from no
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://{}:{}@{}/123/{}/abcd/'.format(
|
||||
'a' * 32, 'b' * 32, '3' * 11, '9' * 15), {
|
||||
# valid everything but target numbers
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://{}:{}@12345/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
|
||||
# using short-code (5 characters)
|
||||
'instance': plugins.NotifySinch,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'sinch://...aaaa:b...b@12345',
|
||||
}),
|
||||
('sinch://{}:{}@123456/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
|
||||
# using short-code (6 characters)
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '5' * 11), {
|
||||
# using phone no with no target - we text ourselves in
|
||||
# this case
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://{}:{}@{}?region=eu'.format('a' * 32, 'b' * 32, '5' * 11), {
|
||||
# Specify a region
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://{}:{}@{}?region=invalid'.format('a' * 32, 'b' * 32, '5' * 11), {
|
||||
# Invalid region
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sinch://_?spi={}&token={}&from={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11), {
|
||||
# use get args to acomplish the same thing
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://_?spi={}&token={}&source={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11), {
|
||||
# use get args to acomplish the same thing (use source instead of from)
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://_?spi={}&token={}&from={}&to={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11, '7' * 13), {
|
||||
# use to=
|
||||
'instance': plugins.NotifySinch,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '6' * 11), {
|
||||
'instance': plugins.NotifySinch,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '6' * 11), {
|
||||
'instance': plugins.NotifySinch,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_sinch_urls():
|
||||
"""
|
||||
NotifyTemplate() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_sinch_edge_cases(mock_post):
|
||||
"""
|
||||
NotifySinch() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare our response
|
||||
response = requests.Request()
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
service_plan_id = '{}'.format('b' * 32)
|
||||
api_token = '{}'.format('b' * 32)
|
||||
source = '+1 (555) 123-3456'
|
||||
|
||||
# No service_plan_id specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySinch(
|
||||
service_plan_id=None, api_token=api_token, source=source)
|
||||
|
||||
# No api_token specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySinch(
|
||||
service_plan_id=service_plan_id, api_token=None, source=source)
|
||||
|
||||
# a error response
|
||||
response.status_code = 400
|
||||
response.content = dumps({
|
||||
'code': 21211,
|
||||
'message': "The 'To' number +1234567 is not a valid phone number.",
|
||||
})
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize our object
|
||||
obj = plugins.NotifySinch(
|
||||
service_plan_id=service_plan_id, api_token=api_token, source=source)
|
||||
|
||||
# We will fail with the above error code
|
||||
assert obj.notify('title', 'body', 'info') is False
|
@ -31,6 +31,7 @@ import requests
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from apprise import AppriseAttachment
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
from json import dumps
|
||||
|
||||
@ -41,15 +42,233 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('slack://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://T1JJ3T3L2', {
|
||||
# Just Token 1 provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://T1JJ3T3L2/A1BRTD4JD/', {
|
||||
# Just 2 tokens provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#hmm/#-invalid-', {
|
||||
# No username specified; this is still okay as we sub in
|
||||
# default; The one invalid channel is skipped when sending a message
|
||||
'instance': plugins.NotifySlack,
|
||||
# There is an invalid channel that we will fail to deliver to
|
||||
# as a result the response type will be false
|
||||
'response': False,
|
||||
'requests_response_text': {
|
||||
'ok': False,
|
||||
'message': 'Bad Channel',
|
||||
},
|
||||
}),
|
||||
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#channel', {
|
||||
# No username specified; this is still okay as we sub in
|
||||
# default; The one invalid channel is skipped when sending a message
|
||||
'instance': plugins.NotifySlack,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
'requests_response_text': 'ok'
|
||||
}),
|
||||
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/+id/@id/', {
|
||||
# + encoded id,
|
||||
# @ userid
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'?to=#nuxref', {
|
||||
'instance': plugins.NotifySlack,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'slack://username@T...2/A...D/T...Q/',
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
# You can't send to email using webhook
|
||||
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/user@gmail.com', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
# we'll have a notify response failure in this case
|
||||
'notify_response': False,
|
||||
}),
|
||||
# Specify Token on argument string (with username)
|
||||
('slack://bot@_/#nuxref?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnadfdajkjkfl/', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
# Specify Token and channels on argument string (no username)
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/&to=#chan', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
# Test webhook that doesn't have a proper response
|
||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'fail',
|
||||
# we'll have a notify response failure in this case
|
||||
'notify_response': False,
|
||||
}),
|
||||
# Test using a bot-token (also test footer set to no flag)
|
||||
('slack://username@xoxb-1234-1234-abc124/#nuxref?footer=no', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': {
|
||||
'ok': True,
|
||||
'message': '',
|
||||
# support attachments
|
||||
'file': {
|
||||
'url_private': 'http://localhost/',
|
||||
},
|
||||
},
|
||||
}),
|
||||
# Test blocks mode
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'&to=#chan&blocks=yes&footer=yes',
|
||||
{
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok'}),
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'&to=#chan&blocks=yes&footer=no',
|
||||
{
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok'}),
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'&to=#chan&blocks=yes&footer=yes&image=no',
|
||||
{
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok'}),
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'&to=#chan&blocks=yes&format=text',
|
||||
{
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok'}),
|
||||
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||
'&to=#chan&blocks=no&format=text',
|
||||
{
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok'}),
|
||||
|
||||
# Test using a bot-token as argument
|
||||
('slack://?token=xoxb-1234-1234-abc124&to=#nuxref&footer=no&user=test', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': {
|
||||
'ok': True,
|
||||
'message': '',
|
||||
# support attachments
|
||||
'file': {
|
||||
'url_private': 'http://localhost/',
|
||||
},
|
||||
},
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'slack://test@x...4/nuxref/',
|
||||
}),
|
||||
# We contain 1 or more invalid channels, so we'll fail on our notify call
|
||||
('slack://?token=xoxb-1234-1234-abc124&to=#nuxref,#$,#-&footer=no', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': {
|
||||
'ok': True,
|
||||
'message': '',
|
||||
# support attachments
|
||||
'file': {
|
||||
'url_private': 'http://localhost/',
|
||||
},
|
||||
},
|
||||
# We fail because of the empty channel #$ and #-
|
||||
'notify_response': False,
|
||||
}),
|
||||
('slack://username@xoxb-1234-1234-abc124/#nuxref', {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': {
|
||||
'ok': True,
|
||||
'message': '',
|
||||
},
|
||||
# we'll fail to send attachments because we had no 'file' response in
|
||||
# our object
|
||||
'response': False,
|
||||
}),
|
||||
|
||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ', {
|
||||
# Missing a channel, falls back to webhook channel bindings
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
# Native URL Support, take the slack URL and still build from it
|
||||
('https://hooks.slack.com/services/{}/{}/{}'.format(
|
||||
'A' * 9, 'B' * 9, 'c' * 24), {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
# Native URL Support with arguments
|
||||
('https://hooks.slack.com/services/{}/{}/{}?format=text'.format(
|
||||
'A' * 9, 'B' * 9, 'c' * 24), {
|
||||
'instance': plugins.NotifySlack,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
('slack://username@-INVALID-/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
|
||||
# invalid 1st Token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://username@T1JJ3T3L2/-INVALID-/TIiajkdnlazkcOXrIdevi7FQ/#great', {
|
||||
# invalid 2rd Token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/-INVALID-/#channel', {
|
||||
# invalid 3rd Token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('slack://l2g@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#usenet', {
|
||||
'instance': plugins.NotifySlack,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
('slack://respect@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#a', {
|
||||
'instance': plugins.NotifySlack,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
('slack://notify@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#b', {
|
||||
'instance': plugins.NotifySlack,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
'requests_response_text': 'ok',
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_slack_urls():
|
||||
"""
|
||||
NotifySlack() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_slack_oauth_access_token(mock_post):
|
||||
def test_plugin_slack_oauth_access_token(mock_post):
|
||||
"""
|
||||
API: NotifySlack() OAuth Access Token Tests
|
||||
NotifySlack() OAuth Access Token Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
plugins.NotifySlack.request_rate_per_sec = 0
|
||||
|
||||
# Generate an invalid bot token
|
||||
token = 'xo-invalid'
|
||||
@ -166,13 +385,13 @@ def test_slack_oauth_access_token(mock_post):
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_slack_webhook(mock_post):
|
||||
def test_plugin_slack_webhook_mode(mock_post):
|
||||
"""
|
||||
API: NotifySlack() Webhook Tests
|
||||
NotifySlack() Webhook Mode Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
plugins.NotifySlack.request_rate_per_sec = 0
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = requests.Request()
|
||||
@ -214,13 +433,13 @@ def test_slack_webhook(mock_post):
|
||||
|
||||
@mock.patch('requests.post')
|
||||
@mock.patch('requests.get')
|
||||
def test_slack_send_by_email(mock_get, mock_post):
|
||||
def test_plugin_slack_send_by_email(mock_get, mock_post):
|
||||
"""
|
||||
API: NotifySlack() Send by Email Tests
|
||||
NotifySlack() Send by Email Tests
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
plugins.NotifySlack.request_rate_per_sec = 0
|
||||
|
||||
# Generate a (valid) bot token
|
||||
token = 'xoxb-1234-1234-abc124'
|
@ -31,6 +31,7 @@ from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
@ -39,11 +40,124 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('smtp2go://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('smtp2go://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No Token specified
|
||||
('smtp2go://user@localhost.localdomain', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Token is valid, but no user name specified
|
||||
('smtp2go://localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid from email address
|
||||
('smtp2go://!@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No To email address, but everything else is valid
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}?format=markdown'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}?format=html'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}?format=text'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
# headers
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'
|
||||
'?+X-Customer-Campaign-ID=Apprise'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
# bcc and cc
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'
|
||||
'?bcc=user@example.com&cc=user2@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
# One To Email address
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}/test@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/'
|
||||
'{}-{}-{}?to=test@example.com'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go}),
|
||||
# One To Email address, a from name specified too
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}/'
|
||||
'test@example.com?name="Frodo"'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go}),
|
||||
# Invalid 'To' Email address
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}/invalid'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
# Multiple 'To', 'Cc', and 'Bcc' addresses (with invalid ones)
|
||||
('smtp2go://user@example.com/{}-{}-{}/{}?bcc={}&cc={}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8,
|
||||
'/'.join(('user1@example.com', 'invalid', 'User2:user2@example.com')),
|
||||
','.join(('user3@example.com', 'i@v', 'User1:user1@example.com')),
|
||||
','.join(('user4@example.com', 'g@r@b', 'Da:user5@example.com'))), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('smtp2go://user@localhost.localdomain/{}-{}-{}'.format(
|
||||
'a' * 32, 'b' * 8, 'c' * 8), {
|
||||
'instance': plugins.NotifySMTP2Go,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_smtp2go_urls():
|
||||
"""
|
||||
NotifySMTP2Go() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_smtp2go_plugin_attachments(mock_post):
|
||||
def test_plugin_smtp2go_attachments(mock_post):
|
||||
"""
|
||||
API: NotifySMTP2Go() Attachments
|
||||
NotifySMTP2Go() Attachments
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
@ -28,6 +28,7 @@ import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
@ -38,13 +39,69 @@ TEST_ACCESS_KEY_ID = 'AHIAJGNT76XIMXDBIJYA'
|
||||
TEST_ACCESS_KEY_SECRET = 'bu1dHSdO22pfaaVy/wmNsdljF4C07D3bndi9PQJ9'
|
||||
TEST_REGION = 'us-east-2'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('sns://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sns://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sns://T1JJ3T3L2', {
|
||||
# Just Token 1 provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sns://T1JJ3TD4JD/TIiajkdnlazk7FQ/', {
|
||||
# Missing a region
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcevi7FQ/us-west-2/12223334444', {
|
||||
# we have a valid URL and one number to text
|
||||
'instance': plugins.NotifySNS,
|
||||
}),
|
||||
('sns://T1JJ3TD4JD/TIiajkdnlazk7FQ/us-west-2/12223334444/12223334445', {
|
||||
# Multi SNS Suppport
|
||||
'instance': plugins.NotifySNS,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'sns://T...D/****/us-west-2',
|
||||
}),
|
||||
('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/us-east-1'
|
||||
'?to=12223334444', {
|
||||
# Missing a topic and/or phone No
|
||||
'instance': plugins.NotifySNS,
|
||||
}),
|
||||
('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcevi7FQ/us-west-2/12223334444', {
|
||||
'instance': plugins.NotifySNS,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcevi7FQ/us-west-2/15556667777', {
|
||||
'instance': plugins.NotifySNS,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_sns_urls():
|
||||
"""
|
||||
NotifySNS() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
# We initialize a post object just incase a test fails below
|
||||
# we don't want it sending any notifications upstream
|
||||
@mock.patch('requests.post')
|
||||
def test_object_initialization(mock_post):
|
||||
def test_plugin_sns_edge_cases(mock_post):
|
||||
"""
|
||||
API: NotifySNS Plugin() initialization
|
||||
NotifySNS() Edge Cases
|
||||
|
||||
"""
|
||||
|
||||
@ -112,9 +169,9 @@ def test_object_initialization(mock_post):
|
||||
assert obj.notify(body='test', title='test') is False
|
||||
|
||||
|
||||
def test_url_parsing():
|
||||
def test_plugin_sns_url_parsing():
|
||||
"""
|
||||
API: NotifySNS Plugin() URL Parsing
|
||||
NotifySNS() URL Parsing
|
||||
|
||||
"""
|
||||
|
||||
@ -156,9 +213,9 @@ def test_url_parsing():
|
||||
assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
|
||||
|
||||
|
||||
def test_object_parsing():
|
||||
def test_plugin_sns_object_parsing():
|
||||
"""
|
||||
API: NotifySNS Plugin() Object Parsing
|
||||
NotifySNS() Object Parsing
|
||||
|
||||
"""
|
||||
|
||||
@ -183,9 +240,9 @@ def test_object_parsing():
|
||||
assert len(a) == 3
|
||||
|
||||
|
||||
def test_aws_response_handling():
|
||||
def test_plugin_sns_aws_response_handling():
|
||||
"""
|
||||
API: NotifySNS Plugin() AWS Response Handling
|
||||
NotifySNS() AWS Response Handling
|
||||
|
||||
"""
|
||||
# Not a string
|
||||
@ -261,9 +318,9 @@ def test_aws_response_handling():
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_aws_topic_handling(mock_post):
|
||||
def test_plugin_sns_aws_topic_handling(mock_post):
|
||||
"""
|
||||
API: NotifySNS Plugin() AWS Topic Handling
|
||||
NotifySNS() AWS Topic Handling
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
408
test/test_plugin_sparkpost.py
Normal file
408
test/test_plugin_sparkpost.py
Normal file
@ -0,0 +1,408 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import mock
|
||||
import requests
|
||||
from json import dumps
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAttachment
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('sparkpost://', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('sparkpost://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No Token specified
|
||||
('sparkpost://user@localhost.localdomain', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Token is valid, but no user name specified
|
||||
('sparkpost://localhost.localdomain/{}'.format('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid from email address
|
||||
('sparkpost://!@localhost.localdomain/{}'.format('b' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No To email address, but everything else is valid
|
||||
('sparkpost://user@localhost.localdomain/{}'.format('c' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}?format=markdown'
|
||||
.format('d' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}?format=html'
|
||||
.format('d' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}?format=text'
|
||||
.format('d' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# valid url with region specified (case insensitve)
|
||||
('sparkpost://user@localhost.localdomain/{}?region=uS'.format('d' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# valid url with region specified (case insensitve)
|
||||
('sparkpost://user@localhost.localdomain/{}?region=EU'.format('e' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# headers
|
||||
('sparkpost://user@localhost.localdomain/{}'
|
||||
'?+X-Customer-Campaign-ID=Apprise'.format('f' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# template tokens
|
||||
('sparkpost://user@localhost.localdomain/{}'
|
||||
'?:name=Chris&:status=admin'.format('g' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# bcc and cc
|
||||
('sparkpost://user@localhost.localdomain/{}'
|
||||
'?bcc=user@example.com&cc=user2@example.com'.format('h' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# invalid url with region specified (case insensitve)
|
||||
('sparkpost://user@localhost.localdomain/{}?region=invalid'.format(
|
||||
'a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# One 'To' Email address
|
||||
('sparkpost://user@localhost.localdomain/{}/test@example.com'.format(
|
||||
'a' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# Invalid 'To' Email address
|
||||
('sparkpost://user@localhost.localdomain/{}/invalid'.format(
|
||||
'i' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
# Expected notify() response
|
||||
'notify_response': False,
|
||||
}),
|
||||
# Multiple 'To', 'Cc', and 'Bcc' addresses (with invalid ones)
|
||||
('sparkpost://user@example.com/{}/{}?bcc={}&cc={}'.format(
|
||||
'j' * 32,
|
||||
'/'.join(('user1@example.com', 'invalid', 'User2:user2@example.com')),
|
||||
','.join(('user3@example.com', 'i@v', 'User1:user1@example.com')),
|
||||
','.join(('user4@example.com', 'g@r@b', 'Da:user5@example.com'))), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/'
|
||||
'{}?to=test@example.com'.format('k' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# One To Email address, a from name specified too
|
||||
('sparkpost://user@localhost.localdomain/{}/'
|
||||
'test@example.com?name="Frodo"'.format('l' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': {
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
},
|
||||
}),
|
||||
# Test invalid JSON response
|
||||
('sparkpost://user@localhost.localdomain/{}'.format('m' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
'requests_response_text': "{",
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}'.format('n' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}'.format('o' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('sparkpost://user@localhost.localdomain/{}'.format('p' * 32), {
|
||||
'instance': plugins.NotifySparkPost,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_sparkpost_urls():
|
||||
"""
|
||||
NotifySparkPost() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_sparkpost_throttling(mock_post):
|
||||
"""
|
||||
NotifySparkPost() Throttling
|
||||
|
||||
"""
|
||||
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
plugins.NotifySparkPost.sparkpost_retry_wait_sec = 0.1
|
||||
plugins.NotifySparkPost.sparkpost_retry_attempts = 3
|
||||
|
||||
# API Key
|
||||
apikey = 'abc123'
|
||||
user = 'user'
|
||||
host = 'example.com'
|
||||
targets = '{}@{}'.format(user, host)
|
||||
|
||||
# Exception should be thrown about the fact no user was specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySparkPost(
|
||||
apikey=apikey, targets=targets, host=host)
|
||||
|
||||
# Exception should be thrown about the fact no private key was specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifySparkPost(
|
||||
apikey=None, targets=targets, user=user, host=host)
|
||||
|
||||
okay_response = requests.Request()
|
||||
okay_response.status_code = requests.codes.ok
|
||||
okay_response.content = dumps({
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
})
|
||||
|
||||
retry_response = requests.Request()
|
||||
retry_response.status_code = requests.codes.too_many_requests
|
||||
retry_response.content = dumps({
|
||||
"errors": [
|
||||
{
|
||||
"description":
|
||||
"Unconfigured or unverified sending domain.",
|
||||
"code": "7001",
|
||||
"message": "Invalid domain"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# Prepare Mock (force 2 retry responses and then one okay)
|
||||
mock_post.side_effect = \
|
||||
(retry_response, retry_response, okay_response)
|
||||
|
||||
obj = Apprise.instantiate(
|
||||
'sparkpost://user@localhost.localdomain/{}'.format(apikey))
|
||||
assert isinstance(obj, plugins.NotifySparkPost)
|
||||
|
||||
# We'll successfully perform the notification as we're within
|
||||
# our retry limit
|
||||
assert obj.notify('test') is True
|
||||
|
||||
mock_post.reset_mock()
|
||||
mock_post.side_effect = \
|
||||
(retry_response, retry_response, retry_response)
|
||||
|
||||
# Now we are less than our expected limit check so we will fail
|
||||
assert obj.notify('test') is False
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_sparkpost_attachments(mock_post):
|
||||
"""
|
||||
NotifySparkPost() Attachments
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
plugins.NotifySparkPost.sparkpost_retry_wait_sec = 0.1
|
||||
plugins.NotifySparkPost.sparkpost_retry_attempts = 3
|
||||
|
||||
okay_response = requests.Request()
|
||||
okay_response.status_code = requests.codes.ok
|
||||
okay_response.content = dumps({
|
||||
"results": {
|
||||
"total_rejected_recipients": 0,
|
||||
"total_accepted_recipients": 1,
|
||||
"id": "11668787484950529"
|
||||
}
|
||||
})
|
||||
|
||||
# Assign our mock object our return value
|
||||
mock_post.return_value = okay_response
|
||||
|
||||
# API Key
|
||||
apikey = 'abc123'
|
||||
|
||||
obj = Apprise.instantiate(
|
||||
'sparkpost://user@localhost.localdomain/{}'.format(apikey))
|
||||
assert isinstance(obj, plugins.NotifySparkPost)
|
||||
|
||||
# Test Valid Attachment
|
||||
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||
attach = AppriseAttachment(path)
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is True
|
||||
|
||||
# Test invalid attachment
|
||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=path) is False
|
||||
|
||||
with mock.patch('base64.b64encode', side_effect=OSError()):
|
||||
# We can't send the message if we fail to parse the data
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is False
|
||||
|
||||
obj = Apprise.instantiate(
|
||||
'sparkpost://no-reply@example.com/{}/'
|
||||
'user1@example.com/user2@example.com?batch=yes'.format(apikey))
|
||||
assert isinstance(obj, plugins.NotifySparkPost)
|
||||
|
||||
# Force our batch to break into separate messages
|
||||
obj.default_batch_size = 1
|
||||
# We'll send 2 messages
|
||||
mock_post.reset_mock()
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is True
|
||||
assert mock_post.call_count == 2
|
||||
|
||||
# single batch
|
||||
mock_post.reset_mock()
|
||||
# We'll send 1 message
|
||||
obj.default_batch_size = 2
|
||||
|
||||
assert obj.notify(
|
||||
body='body', title='title', notify_type=NotifyType.INFO,
|
||||
attach=attach) is True
|
||||
assert mock_post.call_count == 1
|
118
test/test_plugin_spontit.py
Normal file
118
test/test_plugin_spontit.py
Normal file
@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('spontit://', {
|
||||
# invalid url
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Another bad url
|
||||
('spontit://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# No user specified
|
||||
('spontit://%s' % ('a' * 100), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid API Key specified
|
||||
('spontit://user@%%20_', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Provide a valid user and API Key
|
||||
('spontit://%s@%s' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'spontit://{}@b...b/'.format('u' * 11),
|
||||
}),
|
||||
# Provide a valid user and API Key, but provide an invalid channel
|
||||
('spontit://%s@%s/#!!' % ('u' * 11, 'b' * 100), {
|
||||
# An instance is still created, but the channel won't be notified
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide a valid user, API Key and a valid channel
|
||||
('spontit://%s@%s/#abcd' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide a valid user, API Key, and a subtitle
|
||||
('spontit://%s@%s/?subtitle=Test' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide a valid user, API Key, and a lengthy subtitle
|
||||
('spontit://%s@%s/?subtitle=%s' % ('u' * 11, 'b' * 100, 'c' * 300), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide a valid user and API Key, but provide a valid channel (that is
|
||||
# not ours).
|
||||
# Spontit uses a slash (/) to delimite the user from the channel id when
|
||||
# specifying channel entries. For Apprise we need to encode this
|
||||
# so we convert the slash (/) into %2F
|
||||
('spontit://{}@{}/#1245%2Fabcd'.format('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide multipe channels
|
||||
('spontit://{}@{}/#1245%2Fabcd/defg'.format('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
# Provide multipe channels through the use of the to= variable
|
||||
('spontit://{}@{}/?to=#1245/abcd'.format('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
}),
|
||||
('spontit://%s@%s' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('spontit://%s@%s' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('spontit://%s@%s' % ('u' * 11, 'b' * 100), {
|
||||
'instance': plugins.NotifySpontit,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_spontit_urls():
|
||||
"""
|
||||
NotifySpontit() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
125
test/test_plugin_streamlabs.py
Normal file
125
test/test_plugin_streamlabs.py
Normal file
@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('strmlabs://', {
|
||||
# No Access Token specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('strmlabs://a_bd_/', {
|
||||
# invalid Access Token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso', {
|
||||
# access token
|
||||
'instance': plugins.NotifyStreamlabs,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'strmlabs://I...o',
|
||||
}),
|
||||
# Test incorrect currency
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?currency=ABCD', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Test complete params - donations
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/'
|
||||
'?name=tt&identifier=pyt&amount=20¤cy=USD&call=donations',
|
||||
{'instance': plugins.NotifyStreamlabs, }),
|
||||
# Test complete params - donations
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/'
|
||||
'?image_href=https://example.org/rms.jpg'
|
||||
'&sound_href=https://example.org/rms.mp3',
|
||||
{'instance': plugins.NotifyStreamlabs, }),
|
||||
# Test complete params - alerts
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/'
|
||||
'?duration=1000&image_href=&'
|
||||
'sound_href=&alert_type=donation&special_text_color=crimson',
|
||||
{'instance': plugins.NotifyStreamlabs, }),
|
||||
# Test incorrect call
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/'
|
||||
'?name=tt&identifier=pyt&amount=20¤cy=USD&call=rms',
|
||||
{'instance': TypeError, }),
|
||||
# Test incorrect alert_type
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/'
|
||||
'?name=tt&identifier=pyt&amount=20¤cy=USD&alert_type=rms',
|
||||
{'instance': TypeError, }),
|
||||
# Test incorrect name
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?name=t', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?call=donations', {
|
||||
'instance': plugins.NotifyStreamlabs,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?call=alerts', {
|
||||
'instance': plugins.NotifyStreamlabs,
|
||||
# A failure has status set to zero
|
||||
# Test without an 'error' flag
|
||||
'requests_response_text': {
|
||||
'status': 0,
|
||||
},
|
||||
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?call=alerts', {
|
||||
'instance': plugins.NotifyStreamlabs,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('strmlabs://IcIcArukDQtuC1is1X1UdKZjTg118Lag2vScOmso/?call=donations', {
|
||||
'instance': plugins.NotifyStreamlabs,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_streamlabs_urls():
|
||||
"""
|
||||
NotifyStreamlabs() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -36,9 +36,9 @@ logging.disable(logging.CRITICAL)
|
||||
|
||||
@mock.patch('syslog.syslog')
|
||||
@mock.patch('syslog.openlog')
|
||||
def test_notify_syslog_by_url(openlog, syslog):
|
||||
def test_plugin_syslog_by_url(openlog, syslog):
|
||||
"""
|
||||
API: Syslog URL Testing
|
||||
NotifySyslog() Apprise URLs
|
||||
|
||||
"""
|
||||
# an invalid URL
|
||||
@ -105,9 +105,9 @@ def test_notify_syslog_by_url(openlog, syslog):
|
||||
|
||||
@mock.patch('syslog.syslog')
|
||||
@mock.patch('syslog.openlog')
|
||||
def test_notify_syslog_by_class(openlog, syslog):
|
||||
def test_plugin_syslog_edge_cases(openlog, syslog):
|
||||
"""
|
||||
API: Syslog Class Testing
|
||||
NotifySyslog() Edge Cases
|
||||
|
||||
"""
|
||||
|
||||
@ -131,10 +131,10 @@ def test_notify_syslog_by_class(openlog, syslog):
|
||||
@mock.patch('syslog.openlog')
|
||||
@mock.patch('socket.socket')
|
||||
@mock.patch('os.getpid')
|
||||
def test_notify_syslog_remote(
|
||||
def test_plugin_syslog_remote(
|
||||
mock_getpid, mock_socket, mock_openlog, mock_syslog):
|
||||
"""
|
||||
API: Syslog Remote Testing
|
||||
NotifySyslog() Remote Testing
|
||||
|
||||
"""
|
||||
payload = "test"
|
85
test/test_plugin_techululs_push.py
Normal file
85
test/test_plugin_techululs_push.py
Normal file
@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# a test UUID we can use
|
||||
UUID4 = '8b799edf-6f98-4d3a-9be7-2862fb4e5752'
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('push://', {
|
||||
# Missing API Key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Invalid API Key
|
||||
('push://%s' % ('+' * 24), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# APIkey
|
||||
('push://%s' % UUID4, {
|
||||
'instance': plugins.NotifyTechulusPush,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'push://8...2/',
|
||||
}),
|
||||
# API Key + bad url
|
||||
('push://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('push://%s' % UUID4, {
|
||||
'instance': plugins.NotifyTechulusPush,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('push://%s' % UUID4, {
|
||||
'instance': plugins.NotifyTechulusPush,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('push://%s' % UUID4, {
|
||||
'instance': plugins.NotifyTechulusPush,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_techulus_push_urls():
|
||||
"""
|
||||
NotifyTechulusPush() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -35,6 +35,7 @@ from apprise import AppriseAttachment
|
||||
from apprise import AppriseAsset
|
||||
from apprise import NotifyType
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
@ -43,14 +44,185 @@ logging.disable(logging.CRITICAL)
|
||||
# Attachment Directory
|
||||
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyTelegram
|
||||
##################################
|
||||
('tgram://', {
|
||||
'instance': None,
|
||||
}),
|
||||
# Simple Message
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Simple Message (no images)
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Simple Message with multiple chat names
|
||||
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Simple Message with multiple chat names
|
||||
('tgram://123456789:abcdefg_hijklmnop/?to=id1,id2', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Simple Message with an invalid chat ID
|
||||
('tgram://123456789:abcdefg_hijklmnop/%$/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# Notify will fail
|
||||
'response': False,
|
||||
}),
|
||||
# Simple Message with multiple chat ids
|
||||
('tgram://123456789:abcdefg_hijklmnop/id1/id2/23423/-30/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Simple Message with multiple chat ids (no images)
|
||||
('tgram://123456789:abcdefg_hijklmnop/id1/id2/23423/-30/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Support bot keyword prefix
|
||||
('tgram://bottest@123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Testing image
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Testing invalid format (fall's back to html)
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=invalid', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Testing empty format (falls back to html)
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Testing valid formats
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=markdown', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=html', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=text', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Test Silent Settings
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?silent=yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?silent=no', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Test Web Page Preview Settings
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?preview=yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?preview=no', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
}),
|
||||
# Simple Message without image
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
# Invalid Bot Token
|
||||
('tgram://alpha:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# AuthToken + bad url
|
||||
('tgram://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# force a failure without an image specified
|
||||
'include_image': False,
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# force a failure with multiple chat_ids
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# force a failure without an image specified
|
||||
'include_image': False,
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# throw a bizzare code forcing us to fail to look it up without
|
||||
# having an image included
|
||||
'include_image': False,
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
# Test with image set
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# throw a bizzare code forcing us to fail to look it up without
|
||||
# having an image included
|
||||
'include_image': True,
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||
'instance': plugins.NotifyTelegram,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them without images set
|
||||
'include_image': True,
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_telegram_urls():
|
||||
"""
|
||||
NotifyTelegram() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_telegram_plugin(mock_post, mock_get):
|
||||
def test_plugin_telegram_general(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyTelegram() Tests
|
||||
NotifyTelegram() General Tests
|
||||
|
||||
"""
|
||||
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
245
test/test_plugin_twilio.py
Normal file
245
test/test_plugin_twilio.py
Normal file
@ -0,0 +1,245 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
import pytest
|
||||
from json import dumps
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('twilio://', {
|
||||
# No Account SID specified
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twilio://:@/', {
|
||||
# invalid Auth token
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twilio://AC{}@12345678'.format('a' * 32), {
|
||||
# Just sid provided
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twilio://AC{}:{}@_'.format('a' * 32, 'b' * 32), {
|
||||
# sid and token provided but invalid from
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||
# using short-code (5 characters) without a target
|
||||
# We can still instantiate ourselves with a valid short code
|
||||
'instance': plugins.NotifyTwilio,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||
# sid and token provided and from but invalid from no
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}/123/{}/abcd/'.format(
|
||||
'a' * 32, 'b' * 32, '3' * 11, '9' * 15), {
|
||||
# valid everything but target numbers
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://AC{}:{}@12345/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
|
||||
# using short-code (5 characters)
|
||||
'instance': plugins.NotifyTwilio,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'twilio://...aaaa:b...b@12345',
|
||||
}),
|
||||
('twilio://AC{}:{}@123456/{}'.format('a' * 32, 'b' * 32, '4' * 11), {
|
||||
# using short-code (6 characters)
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '5' * 11), {
|
||||
# using phone no with no target - we text ourselves in
|
||||
# this case
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://_?sid=AC{}&token={}&from={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11), {
|
||||
# use get args to acomplish the same thing
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://_?sid=AC{}&token={}&source={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11), {
|
||||
# use get args to acomplish the same thing (use source instead of from)
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://_?sid=AC{}&token={}&from={}&to={}'.format(
|
||||
'a' * 32, 'b' * 32, '5' * 11, '7' * 13), {
|
||||
# use to=
|
||||
'instance': plugins.NotifyTwilio,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '6' * 11), {
|
||||
'instance': plugins.NotifyTwilio,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '6' * 11), {
|
||||
'instance': plugins.NotifyTwilio,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_twilio_urls():
|
||||
"""
|
||||
NotifyTwilio() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_twilio_auth(mock_post):
|
||||
"""
|
||||
NotifyTwilio() Auth
|
||||
- account-wide auth token
|
||||
- API key and its own auth token
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
response = mock.Mock()
|
||||
response.content = ''
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
account_sid = 'AC{}'.format('b' * 32)
|
||||
apikey = 'SK{}'.format('b' * 32)
|
||||
auth_token = '{}'.format('b' * 32)
|
||||
source = '+1 (555) 123-3456'
|
||||
dest = '+1 (555) 987-6543'
|
||||
message_contents = "test"
|
||||
|
||||
# Variation of initialization without API key
|
||||
obj = Apprise.instantiate(
|
||||
'twilio://{}:{}@{}/{}'
|
||||
.format(account_sid, auth_token, source, dest))
|
||||
assert isinstance(obj, plugins.NotifyTwilio) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body=message_contents) is True
|
||||
|
||||
# Variation of initialization with API key
|
||||
obj = Apprise.instantiate(
|
||||
'twilio://{}:{}@{}/{}?apikey={}'
|
||||
.format(account_sid, auth_token, source, dest, apikey))
|
||||
assert isinstance(obj, plugins.NotifyTwilio) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body=message_contents) is True
|
||||
|
||||
# Validate expected call parameters
|
||||
assert mock_post.call_count == 2
|
||||
first_call = mock_post.call_args_list[0]
|
||||
second_call = mock_post.call_args_list[1]
|
||||
|
||||
# URL and message parameters are the same for both calls
|
||||
assert first_call[0][0] == \
|
||||
second_call[0][0] == \
|
||||
'https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json'.format(
|
||||
account_sid)
|
||||
assert first_call[1]['data']['Body'] == \
|
||||
second_call[1]['data']['Body'] == \
|
||||
message_contents
|
||||
assert first_call[1]['data']['From'] == \
|
||||
second_call[1]['data']['From'] == \
|
||||
'+15551233456'
|
||||
assert first_call[1]['data']['To'] == \
|
||||
second_call[1]['data']['To'] == \
|
||||
'+15559876543'
|
||||
|
||||
# Auth differs depending on if API Key is used
|
||||
assert first_call[1]['auth'] == (account_sid, auth_token)
|
||||
assert second_call[1]['auth'] == (apikey, auth_token)
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_plugin_twilio_edge_cases(mock_post):
|
||||
"""
|
||||
NotifyTwilio() Edge Cases
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
# Prepare our response
|
||||
response = requests.Request()
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
account_sid = 'AC{}'.format('b' * 32)
|
||||
auth_token = '{}'.format('b' * 32)
|
||||
source = '+1 (555) 123-3456'
|
||||
|
||||
# No account_sid specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwilio(
|
||||
account_sid=None, auth_token=auth_token, source=source)
|
||||
|
||||
# No auth_token specified
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwilio(
|
||||
account_sid=account_sid, auth_token=None, source=source)
|
||||
|
||||
# a error response
|
||||
response.status_code = 400
|
||||
response.content = dumps({
|
||||
'code': 21211,
|
||||
'message': "The 'To' number +1234567 is not a valid phone number.",
|
||||
})
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize our object
|
||||
obj = plugins.NotifyTwilio(
|
||||
account_sid=account_sid, auth_token=auth_token, source=source)
|
||||
|
||||
# We will fail with the above error code
|
||||
assert obj.notify('title', 'body', 'info') is False
|
@ -28,15 +28,83 @@ import requests
|
||||
from json import dumps
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('twist://', {
|
||||
# Missing Email and Login
|
||||
'instance': None,
|
||||
}),
|
||||
('twist://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('twist://user@example.com/', {
|
||||
# No password
|
||||
'instance': None,
|
||||
}),
|
||||
('twist://user@example.com/password', {
|
||||
# Password acceptable as first entry in path
|
||||
'instance': plugins.NotifyTwist,
|
||||
# Expected notify() response is False because internally we would
|
||||
# have failed to login
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twist://password:user1@example.com', {
|
||||
# password:login acceptable
|
||||
'instance': plugins.NotifyTwist,
|
||||
# Expected notify() response is False because internally we would
|
||||
# have failed to login
|
||||
'notify_response': False,
|
||||
|
||||
def test_twist_plugin_init():
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'twist://****:user1@example.com',
|
||||
}),
|
||||
('twist://password:user2@example.com', {
|
||||
# password:login acceptable
|
||||
'instance': plugins.NotifyTwist,
|
||||
# Expected notify() response is False because internally we would
|
||||
# have logged in, but we would have failed to look up the #General
|
||||
# channel and workspace.
|
||||
'requests_response_text': {
|
||||
# Login expected response
|
||||
'id': 1234,
|
||||
'default_workspace': 9876,
|
||||
},
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twist://password:user2@example.com', {
|
||||
'instance': plugins.NotifyTwist,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('twist://password:user2@example.com', {
|
||||
'instance': plugins.NotifyTwist,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_twist_urls():
|
||||
"""
|
||||
API: NotifyTwist init()
|
||||
NotifyTwist() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
def test_plugin_twist_init():
|
||||
"""
|
||||
NotifyTwist() init()
|
||||
|
||||
"""
|
||||
try:
|
||||
@ -95,9 +163,9 @@ def test_twist_plugin_init():
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_twist_plugin_auth(mock_post, mock_get):
|
||||
def test_plugin_twist_auth(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyTwist login/logout()
|
||||
NotifyTwist() login/logout()
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -214,11 +282,9 @@ def test_twist_plugin_auth(mock_post, mock_get):
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_twist_plugin_cache(mock_post, mock_get):
|
||||
def test_plugin_twist_cache(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyTwist cache()
|
||||
|
||||
Test cache handling
|
||||
NotifyTwist() Cache Handling
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
@ -303,9 +369,9 @@ def test_twist_plugin_cache(mock_post, mock_get):
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_twist_plugin_fetch(mock_post, mock_get):
|
||||
def test_plugin_twist_fetch(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyTwist fetch()
|
||||
NotifyTwist() fetch()
|
||||
|
||||
fetch() is a wrapper that handles all kinds of edge cases and even
|
||||
attempts to re-authenticate to the Twist server if our token
|
@ -30,59 +30,176 @@ import requests
|
||||
from json import dumps
|
||||
from datetime import datetime
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
##################################
|
||||
# NotifyTwitter
|
||||
##################################
|
||||
('twitter://', {
|
||||
# Missing Consumer API Key
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://:@/', {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://consumer_key', {
|
||||
# Missing Keys
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://consumer_key/consumer_secret/', {
|
||||
# Missing Keys
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://consumer_key/consumer_secret/access_token/', {
|
||||
# Missing Access Secret
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://consumer_key/consumer_secret/access_token/access_secret', {
|
||||
# No user mean's we message ourselves
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# Expected notify() response False (because we won't be able
|
||||
# to detect our user)
|
||||
'notify_response': False,
|
||||
|
||||
def test_twitter_plugin_init():
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'twitter://c...y/****/a...n/****',
|
||||
}),
|
||||
('twitter://consumer_key/consumer_secret/access_token/access_secret'
|
||||
'?cache=no', {
|
||||
# No user mean's we message ourselves
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# However we'll be okay if we return a proper response
|
||||
'requests_response_text': {
|
||||
'id': 12345,
|
||||
'screen_name': 'test'
|
||||
},
|
||||
}),
|
||||
('twitter://consumer_key/consumer_secret/access_token/access_secret', {
|
||||
# No user mean's we message ourselves
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# However we'll be okay if we return a proper response
|
||||
'requests_response_text': {
|
||||
'id': 12345,
|
||||
'screen_name': 'test'
|
||||
},
|
||||
}),
|
||||
# A duplicate of the entry above, this will cause cache to be referenced
|
||||
('twitter://consumer_key/consumer_secret/access_token/access_secret', {
|
||||
# No user mean's we message ourselves
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# However we'll be okay if we return a proper response
|
||||
'requests_response_text': {
|
||||
'id': 12345,
|
||||
'screen_name': 'test'
|
||||
},
|
||||
}),
|
||||
# handle cases where the screen_name is missing from the response causing
|
||||
# an exception during parsing
|
||||
('twitter://consumer_key/consumer_secret2/access_token/access_secret', {
|
||||
# No user mean's we message ourselves
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# However we'll be okay if we return a proper response
|
||||
'requests_response_text': {
|
||||
'id': 12345,
|
||||
},
|
||||
# due to a mangled response_text we'll fail
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twitter://user@consumer_key/csecret2/access_token/access_secret/-/%/', {
|
||||
# One Invalid User
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# Expected notify() response False (because we won't be able
|
||||
# to detect our user)
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twitter://user@consumer_key/csecret/access_token/access_secret'
|
||||
'?cache=No', {
|
||||
# No Cache
|
||||
'instance': plugins.NotifyTwitter,
|
||||
'requests_response_text': [{
|
||||
'id': 12345,
|
||||
'screen_name': 'user'
|
||||
}],
|
||||
}),
|
||||
('twitter://user@consumer_key/csecret/access_token/access_secret', {
|
||||
# We're good!
|
||||
'instance': plugins.NotifyTwitter,
|
||||
'requests_response_text': [{
|
||||
'id': 12345,
|
||||
'screen_name': 'user'
|
||||
}],
|
||||
}),
|
||||
# A duplicate of the entry above, this will cause cache to be referenced
|
||||
# for this reason, we don't even need to return a valid response
|
||||
('twitter://user@consumer_key/csecret/access_token/access_secret', {
|
||||
# We're identifying the same user we already sent to
|
||||
'instance': plugins.NotifyTwitter,
|
||||
}),
|
||||
('twitter://ckey/csecret/access_token/access_secret?mode=tweet', {
|
||||
# A Public Tweet
|
||||
'instance': plugins.NotifyTwitter,
|
||||
}),
|
||||
('twitter://user@ckey/csecret/access_token/access_secret?mode=invalid', {
|
||||
# An invalid mode
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('twitter://usera@consumer_key/consumer_secret/access_token/'
|
||||
'access_secret/user/?to=userb', {
|
||||
# We're good!
|
||||
'instance': plugins.NotifyTwitter,
|
||||
'requests_response_text': [{
|
||||
'id': 12345,
|
||||
'screen_name': 'usera'
|
||||
}, {
|
||||
'id': 12346,
|
||||
'screen_name': 'userb'
|
||||
}, {
|
||||
# A garbage entry we can test exception handling on
|
||||
'id': 123,
|
||||
}],
|
||||
}),
|
||||
('twitter://ckey/csecret/access_token/access_secret', {
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('twitter://ckey/csecret/access_token/access_secret', {
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
('twitter://ckey/csecret/access_token/access_secret?mode=tweet', {
|
||||
'instance': plugins.NotifyTwitter,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_twitter_urls():
|
||||
"""
|
||||
API: NotifyTwitter Plugin() (pt2)
|
||||
NotifyTwitter() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey=None, csecret=None, akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret=None, akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret=None)
|
||||
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
user='l2gnux'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
# Invalid Target User
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
targets='%G@rB@g3')
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
def test_notify_twitter_plugin_general(mock_post, mock_get):
|
||||
def test_plugin_twitter_general(mock_post, mock_get):
|
||||
"""
|
||||
API: NotifyTwitter() General Tests
|
||||
NotifyTwitter() General Tests
|
||||
|
||||
"""
|
||||
ckey = 'ckey'
|
||||
@ -245,3 +362,45 @@ def test_notify_twitter_plugin_general(mock_post, mock_get):
|
||||
del plugins.NotifyTwitter._whoami_cache
|
||||
obj.ckey = 'different.again'
|
||||
assert obj.send(body="test") is True
|
||||
|
||||
|
||||
def test_plugin_twitter_edge_cases():
|
||||
"""
|
||||
NotifyTwitter() Edge Cases
|
||||
|
||||
"""
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey=None, csecret=None, akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret=None, akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey=None, asecret=None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret=None)
|
||||
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
user='l2gnux'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
# Invalid Target User
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
targets='%G@rB@g3')
|
91
test/test_plugin_webex_teams.py
Normal file
91
test/test_plugin_webex_teams.py
Normal file
@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('wxteams://', {
|
||||
# Teams Token missing
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('wxteams://:@/', {
|
||||
# We don't have strict host checking on for wxteams, so this URL
|
||||
# actually becomes parseable and :@ becomes a hostname.
|
||||
# The below errors because a second token wasn't found
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('wxteams://{}'.format('a' * 80), {
|
||||
# token provided - we're good
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'wxteams://a...a/',
|
||||
}),
|
||||
# Support Native URLs
|
||||
('https://api.ciscospark.com/v1/webhooks/incoming/{}'.format('a' * 80), {
|
||||
# token provided - we're good
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
}),
|
||||
# Support Native URLs with arguments
|
||||
('https://api.ciscospark.com/v1/webhooks/incoming/{}?format=text'.format(
|
||||
'a' * 80), {
|
||||
# token provided - we're good
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
}),
|
||||
('wxteams://{}'.format('a' * 80), {
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('wxteams://{}'.format('a' * 80), {
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('wxteams://{}'.format('a' * 80), {
|
||||
'instance': plugins.NotifyWebexTeams,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_webex_teams_urls():
|
||||
"""
|
||||
NotifyWebexTeams() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
@ -23,12 +23,11 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import pytest
|
||||
import mock
|
||||
import sys
|
||||
import six
|
||||
import types
|
||||
|
||||
# Rebuild our Apprise environment
|
||||
import apprise
|
||||
|
||||
try:
|
||||
@ -47,9 +46,14 @@ import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
def test_windows_plugin():
|
||||
@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+")
|
||||
@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():
|
||||
"""
|
||||
API: NotifyWindows Plugin()
|
||||
NotifyWindows() General Checks (via non-Windows platform)
|
||||
|
||||
"""
|
||||
|
||||
@ -116,7 +120,108 @@ def test_windows_plugin():
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert obj._enabled is True
|
||||
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(), six.string_types) is True
|
||||
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(), six.string_types) is True
|
||||
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(), six.string_types) is True
|
||||
# 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_update_window, mock_loadimage, mock_notify):
|
||||
"""
|
||||
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(), six.string_types) is True
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert obj.enabled is True
|
||||
|
||||
# _on_destroy check
|
||||
obj._on_destroy(0, '', 0, 0)
|
||||
@ -166,26 +271,29 @@ def test_windows_plugin():
|
||||
# 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
|
||||
mock_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
|
||||
mock_loadimage.side_effect = None
|
||||
|
||||
# Test our global exception handling
|
||||
win32gui.UpdateWindow.side_effect = AttributeError
|
||||
mock_update_window.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
|
||||
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
|
||||
obj.enabled = False
|
||||
assert obj.notify(
|
||||
title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
191
test/test_plugin_xbmc_kodi.py
Normal file
191
test/test_plugin_xbmc_kodi.py
Normal file
@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# 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
|
||||
# THE SOFTWARE.
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('kodi://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('kodis://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('kodi://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodi://192.168.4.1', {
|
||||
# Support IPv4 Addresses
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodi://[2001:db8:002a:3256:adfe:05c0:0003:0006]', {
|
||||
# Support IPv6 Addresses
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodi://user:pass@localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kodi://user:****@localhost',
|
||||
}),
|
||||
('kodi://localhost:8080', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodi://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodis://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodis://user:pass@localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodis://localhost:8080/path/', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('kodis://user:password@localhost:8080', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'kodis://user:****@localhost:8080',
|
||||
}),
|
||||
('kodi://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Experement with different notification types
|
||||
'notify_type': NotifyType.WARNING,
|
||||
}),
|
||||
('kodi://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Experement with different notification types
|
||||
'notify_type': NotifyType.FAILURE,
|
||||
}),
|
||||
('kodis://localhost:443', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('kodi://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('kodi://user:pass@localhost:8081', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('kodi://user:pass@localhost:8082', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('kodi://user:pass@localhost:8083', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
|
||||
#
|
||||
# XMBC (Legacy Platform) Shares this same KODI Plugin
|
||||
#
|
||||
|
||||
('xbmc://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('xbmc://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://localhost?duration=14', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://localhost?duration=invalid', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://localhost?duration=-1', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://user:pass@localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://localhost:8080', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://user:pass@localhost:8080', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
}),
|
||||
('xbmc://user@localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# don't include an image by default
|
||||
'include_image': False,
|
||||
}),
|
||||
('xbmc://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Experement with different notification types
|
||||
'notify_type': NotifyType.WARNING,
|
||||
}),
|
||||
('xbmc://localhost', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Experement with different notification types
|
||||
'notify_type': NotifyType.FAILURE,
|
||||
}),
|
||||
('xbmc://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('xbmc://user:pass@localhost:8081', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('xbmc://user:pass@localhost:8082', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('xbmc://user:pass@localhost:8083', {
|
||||
'instance': plugins.NotifyXBMC,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_xbmc_kodi_urls():
|
||||
"""
|
||||
NotifyXBMC() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user