Refactored qa, unit testing, and service init; refs #157 (#160)

pull/162/head
Chris Caron 2019-10-09 12:39:31 -04:00 committed by GitHub
parent 44a21651b3
commit c6922d8f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 2066 additions and 2001 deletions

View File

@ -40,6 +40,7 @@ except ImportError:
from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..utils import parse_bool
from ..utils import validate_regex
from ..common import NotifyType
from ..common import NotifyImageSize
from ..AppriseLocale import gettext_lazy as _
@ -58,11 +59,6 @@ IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
# this plugin supports it.
IS_DEVICETOKEN = re.compile(r'^[A-Z0-9]{64}$', re.I)
# Both an access key and seret key are created and assigned to each project
# you create on the boxcar website
VALIDATE_ACCESS = re.compile(r'[A-Z0-9_-]{64}', re.I)
VALIDATE_SECRET = re.compile(r'[A-Z0-9_-]{64}', re.I)
# Used to break apart list of potential tags by their delimiter into a useable
# list.
TAGS_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
@ -105,30 +101,30 @@ class NotifyBoxcar(NotifyBase):
'access_key': {
'name': _('Access Key'),
'type': 'string',
'regex': (r'[A-Z0-9_-]{64}', 'i'),
'private': True,
'required': True,
'regex': (r'^[A-Z0-9_-]{64}$', 'i'),
'map_to': 'access',
},
'secret_key': {
'name': _('Secret Key'),
'type': 'string',
'regex': (r'[A-Z0-9_-]{64}', 'i'),
'private': True,
'required': True,
'regex': (r'^[A-Z0-9_-]{64}$', 'i'),
'map_to': 'secret',
},
'target_tag': {
'name': _('Target Tag ID'),
'type': 'string',
'prefix': '@',
'regex': (r'[A-Z0-9]{1,63}', 'i'),
'regex': (r'^[A-Z0-9]{1,63}$', 'i'),
'map_to': 'targets',
},
'target_device': {
'name': _('Target Device ID'),
'type': 'string',
'regex': (r'[A-Z0-9]{64}', 'i'),
'regex': (r'^[A-Z0-9]{64}$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -163,33 +159,21 @@ class NotifyBoxcar(NotifyBase):
# Initialize device_token list
self.device_tokens = list()
try:
# Access Key (associated with project)
self.access = access.strip()
except AttributeError:
msg = 'The specified access key is invalid.'
# Access Key (associated with project)
self.access = validate_regex(
access, *self.template_tokens['access_key']['regex'])
if not self.access:
msg = 'An invalid Boxcar Access Key ' \
'({}) was specified.'.format(access)
self.logger.warning(msg)
raise TypeError(msg)
try:
# Secret Key (associated with project)
self.secret = secret.strip()
except AttributeError:
msg = 'The specified secret key is invalid.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_ACCESS.match(self.access):
msg = 'The access key specified ({}) is invalid.'\
.format(self.access)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_SECRET.match(self.secret):
msg = 'The secret key specified ({}) is invalid.'\
.format(self.secret)
# Secret Key (associated with project)
self.secret = validate_regex(
secret, *self.template_tokens['secret_key']['regex'])
if not self.secret:
msg = 'An invalid Boxcar Secret Key ' \
'({}) was specified.'.format(secret)
self.logger.warning(msg)
raise TypeError(msg)
@ -228,7 +212,6 @@ class NotifyBoxcar(NotifyBase):
"""
Perform Boxcar Notification
"""
headers = {
'User-Agent': self.app_id,
'Content-Type': 'application/json'

View File

@ -112,7 +112,7 @@ class NotifyClickSend(NotifyBase):
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'targets': {

View File

@ -131,7 +131,7 @@ class NotifyD7Networks(NotifyBase):
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -227,6 +227,8 @@ class NotifyD7Networks(NotifyBase):
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Depending on whether we are set to batch mode or single mode this

View File

@ -240,6 +240,8 @@ class NotifyDBus(NotifyBase):
# or not.
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform DBus Notification

View File

@ -49,6 +49,7 @@ from ..common import NotifyImageSize
from ..common import NotifyFormat
from ..common import NotifyType
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
@ -144,20 +145,22 @@ class NotifyDiscord(NotifyBase):
"""
super(NotifyDiscord, self).__init__(**kwargs)
if not webhook_id:
msg = 'An invalid Client ID was specified.'
# Webhook ID (associated with project)
self.webhook_id = validate_regex(webhook_id)
if not self.webhook_id:
msg = 'An invalid Discord Webhook ID ' \
'({}) was specified.'.format(webhook_id)
self.logger.warning(msg)
raise TypeError(msg)
if not webhook_token:
msg = 'An invalid Webhook Token was specified.'
# Webhook Token (associated with project)
self.webhook_token = validate_regex(webhook_token)
if not self.webhook_token:
msg = 'An invalid Discord Webhook Token ' \
'({}) was specified.'.format(webhook_token)
self.logger.warning(msg)
raise TypeError(msg)
# Store our data
self.webhook_id = webhook_id
self.webhook_token = webhook_token
# Text To Speech
self.tts = tts

View File

@ -139,7 +139,7 @@ class NotifyEmby(NotifyBase):
if not self.user:
# User was not specified
msg = 'No Username was specified.'
msg = 'No Emby username was specified.'
self.logger.warning(msg)
raise TypeError(msg)

View File

@ -91,6 +91,8 @@ class NotifyFaast(NotifyBase):
# Associate an image with our post
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Faast Notification

View File

@ -47,6 +47,7 @@ from ..common import NotifyFormat
from ..common import NotifyImageSize
from ..utils import parse_list
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
@ -56,12 +57,8 @@ FLOCK_HTTP_ERROR_MAP = {
}
# Used to detect a channel/user
IS_CHANNEL_RE = re.compile(r'^(#|g:)(?P<id>[A-Z0-9_]{12})$', re.I)
IS_USER_RE = re.compile(r'^(@|u:)?(?P<id>[A-Z0-9_]{12})$', re.I)
# Token required as part of the API request
# /134b8gh0-eba0-4fa9-ab9c-257ced0e8221
IS_API_TOKEN = re.compile(r'^[a-z0-9-]{24}$', re.I)
IS_CHANNEL_RE = re.compile(r'^(#|g:)(?P<id>[A-Z0-9_]+)$', re.I)
IS_USER_RE = re.compile(r'^(@|u:)?(?P<id>[A-Z0-9_]+)$', re.I)
class NotifyFlock(NotifyBase):
@ -103,7 +100,7 @@ class NotifyFlock(NotifyBase):
'token': {
'name': _('Access Key'),
'type': 'string',
'regex': (r'[a-z0-9-]{24}', 'i'),
'regex': (r'^[a-z0-9-]{24}$', 'i'),
'private': True,
'required': True,
},
@ -115,14 +112,14 @@ class NotifyFlock(NotifyBase):
'name': _('To User ID'),
'type': 'string',
'prefix': '@',
'regex': (r'[A-Z0-9_]{12}', 'i'),
'regex': (r'^[A-Z0-9_]{12}$', 'i'),
'map_to': 'targets',
},
'to_channel': {
'name': _('To Channel ID'),
'type': 'string',
'prefix': '#',
'regex': (r'[A-Z0-9_]{12}', 'i'),
'regex': (r'^[A-Z0-9_]{12}$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -153,15 +150,18 @@ class NotifyFlock(NotifyBase):
# Build ourselves a target list
self.targets = list()
# Initialize our token object
self.token = token.strip()
if not IS_API_TOKEN.match(self.token):
msg = 'The Flock API Token specified ({}) is invalid.'.format(
self.token)
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'An invalid Flock Access Key ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
# Track whether or not we want to send an image with our notification
# or not.
self.include_image = include_image
# Track any issues
has_error = False
@ -183,15 +183,13 @@ class NotifyFlock(NotifyBase):
self.logger.warning(
'Ignoring invalid target ({}) specified.'.format(target))
if has_error and len(self.targets) == 0:
if has_error and not self.targets:
# We have a bot token and no target(s) to message
msg = 'No targets found with specified Flock Bot Token.'
msg = 'No Flock targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
# Track whether or not we want to send an image with our notification
# or not.
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""

View File

@ -50,14 +50,12 @@ from ..common import NotifyFormat
from ..common import NotifyType
from ..utils import parse_list
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# API Gitter URL
GITTER_API_URL = 'https://api.gitter.im/v1'
# Used to validate your personal access token
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{40}$', re.I)
# Used to break path apart into list of targets
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
@ -112,9 +110,9 @@ class NotifyGitter(NotifyBase):
'token': {
'name': _('Token'),
'type': 'string',
'regex': (r'[a-z0-9]{40}', 'i'),
'private': True,
'required': True,
'regex': (r'^[a-z0-9]{40}$', 'i'),
},
'targets': {
'name': _('Rooms'),
@ -141,24 +139,21 @@ class NotifyGitter(NotifyBase):
"""
super(NotifyGitter, self).__init__(**kwargs)
try:
# The personal access token associated with the account
self.token = token.strip()
except AttributeError:
# Token was None
msg = 'No API Token was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN.match(self.token):
msg = 'The Personal Access Token specified ({}) is invalid.' \
.format(token)
# Secret Key (associated with project)
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'An invalid Gitter API Token ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
# Parse our targets
self.targets = parse_list(targets)
if not self.targets:
msg = 'There are no valid Gitter targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
# Used to track maping of rooms to their numeric id lookup for
# messaging
@ -168,6 +163,8 @@ class NotifyGitter(NotifyBase):
# or not.
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Gitter Notification
@ -183,8 +180,6 @@ class NotifyGitter(NotifyBase):
if image_url:
body = '![alt]({})\n{}'.format(image_url, body)
# Create a copy of the targets list
targets = list(self.targets)
if self._room_mapping is None:
# Populate our room mapping
self._room_mapping = {}
@ -225,10 +220,8 @@ class NotifyGitter(NotifyBase):
'uri': entry['uri'],
}
if len(targets) == 0:
# No targets specified
return False
# Create a copy of the targets list
targets = list(self.targets)
while len(targets):
target = targets.pop(0).lower()

View File

@ -150,6 +150,8 @@ class NotifyGnome(NotifyBase):
# or not.
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Gnome Notification

View File

@ -31,12 +31,12 @@
# f2c2688f0b5e6a816bbcec768ca1c0de5af76b88/ADD_MESSAGE_EXAMPLES.md#python
# API: https://gotify.net/docs/swagger-docs
import six
import requests
from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
@ -121,9 +121,12 @@ class NotifyGotify(NotifyBase):
"""
super(NotifyGotify, self).__init__(**kwargs)
if not isinstance(token, six.string_types):
msg = 'An invalid Gotify token was specified.'
self.logger.warning('msg')
# Token (associated with project)
self.token = validate_regex(token)
if not self.token:
msg = 'An invalid Gotify Token ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
if priority not in GOTIFY_PRIORITIES:
@ -138,11 +141,6 @@ class NotifyGotify(NotifyBase):
else:
self.schema = 'http'
# Our access token does not get created until we first
# authenticate with our Gotify server. The same goes for the
# user id below.
self.token = token
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):

View File

@ -324,7 +324,6 @@ class NotifyGrowl(NotifyBase):
# We're done early as we couldn't load the results
return results
# Apply our settings now
version = None
if 'version' in results['qsd'] and len(results['qsd']['version']):
# Allow the user to specify the version of the protocol to use.

View File

@ -46,6 +46,7 @@ from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
@ -148,22 +149,21 @@ class NotifyIFTTT(NotifyBase):
"""
super(NotifyIFTTT, self).__init__(**kwargs)
if not webhook_id:
msg = 'You must specify the Webhooks webhook_id.'
# Webhook ID (associated with project)
self.webhook_id = validate_regex(webhook_id)
if not self.webhook_id:
msg = 'An invalid IFTTT Webhook ID ' \
'({}) was specified.'.format(webhook_id)
self.logger.warning(msg)
raise TypeError(msg)
# Store our Events we wish to trigger
self.events = parse_list(events)
if not self.events:
msg = 'You must specify at least one event you wish to trigger on.'
self.logger.warning(msg)
raise TypeError(msg)
# Store our APIKey
self.webhook_id = webhook_id
# Tokens to include in post
self.add_tokens = {}
if add_tokens:

View File

@ -41,18 +41,16 @@ from ..common import NotifyImageSize
from ..common import NotifyType
from ..utils import parse_list
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_APIKEY = re.compile(r'[a-z0-9]{32}', re.I)
# Extend HTTP Error Messages
JOIN_HTTP_ERROR_MAP = {
401: 'Unauthorized - Invalid Token.',
}
# Used to detect a device
IS_DEVICE_RE = re.compile(r'([a-z0-9]{32})', re.I)
IS_DEVICE_RE = re.compile(r'^[a-z0-9]{32}$', re.I)
# Used to detect a device
IS_GROUP_RE = re.compile(
@ -64,6 +62,24 @@ IS_GROUP_RE = re.compile(
JOIN_IMAGE_XY = NotifyImageSize.XY_72
# Priorities
class JoinPriority(object):
LOW = -2
MODERATE = -1
NORMAL = 0
HIGH = 1
EMERGENCY = 2
JOIN_PRIORITIES = (
JoinPriority.LOW,
JoinPriority.MODERATE,
JoinPriority.NORMAL,
JoinPriority.HIGH,
JoinPriority.EMERGENCY,
)
class NotifyJoin(NotifyBase):
"""
A wrapper for Join Notifications
@ -104,14 +120,14 @@ class NotifyJoin(NotifyBase):
'apikey': {
'name': _('API Key'),
'type': 'string',
'regex': (r'[a-z0-9]{32}', 'i'),
'regex': (r'^[a-z0-9]{32}$', 'i'),
'private': True,
'required': True,
},
'device': {
'name': _('Device ID'),
'type': 'string',
'regex': (r'[a-z0-9]{32}', 'i'),
'regex': (r'^[a-z0-9]{32}$', 'i'),
'map_to': 'targets',
},
'group': {
@ -136,37 +152,79 @@ class NotifyJoin(NotifyBase):
'default': False,
'map_to': 'include_image',
},
'priority': {
'name': _('Priority'),
'type': 'choice:int',
'values': JOIN_PRIORITIES,
'default': JoinPriority.NORMAL,
},
'to': {
'alias_of': 'targets',
},
})
def __init__(self, apikey, targets, include_image=True, **kwargs):
def __init__(self, apikey, targets=None, include_image=True, priority=None,
**kwargs):
"""
Initialize Join Object
"""
super(NotifyJoin, self).__init__(**kwargs)
if not VALIDATE_APIKEY.match(apikey.strip()):
msg = 'The JOIN API Token specified ({}) is invalid.'\
.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.apikey = apikey.strip()
# Parse devices specified
self.devices = parse_list(targets)
if len(self.devices) == 0:
# Default to everyone
self.devices.append(self.default_join_group)
# Track whether or not we want to send an image with our notification
# or not.
self.include_image = include_image
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid Join API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# The Priority of the message
if priority not in JOIN_PRIORITIES:
self.priority = self.template_args['priority']['default']
else:
self.priority = priority
# Prepare a list of targets to store entries into
self.targets = list()
# Prepare a parsed list of targets
targets = parse_list(targets)
if len(targets) == 0:
# Default to everyone if our list was empty
self.targets.append(self.default_join_group)
return
# If we reach here we have some targets to parse
while len(targets):
# Parse our targets
target = targets.pop(0)
group_re = IS_GROUP_RE.match(target)
if group_re:
self.targets.append(
'group.{}'.format(group_re.group('name').lower()))
continue
elif IS_DEVICE_RE.match(target):
self.targets.append(target)
continue
self.logger.warning(
'Ignoring invalid Join device/group "{}"'.format(target)
)
if not self.targets:
msg = 'No Join targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Join Notification
@ -180,26 +238,17 @@ class NotifyJoin(NotifyBase):
# error tracking (used for function return)
has_error = False
# Create a copy of the devices list
devices = list(self.devices)
while len(devices):
device = devices.pop(0)
group_re = IS_GROUP_RE.match(device)
if group_re:
device = 'group.{}'.format(group_re.group('name').lower())
# Capture a list of our targets to notify
targets = list(self.targets)
elif not IS_DEVICE_RE.match(device):
self.logger.warning(
'Skipping specified invalid device/group "{}"'
.format(device)
)
# Mark our failure
has_error = True
continue
while len(targets):
# Pop the first element off of our list
target = targets.pop(0)
url_args = {
'apikey': self.apikey,
'deviceId': device,
'deviceId': target,
'priority': str(self.priority),
'title': title,
'text': body,
}
@ -242,7 +291,7 @@ class NotifyJoin(NotifyBase):
self.logger.warning(
'Failed to send Join notification to {}: '
'{}{}error={}.'.format(
device,
target,
status_str,
', ' if status_str else '',
r.status_code))
@ -255,12 +304,12 @@ class NotifyJoin(NotifyBase):
continue
else:
self.logger.info('Sent Join notification to %s.' % device)
self.logger.info('Sent Join notification to %s.' % target)
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Join:%s '
'notification.' % device
'notification.' % target
)
self.logger.debug('Socket Exception: %s' % str(e))
@ -274,20 +323,30 @@ class NotifyJoin(NotifyBase):
"""
Returns the URL built dynamically based on specified arguments.
"""
_map = {
JoinPriority.LOW: 'low',
JoinPriority.MODERATE: 'moderate',
JoinPriority.NORMAL: 'normal',
JoinPriority.HIGH: 'high',
JoinPriority.EMERGENCY: 'emergency',
}
# Define any arguments set
args = {
'format': self.notify_format,
'overflow': self.overflow_mode,
'priority':
_map[self.template_args['priority']['default']]
if self.priority not in _map else _map[self.priority],
'image': 'yes' if self.include_image else 'no',
'verify': 'yes' if self.verify_certificate else 'no',
}
return '{schema}://{apikey}/{devices}/?{args}'.format(
return '{schema}://{apikey}/{targets}/?{args}'.format(
schema=self.secure_protocol,
apikey=self.pprint(self.apikey, privacy, safe=''),
devices='/'.join([NotifyJoin.quote(x, safe='')
for x in self.devices]),
targets='/'.join([NotifyJoin.quote(x, safe='')
for x in self.targets]),
args=NotifyJoin.urlencode(args))
@staticmethod
@ -310,6 +369,23 @@ class NotifyJoin(NotifyBase):
# Unquote our API Key
results['apikey'] = NotifyJoin.unquote(results['apikey'])
# Set our priority
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': JoinPriority.LOW,
'm': JoinPriority.MODERATE,
'n': JoinPriority.NORMAL,
'h': JoinPriority.HIGH,
'e': JoinPriority.EMERGENCY,
}
try:
results['priority'] = \
_map[results['qsd']['priority'][0].lower()]
except KeyError:
# No priority was set
pass
# Our Devices
results['targets'] = list()
if results['user']:

View File

@ -33,27 +33,14 @@
# The API reference used to build this plugin was documented here:
# https://docs.kumulos.com/messaging/api/#sending-in-app-messages
#
import re
import requests
from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
#
# API Key is a UUID; below is the regex matching
UUID4_RE = \
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
# Secret Key Regex Mapping
SERVER_KEY_RE = r'[A-Z0-9+]{36}'
# API Key
VALIDATE_APIKEY = re.compile(UUID4_RE, re.I)
VALIDATE_SERVER_KEY = re.compile(SERVER_KEY_RE, re.I)
# Extend HTTP Error Messages
KUMULOS_HTTP_ERROR_MAP = {
401: 'Unauthorized - Invalid API and/or Server Key.',
@ -61,9 +48,6 @@ KUMULOS_HTTP_ERROR_MAP = {
400: 'Bad Request - Targeted users do not exist or have unsubscribed.',
}
# Used to break path apart into list of channels
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
class NotifyKumulos(NotifyBase):
"""
@ -103,14 +87,16 @@ class NotifyKumulos(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (UUID4_RE, 'i'),
# UUID4
'regex': (r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-'
r'[89ab][0-9a-f]{3}-[0-9a-f]{12}$', 'i')
},
'serverkey': {
'name': _('Server Key'),
'type': 'string',
'private': True,
'required': True,
'regex': (SERVER_KEY_RE, 'i'),
'regex': (r'^[A-Z0-9+]{36}$', 'i'),
},
})
@ -120,27 +106,21 @@ class NotifyKumulos(NotifyBase):
"""
super(NotifyKumulos, self).__init__(**kwargs)
if not apikey:
msg = 'The Kumulos API Key is not specified.'
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid Kumulos API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
self.apikey = apikey.strip()
if not VALIDATE_APIKEY.match(self.apikey):
msg = 'The Kumulos API Key specified ({}) is invalid.'\
.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
if not serverkey:
msg = 'The Kumulos Server Key is not specified.'
self.logger.warning(msg)
raise TypeError(msg)
self.serverkey = serverkey.strip()
if not VALIDATE_SERVER_KEY.match(self.serverkey):
msg = 'The Kumulos Server Key specified ({}) is invalid.'\
.format(serverkey)
# Server Key (associated with project)
self.serverkey = validate_regex(
serverkey, *self.template_tokens['serverkey']['regex'])
if not self.serverkey:
msg = 'An invalid Kumulos Server Key ' \
'({}) was specified.'.format(serverkey)
self.logger.warning(msg)
raise TypeError(msg)

View File

@ -37,11 +37,9 @@ import requests
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_AUTHKEY = re.compile(r'^[a-z0-9]+$', re.I)
# Some Phone Number Detection
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
@ -118,13 +116,14 @@ class NotifyMSG91(NotifyBase):
'name': _('Authentication Key'),
'type': 'string',
'required': True,
'regex': (r'[a-z0-9]+', 'i'),
'private': True,
'regex': (r'^[a-z0-9]+$', 'i'),
},
'target_phone': {
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -162,19 +161,12 @@ class NotifyMSG91(NotifyBase):
"""
super(NotifyMSG91, self).__init__(**kwargs)
try:
# The authentication key associated with the account
self.authkey = authkey.strip()
except AttributeError:
# Token was None
msg = 'No MSG91 authentication key was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_AUTHKEY.match(self.authkey):
msg = 'The MSG91 authentication key specified ({}) is invalid.'\
.format(self.authkey)
# Authentication Key (associated with project)
self.authkey = validate_regex(
authkey, *self.template_tokens['authkey']['regex'])
if not self.authkey:
msg = 'An invalid MSG91 Authentication Key ' \
'({}) was specified.'.format(authkey)
self.logger.warning(msg)
raise TypeError(msg)
@ -237,16 +229,19 @@ class NotifyMSG91(NotifyBase):
'({}) specified.'.format(target),
)
if not self.targets:
# We have a bot token and no target(s) to message
msg = 'No MSG91 targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform MSG91 Notification
"""
if not len(self.targets):
# There were no services to notify
self.logger.warning('There were no MSG91 targets to notify')
return False
# Prepare our headers
headers = {
'User-Agent': self.app_id,

View File

@ -69,24 +69,13 @@ from ..common import NotifyImageSize
from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Used to prepare our UUID regex matching
UUID4_RE = \
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
# Token required as part of the API request
# /AAAAAAAAA@AAAAAAAAA/........./.........
VALIDATE_TOKEN_A = re.compile(r'{}@{}'.format(UUID4_RE, UUID4_RE), re.I)
# Token required as part of the API request
# /................../BBBBBBBBB/..........
VALIDATE_TOKEN_B = re.compile(r'[A-Za-z0-9]{32}')
# Token required as part of the API request
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
VALIDATE_TOKEN_C = re.compile(UUID4_RE, re.I)
class NotifyMSTeams(NotifyBase):
"""
@ -124,26 +113,32 @@ class NotifyMSTeams(NotifyBase):
# Define our template tokens
template_tokens = dict(NotifyBase.template_tokens, **{
# Token required as part of the API request
# /AAAAAAAAA@AAAAAAAAA/........./.........
'token_a': {
'name': _('Token A'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'{}@{}'.format(UUID4_RE, UUID4_RE), 'i'),
'regex': (r'^{}@{}$'.format(UUID4_RE, UUID4_RE), 'i'),
},
# Token required as part of the API request
# /................../BBBBBBBBB/..........
'token_b': {
'name': _('Token B'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-z0-9]{32}', 'i'),
'regex': (r'^[A-Za-z0-9]{32}$', 'i'),
},
# Token required as part of the API request
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
'token_c': {
'name': _('Token C'),
'type': 'string',
'private': True,
'required': True,
'regex': (UUID4_RE, 'i'),
'regex': (r'^{}$'.format(UUID4_RE), 'i'),
},
})
@ -164,51 +159,35 @@ class NotifyMSTeams(NotifyBase):
"""
super(NotifyMSTeams, self).__init__(**kwargs)
if not token_a:
msg = 'The first MSTeams API token is not specified.'
self.token_a = validate_regex(
token_a, *self.template_tokens['token_a']['regex'])
if not self.token_a:
msg = 'An invalid MSTeams (first) Token ' \
'({}) was specified.'.format(token_a)
self.logger.warning(msg)
raise TypeError(msg)
if not token_b:
msg = 'The second MSTeams API token is not specified.'
self.token_b = validate_regex(
token_b, *self.template_tokens['token_b']['regex'])
if not self.token_b:
msg = 'An invalid MSTeams (second) Token ' \
'({}) was specified.'.format(token_b)
self.logger.warning(msg)
raise TypeError(msg)
if not token_c:
msg = 'The third MSTeams API token is not specified.'
self.token_c = validate_regex(
token_c, *self.template_tokens['token_c']['regex'])
if not self.token_c:
msg = 'An invalid MSTeams (third) Token ' \
'({}) was specified.'.format(token_c)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN_A.match(token_a.strip()):
msg = 'The first MSTeams API token specified ({}) is invalid.'\
.format(token_a)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_a = token_a.strip()
if not VALIDATE_TOKEN_B.match(token_b.strip()):
msg = 'The second MSTeams API token specified ({}) is invalid.'\
.format(token_b)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_b = token_b.strip()
if not VALIDATE_TOKEN_C.match(token_c.strip()):
msg = 'The third MSTeams API token specified ({}) is invalid.'\
.format(token_c)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_c = token_c.strip()
# Place a thumbnail image inline with the message body
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Microsoft Teams Notification

View File

@ -57,6 +57,7 @@ from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import is_email
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Provide some known codes Mailgun uses and what they translate to:
@ -169,19 +170,17 @@ class NotifyMailgun(NotifyBase):
"""
super(NotifyMailgun, self).__init__(**kwargs)
try:
# The personal access apikey associated with the account
self.apikey = apikey.strip()
except AttributeError:
# Token was None
msg = 'No API Key was specified.'
# API Key (associated with project)
self.apikey = validate_regex(apikey)
if not self.apikey:
msg = 'An invalid Mailgun API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# Validate our username
if not self.user:
msg = 'No username was specified.'
msg = 'No Mailgun username was specified.'
self.logger.warning(msg)
raise TypeError(msg)
@ -198,7 +197,7 @@ class NotifyMailgun(NotifyBase):
raise
except:
# Invalid region specified
msg = 'The region specified ({}) is invalid.' \
msg = 'The Mailgun region specified ({}) is invalid.' \
.format(region_name)
self.logger.warning(msg)
raise TypeError(msg)

View File

@ -23,7 +23,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import re
import six
import requests
from json import dumps
@ -33,15 +32,13 @@ from ..common import NotifyImageSize
from ..common import NotifyType
from ..utils import parse_bool
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Some Reference Locations:
# - https://docs.mattermost.com/developer/webhooks-incoming.html
# - https://docs.mattermost.com/administration/config-settings.html
# Used to validate Authorization Token
VALIDATE_AUTHTOKEN = re.compile(r'[a-z0-9]{24,32}', re.I)
class NotifyMatterMost(NotifyBase):
"""
@ -97,7 +94,7 @@ class NotifyMatterMost(NotifyBase):
'authtoken': {
'name': _('Access Key'),
'type': 'string',
'regex': (r'[a-z0-9]{24,32}', 'i'),
'regex': (r'^[a-z0-9]{24,32}$', 'i'),
'private': True,
'required': True,
},
@ -152,17 +149,12 @@ class NotifyMatterMost(NotifyBase):
self.fullpath = '' if not isinstance(
fullpath, six.string_types) else fullpath.strip()
# Our Authorization Token
self.authtoken = authtoken
# Validate authtoken
if not authtoken:
msg = 'Missing MatterMost Authorization Token.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_AUTHTOKEN.match(authtoken):
msg = 'Invalid MatterMost Authorization Token Specified.'
# Authorization Token (associated with project)
self.authtoken = validate_regex(
authtoken, *self.template_tokens['authtoken']['regex'])
if not self.authtoken:
msg = 'An invalid MatterMost Authorization Token ' \
'({}) was specified.'.format(authtoken)
self.logger.warning(msg)
raise TypeError(msg)
@ -340,7 +332,6 @@ class NotifyMatterMost(NotifyBase):
# all entries before it will be our path
tokens = NotifyMatterMost.split_path(results['fullpath'])
# Apply our settings now
results['authtoken'] = None if not tokens else tokens.pop()
# Store our path

View File

@ -35,11 +35,9 @@ import requests
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_APIKEY = re.compile(r'^[a-z0-9]{25}$', re.I)
# Some Phone Number Detection
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
@ -83,20 +81,21 @@ class NotifyMessageBird(NotifyBase):
'name': _('API Key'),
'type': 'string',
'required': True,
'regex': (r'[a-z0-9]{25}', 'i'),
'private': True,
'regex': (r'^[a-z0-9]{25}$', 'i'),
},
'source': {
'name': _('Source Phone No'),
'type': 'string',
'prefix': '+',
'required': True,
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
},
'target_phone': {
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -121,19 +120,12 @@ class NotifyMessageBird(NotifyBase):
"""
super(NotifyMessageBird, self).__init__(**kwargs)
try:
# The authentication key associated with the account
self.apikey = apikey.strip()
except AttributeError:
# Token was None
msg = 'No MessageBird authentication key was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_APIKEY.match(self.apikey):
msg = 'The MessageBird authentication key specified ({}) is ' \
'invalid.'.format(self.apikey)
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid MessageBird API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
@ -158,7 +150,14 @@ class NotifyMessageBird(NotifyBase):
# Parse our targets
self.targets = list()
for target in parse_list(targets):
targets = parse_list(targets)
if not targets:
# No sources specified, use our own phone no
self.targets.append(self.source)
return
# otherwise, store all of our target numbers
for target in targets:
# Validate targets and drop bad ones:
result = IS_PHONE_NO.match(target)
if result:
@ -180,6 +179,14 @@ class NotifyMessageBird(NotifyBase):
'({}) specified.'.format(target),
)
if not self.targets:
# We have a bot token and no target(s) to message
msg = 'No MessageBird targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform MessageBird Notification
@ -202,13 +209,10 @@ class NotifyMessageBird(NotifyBase):
'body': body,
}
# Create a copy of the targets list
targets = list(self.targets)
if len(targets) == 0:
# No sources specified, use our own phone no
targets.append(self.source)
while len(targets):
# Get our target to notify
target = targets.pop(0)

View File

@ -36,12 +36,9 @@ from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_APIKEY = re.compile(r'^[a-z0-9]{8}$', re.I)
VALIDATE_SECRET = re.compile(r'^[a-z0-9]{16}$', re.I)
# Some Phone Number Detection
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
@ -94,27 +91,28 @@ class NotifyNexmo(NotifyBase):
'name': _('API Key'),
'type': 'string',
'required': True,
'regex': (r'AC[a-z0-9]{8}', 'i'),
'regex': (r'^AC[a-z0-9]{8}$', 'i'),
'private': True,
},
'secret': {
'name': _('API Secret'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-z0-9]{16}', 'i'),
'regex': (r'^[a-z0-9]{16}$', 'i'),
},
'from_phone': {
'name': _('From Phone No'),
'type': 'string',
'required': True,
'regex': (r'\+?[0-9\s)(+-]+', 'i'),
'regex': (r'^\+?[0-9\s)(+-]+$', 'i'),
'map_to': 'source',
},
'target_phone': {
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -153,35 +151,21 @@ class NotifyNexmo(NotifyBase):
"""
super(NotifyNexmo, self).__init__(**kwargs)
try:
# The Account SID associated with the account
self.apikey = apikey.strip()
except AttributeError:
# Token was None
msg = 'No Nexmo APIKey was specified.'
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid Nexmo API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_APIKEY.match(self.apikey):
msg = 'The Nexmo API Key specified ({}) is invalid.'\
.format(self.apikey)
self.logger.warning(msg)
raise TypeError(msg)
try:
# The Account SID associated with the account
self.secret = secret.strip()
except AttributeError:
# Token was None
msg = 'No Nexmo API Secret was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_SECRET.match(self.secret):
msg = 'The Nexmo API Secret specified ({}) is invalid.'\
.format(self.secret)
# API Secret (associated with project)
self.secret = validate_regex(
secret, *self.template_tokens['secret']['regex'])
if not self.secret:
msg = 'An invalid Nexmo API Secret ' \
'({}) was specified.'.format(secret)
self.logger.warning(msg)
raise TypeError(msg)
@ -242,6 +226,8 @@ class NotifyNexmo(NotifyBase):
'({}) specified.'.format(target),
)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Nexmo Notification

View File

@ -23,19 +23,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import re
import requests
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Used to validate API Key
VALIDATE_APIKEY = re.compile(r'[A-Za-z0-9]{40}')
# Used to validate Provider Key
VALIDATE_PROVIDERKEY = re.compile(r'[A-Za-z0-9]{40}')
# Priorities
class ProwlPriority(object):
@ -104,11 +98,13 @@ class NotifyProwl(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'^[A-Za-z0-9]{40}$', 'i'),
},
'providerkey': {
'name': _('Provider Key'),
'type': 'string',
'private': True,
'regex': (r'^[A-Za-z0-9]{40}$', 'i'),
},
})
@ -129,31 +125,35 @@ class NotifyProwl(NotifyBase):
super(NotifyProwl, self).__init__(**kwargs)
if priority not in PROWL_PRIORITIES:
self.priority = ProwlPriority.NORMAL
self.priority = self.template_args['priority']['default']
else:
self.priority = priority
if not VALIDATE_APIKEY.match(apikey):
msg = 'The API key specified ({}) is invalid.'.format(apikey)
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid Prowl API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# Store the API key
self.apikey = apikey
# Store the provider key (if specified)
if providerkey:
if not VALIDATE_PROVIDERKEY.match(providerkey):
msg = \
'The Provider key specified ({}) is invalid.' \
.format(providerkey)
self.providerkey = validate_regex(
providerkey, *self.template_tokens['providerkey']['regex'])
if not self.providerkey:
msg = 'An invalid Prowl Provider Key ' \
'({}) was specified.'.format(providerkey)
self.logger.warning(msg)
raise TypeError(msg)
# Store the Provider Key
self.providerkey = providerkey
else:
# No provider key was set
self.providerkey = None
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""

View File

@ -30,6 +30,7 @@ from .NotifyBase import NotifyBase
from ..utils import GET_EMAIL_RE
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Flag used as a placeholder to sending to all devices
@ -110,12 +111,20 @@ class NotifyPushBullet(NotifyBase):
"""
super(NotifyPushBullet, self).__init__(**kwargs)
self.accesstoken = accesstoken
# Access Token (associated with project)
self.accesstoken = validate_regex(accesstoken)
if not self.accesstoken:
msg = 'An invalid PushBullet Access Token ' \
'({}) was specified.'.format(accesstoken)
self.logger.warning(msg)
raise TypeError(msg)
self.targets = parse_list(targets)
if len(self.targets) == 0:
self.targets = (PUSHBULLET_SEND_TO_ALL, )
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform PushBullet Notification

View File

@ -32,10 +32,11 @@ from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Used to detect and parse channels
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
IS_CHANNEL = re.compile(r'^#?(?P<name>[A-Za-z0-9]+)$')
# Used to detect and parse a users push id
IS_USER_PUSHED_ID = re.compile(r'^@(?P<name>[A-Za-z0-9]+)$')
@ -121,13 +122,19 @@ class NotifyPushed(NotifyBase):
"""
super(NotifyPushed, self).__init__(**kwargs)
if not app_key:
msg = 'An invalid Application Key was specified.'
# Application Key (associated with project)
self.app_key = validate_regex(app_key)
if not self.app_key:
msg = 'An invalid Pushed Application Key ' \
'({}) was specified.'.format(app_key)
self.logger.warning(msg)
raise TypeError(msg)
if not app_secret:
msg = 'An invalid Application Secret was specified.'
# Access Secret (associated with project)
self.app_secret = validate_regex(app_secret)
if not self.app_secret:
msg = 'An invalid Pushed Application Secret ' \
'({}) was specified.'.format(app_secret)
self.logger.warning(msg)
raise TypeError(msg)
@ -137,28 +144,34 @@ class NotifyPushed(NotifyBase):
# Initialize user list
self.users = list()
# Validate recipients and drop bad ones:
for target in parse_list(targets):
result = IS_CHANNEL.match(target)
if result:
# store valid device
self.channels.append(result.group('name'))
continue
# Get our targets
targets = parse_list(targets)
if targets:
# Validate recipients and drop bad ones:
for target in targets:
result = IS_CHANNEL.match(target)
if result:
# store valid device
self.channels.append(result.group('name'))
continue
result = IS_USER_PUSHED_ID.match(target)
if result:
# store valid room
self.users.append(result.group('name'))
continue
result = IS_USER_PUSHED_ID.match(target)
if result:
# store valid room
self.users.append(result.group('name'))
continue
self.logger.warning(
'Dropped invalid channel/userid '
'(%s) specified.' % target,
)
self.logger.warning(
'Dropped invalid channel/userid '
'(%s) specified.' % target,
)
# Store our data
self.app_key = app_key
self.app_secret = app_secret
if len(self.channels) + len(self.users) == 0:
# We have no valid channels or users to notify after
# explicitly identifying at least one.
msg = 'No Pushed targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
@ -325,8 +338,6 @@ class NotifyPushed(NotifyBase):
# We're done early as we couldn't load the results
return results
# Apply our settings now
# The first token is stored in the hostname
app_key = NotifyPushed.unquote(results['host'])

View File

@ -29,6 +29,7 @@ from json import dumps
from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
@ -107,14 +108,15 @@ class NotifyPushjet(NotifyBase):
"""
super(NotifyPushjet, self).__init__(**kwargs)
if not secret_key:
# You must provide a Pushjet key to work with
msg = 'You must specify a Pushjet Secret Key.'
# Secret Key (associated with project)
self.secret_key = validate_regex(secret_key)
if not self.secret_key:
msg = 'An invalid Pushjet Secret Key ' \
'({}) was specified.'.format(secret_key)
self.logger.warning(msg)
raise TypeError(msg)
# store our key
self.secret_key = secret_key
return
def url(self, privacy=False, *args, **kwargs):
"""
@ -125,9 +127,6 @@ class NotifyPushjet(NotifyBase):
args = {
'format': self.notify_format,
'overflow': self.overflow_mode,
'secret': self.pprint(
self.secret_key, privacy,
mode=PrivacyMode.Secret, quote=False),
'verify': 'yes' if self.verify_certificate else 'no',
}
@ -142,12 +141,14 @@ class NotifyPushjet(NotifyBase):
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
)
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
return '{schema}://{auth}{hostname}{port}/{secret}/?{args}'.format(
schema=self.secure_protocol if self.secure else self.protocol,
auth=auth,
hostname=NotifyPushjet.quote(self.host, safe=''),
port='' if self.port is None or self.port == default_port
else ':{}'.format(self.port),
secret=self.pprint(
self.secret_key, privacy, mode=PrivacyMode.Secret, safe=''),
args=NotifyPushjet.urlencode(args),
)
@ -273,7 +274,7 @@ class NotifyPushjet(NotifyBase):
# through it in addition to supporting the secret key
if 'secret' in results['qsd'] and len(results['qsd']['secret']):
results['secret_key'] = \
NotifyPushjet.parse_list(results['qsd']['secret'])
NotifyPushjet.unquote(results['qsd']['secret'])
if results.get('secret_key') is None:
# Deprication Notice issued for v0.7.9

View File

@ -30,18 +30,13 @@ import requests
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Flag used as a placeholder to sending to all devices
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
# Used to validate API Key
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{30}$', re.I)
# Used to detect a User and/or Group
VALIDATE_USER_KEY = re.compile(r'^[a-z0-9]{30}$', re.I)
# Used to detect a User and/or Group
# Used to detect a Device
VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I)
@ -158,20 +153,19 @@ class NotifyPushover(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-z0-9]{30}', 'i'),
'map_to': 'user',
'regex': (r'^[a-z0-9]{30}$', 'i'),
},
'token': {
'name': _('Access Token'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-z0-9]{30}', 'i'),
'regex': (r'^[a-z0-9]{30}$', 'i'),
},
'target_device': {
'name': _('Target Device'),
'type': 'string',
'regex': (r'[a-z0-9_]{1,25}', 'i'),
'regex': (r'^[a-z0-9_]{1,25}$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -191,7 +185,7 @@ class NotifyPushover(NotifyBase):
'sound': {
'name': _('Sound'),
'type': 'string',
'regex': (r'[a-z]{1,12}', 'i'),
'regex': (r'^[a-z]{1,12}$', 'i'),
'default': PushoverSound.PUSHOVER,
},
'retry': {
@ -212,26 +206,28 @@ class NotifyPushover(NotifyBase):
},
})
def __init__(self, token, targets=None, priority=None, sound=None,
retry=None, expire=None,
**kwargs):
def __init__(self, user_key, token, targets=None, priority=None,
sound=None, retry=None, expire=None, **kwargs):
"""
Initialize Pushover Object
"""
super(NotifyPushover, self).__init__(**kwargs)
try:
# The token associated with the account
self.token = token.strip()
except AttributeError:
# Token was None
msg = 'No API Token was specified.'
# Access Token (associated with project)
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'An invalid Pushover Access Token ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN.match(self.token):
msg = 'The API Token specified (%s) is invalid.'.format(token)
# User Key (associated with project)
self.user_key = validate_regex(
user_key, *self.template_tokens['user_key']['regex'])
if not self.user_key:
msg = 'An invalid Pushover User Key ' \
'({}) was specified.'.format(user_key)
self.logger.warning(msg)
raise TypeError(msg)
@ -249,7 +245,7 @@ class NotifyPushover(NotifyBase):
# The Priority of the message
if priority not in PUSHOVER_PRIORITIES:
self.priority = PushoverPriority.NORMAL
self.priority = self.template_args['priority']['default']
else:
self.priority = priority
@ -258,7 +254,7 @@ class NotifyPushover(NotifyBase):
if self.priority == PushoverPriority.EMERGENCY:
# How often to resend notification, in seconds
self.retry = NotifyPushover.template_args['retry']['default']
self.retry = self.template_args['retry']['default']
try:
self.retry = int(retry)
except (ValueError, TypeError):
@ -266,7 +262,7 @@ class NotifyPushover(NotifyBase):
pass
# How often to resend notification, in seconds
self.expire = NotifyPushover.template_args['expire']['default']
self.expire = self.template_args['expire']['default']
try:
self.expire = int(expire)
except (ValueError, TypeError):
@ -274,23 +270,16 @@ class NotifyPushover(NotifyBase):
pass
if self.retry < 30:
msg = 'Retry must be at least 30.'
msg = 'Pushover retry must be at least 30 seconds.'
self.logger.warning(msg)
raise TypeError(msg)
if self.expire < 0 or self.expire > 10800:
msg = 'Expire has a max value of at most 10800 seconds.'
msg = 'Pushover expire must reside in the range of ' \
'0 to 10800 seconds.'
self.logger.warning(msg)
raise TypeError(msg)
if not self.user:
msg = 'No user key was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_USER_KEY.match(self.user):
msg = 'The user key specified (%s) is invalid.' % self.user
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
@ -323,7 +312,7 @@ class NotifyPushover(NotifyBase):
# prepare JSON Object
payload = {
'token': self.token,
'user': self.user,
'user': self.user_key,
'priority': str(self.priority),
'title': title,
'message': body,
@ -406,8 +395,8 @@ class NotifyPushover(NotifyBase):
'format': self.notify_format,
'overflow': self.overflow_mode,
'priority':
_map[PushoverPriority.NORMAL] if self.priority not in _map
else _map[self.priority],
_map[self.template_args['priority']['default']]
if self.priority not in _map else _map[self.priority],
'verify': 'yes' if self.verify_certificate else 'no',
}
# Only add expire and retry for emergency messages,
@ -426,7 +415,7 @@ class NotifyPushover(NotifyBase):
return '{schema}://{user_key}@{token}/{devices}/?{args}'.format(
schema=self.secure_protocol,
user_key=self.pprint(self.user, privacy, safe=''),
user_key=self.pprint(self.user_key, privacy, safe=''),
token=self.pprint(self.token, privacy, safe=''),
devices=devices,
args=NotifyPushover.urlencode(args))
@ -464,6 +453,9 @@ class NotifyPushover(NotifyBase):
# Retrieve all of our targets
results['targets'] = NotifyPushover.split_path(results['fullpath'])
# User Key is retrieved from the user
results['user_key'] = NotifyPushover.unquote(results['user'])
# Get the sound
if 'sound' in results['qsd'] and len(results['qsd']['sound']):
results['sound'] = \

View File

@ -40,14 +40,9 @@ from .NotifyBase import NotifyBase
from ..common import NotifyImageSize
from ..common import NotifyType
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_TOKEN = re.compile(r'[A-Z0-9]{15}', re.I)
# Organization required as part of the API request
VALIDATE_ORG = re.compile(r'[A-Z0-9_-]{3,32}', re.I)
class RyverWebhookMode(object):
"""
@ -99,12 +94,14 @@ class NotifyRyver(NotifyBase):
'name': _('Organization'),
'type': 'string',
'required': True,
'regex': (r'^[A-Z0-9_-]{3,32}$', 'i'),
},
'token': {
'name': _('Token'),
'type': 'string',
'required': True,
'private': True,
'regex': (r'^[A-Z0-9]{15}$', 'i'),
},
'user': {
'name': _('Bot Name'),
@ -135,25 +132,21 @@ class NotifyRyver(NotifyBase):
"""
super(NotifyRyver, self).__init__(**kwargs)
if not token:
msg = 'No Ryver token was specified.'
# API Token (associated with project)
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'An invalid Ryver API Token ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
if not organization:
msg = 'No Ryver organization was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN.match(token.strip()):
msg = 'The Ryver token specified ({}) is invalid.'\
.format(token)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_ORG.match(organization.strip()):
msg = 'The Ryver organization specified ({}) is invalid.'\
.format(organization)
# Organization (associated with project)
self.organization = validate_regex(
organization, *self.template_tokens['organization']['regex'])
if not self.organization:
msg = 'An invalid Ryver Organization ' \
'({}) was specified.'.format(organization)
self.logger.warning(msg)
raise TypeError(msg)
@ -167,12 +160,6 @@ class NotifyRyver(NotifyBase):
self.logger.warning(msg)
raise TypeError(msg)
# The organization associated with the account
self.organization = organization.strip()
# The token associated with the account
self.token = token.strip()
# Place an image inline with the message body
self.include_image = include_image
@ -193,6 +180,8 @@ class NotifyRyver(NotifyBase):
re.IGNORECASE,
)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Ryver Notification

View File

@ -36,6 +36,7 @@ from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Some Phone Number Detection
@ -117,21 +118,21 @@ class NotifySNS(NotifyBase):
'name': _('Region'),
'type': 'string',
'required': True,
'regex': (r'[a-z]{2}-[a-z]+-[0-9]+', 'i'),
'regex': (r'^[a-z]{2}-[a-z]+-[0-9]+$', 'i'),
'map_to': 'region_name',
},
'target_phone_no': {
'name': _('Target Phone No'),
'type': 'string',
'map_to': 'targets',
'regex': (r'[0-9\s)(+-]+', 'i')
'regex': (r'^[0-9\s)(+-]+$', 'i')
},
'target_topic': {
'name': _('Target Topic'),
'type': 'string',
'map_to': 'targets',
'prefix': '#',
'regex': (r'[A-Za-z0-9_-]+', 'i'),
'regex': (r'^[A-Za-z0-9_-]+$', 'i'),
},
'targets': {
'name': _('Targets'),
@ -153,18 +154,28 @@ class NotifySNS(NotifyBase):
"""
super(NotifySNS, self).__init__(**kwargs)
if not access_key_id:
# Store our AWS API Access Key
self.aws_access_key_id = validate_regex(access_key_id)
if not self.aws_access_key_id:
msg = 'An invalid AWS Access Key ID was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not secret_access_key:
msg = 'An invalid AWS Secret Access Key was specified.'
# Store our AWS API Secret Access key
self.aws_secret_access_key = validate_regex(secret_access_key)
if not self.aws_secret_access_key:
msg = 'An invalid AWS Secret Access Key ' \
'({}) was specified.'.format(secret_access_key)
self.logger.warning(msg)
raise TypeError(msg)
if not (region_name and IS_REGION.match(region_name)):
msg = 'An invalid AWS Region was specified.'
# Acquire our AWS Region Name:
# eg. us-east-1, cn-north-1, us-west-2, ...
self.aws_region_name = validate_regex(
region_name, *self.template_tokens['region']['regex'])
if not self.aws_region_name:
msg = 'An invalid AWS Region ({}) was specified.'.format(
region_name)
self.logger.warning(msg)
raise TypeError(msg)
@ -174,16 +185,6 @@ class NotifySNS(NotifyBase):
# Initialize numbers list
self.phone = list()
# Store our AWS API Key
self.aws_access_key_id = access_key_id
# Store our AWS API Secret Access key
self.aws_secret_access_key = secret_access_key
# Acquire our AWS Region Name:
# eg. us-east-1, cn-north-1, us-west-2, ...
self.aws_region_name = region_name
# Set our notify_url based on our region
self.notify_url = 'https://sns.{}.amazonaws.com/'\
.format(self.aws_region_name)
@ -231,8 +232,12 @@ class NotifySNS(NotifyBase):
)
if len(self.phone) == 0 and len(self.topics) == 0:
self.logger.warning(
'There are no valid target(s) identified to notify.')
# We have a bot token and no target(s) to message
msg = 'No AWS targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""

View File

@ -43,7 +43,6 @@
# - https://sendgrid.com/docs/ui/sending-email/\
# how-to-send-an-email-with-dynamic-transactional-templates/
import re
import requests
from json import dumps
@ -52,10 +51,9 @@ from ..common import NotifyFormat
from ..common import NotifyType
from ..utils import parse_list
from ..utils import GET_EMAIL_RE
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
IS_APIKEY_RE = re.compile(r'^([A-Z0-9._-]+)$', re.I)
# Extend HTTP Error Messages
SENDGRID_HTTP_ERROR_MAP = {
401: 'Unauthorized - You do not have authorization to make the request.',
@ -109,6 +107,7 @@ class NotifySendGrid(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'^[A-Z0-9._-]+$', 'i'),
},
'from_email': {
'name': _('Source Email'),
@ -162,16 +161,12 @@ class NotifySendGrid(NotifyBase):
"""
super(NotifySendGrid, self).__init__(**kwargs)
# The API Key needed to perform all SendMail API i/o
self.apikey = apikey
try:
result = IS_APIKEY_RE.match(self.apikey)
if not result:
# let outer exception handle this
raise TypeError
except (TypeError, AttributeError):
msg = 'Invalid API Key specified: {}'.format(self.apikey)
# API Key (associated with project)
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid SendGrid API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)

View File

@ -29,6 +29,7 @@ import requests
from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Default our global support flag
@ -120,11 +121,26 @@ class NotifySimplePush(NotifyBase):
"""
super(NotifySimplePush, self).__init__(**kwargs)
# Store the API key
self.apikey = apikey
# API Key (associated with project)
self.apikey = validate_regex(apikey)
if not self.apikey:
msg = 'An invalid SimplePush API Key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# Event Name
self.event = event
if event:
# Event Name (associated with project)
self.event = validate_regex(event)
if not self.event:
msg = 'An invalid SimplePush Event Name ' \
'({}) was specified.'.format(event)
self.logger.warning(msg)
raise TypeError(msg)
else:
# Default Event Name
self.event = None
# Encrypt Message (providing support is available)
if self.password and self.user and not CRYPTOGRAPHY_AVAILABLE:
@ -182,7 +198,6 @@ class NotifySimplePush(NotifyBase):
payload = {
'key': self.apikey,
}
event = self.event
if self.password and self.user and CRYPTOGRAPHY_AVAILABLE:
body = self._encrypt(body)
@ -198,8 +213,9 @@ class NotifySimplePush(NotifyBase):
'title': title,
})
if event:
payload['event'] = event
if self.event:
# Store Event
payload['event'] = self.event
self.logger.debug('SimplePush POST URL: %s (cert_verify=%r)' % (
self.notify_url, self.verify_certificate,

View File

@ -46,20 +46,9 @@ from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import parse_bool
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
# /AAAAAAAAA/........./........................
VALIDATE_TOKEN_A = re.compile(r'[A-Z0-9]{9}')
# Token required as part of the API request
# /........./BBBBBBBBB/........................
VALIDATE_TOKEN_B = re.compile(r'[A-Z0-9]{9}')
# Token required as part of the API request
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
VALIDATE_TOKEN_C = re.compile(r'[A-Za-z0-9]{24}')
# Extend HTTP Error Messages
SLACK_HTTP_ERROR_MAP = {
401: 'Unauthorized - Invalid Token.',
@ -68,9 +57,6 @@ SLACK_HTTP_ERROR_MAP = {
# Used to break path apart into list of channels
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
# Used to detect a channel
IS_VALID_TARGET_RE = re.compile(r'[+#@]?([A-Z0-9_]{1,32})', re.I)
class NotifySlack(NotifyBase):
"""
@ -116,26 +102,32 @@ class NotifySlack(NotifyBase):
'type': 'string',
'map_to': 'user',
},
# Token required as part of the API request
# /AAAAAAAAA/........./........................
'token_a': {
'name': _('Token A'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[A-Z0-9]{9}', 'i'),
'regex': (r'^[A-Z0-9]{9}$', 'i'),
},
# Token required as part of the API request
# /........./BBBBBBBBB/........................
'token_b': {
'name': _('Token B'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[A-Z0-9]{9}', 'i'),
'regex': (r'^[A-Z0-9]{9}$', 'i'),
},
# Token required as part of the API request
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
'token_c': {
'name': _('Token C'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[A-Za-z0-9]{24}', 'i'),
'regex': (r'^[A-Za-z0-9]{24}$', 'i'),
},
'target_encoded_id': {
'name': _('Target Encoded ID'),
@ -181,48 +173,30 @@ class NotifySlack(NotifyBase):
"""
super(NotifySlack, self).__init__(**kwargs)
if not token_a:
msg = 'The first API token is not specified.'
self.token_a = validate_regex(
token_a, *self.template_tokens['token_a']['regex'])
if not self.token_a:
msg = 'An invalid Slack (first) Token ' \
'({}) was specified.'.format(token_a)
self.logger.warning(msg)
raise TypeError(msg)
if not token_b:
msg = 'The second API token is not specified.'
self.token_b = validate_regex(
token_b, *self.template_tokens['token_b']['regex'])
if not self.token_b:
msg = 'An invalid Slack (second) Token ' \
'({}) was specified.'.format(token_b)
self.logger.warning(msg)
raise TypeError(msg)
if not token_c:
msg = 'The third API token is not specified.'
self.token_c = validate_regex(
token_c, *self.template_tokens['token_c']['regex'])
if not self.token_c:
msg = 'An invalid Slack (third) Token ' \
'({}) was specified.'.format(token_c)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN_A.match(token_a.strip()):
msg = 'The first API token specified ({}) is invalid.'\
.format(token_a)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_a = token_a.strip()
if not VALIDATE_TOKEN_B.match(token_b.strip()):
msg = 'The second API token specified ({}) is invalid.'\
.format(token_b)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_b = token_b.strip()
if not VALIDATE_TOKEN_C.match(token_c.strip()):
msg = 'The third API token specified ({}) is invalid.'\
.format(token_c)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token_c = token_c.strip()
if not self.user:
self.logger.warning(
'No user was specified; using "%s".' % self.app_id)
@ -255,6 +229,8 @@ class NotifySlack(NotifyBase):
# Place a thumbnail image inline with the message body
self.include_image = include_image
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Slack Notification
@ -303,27 +279,30 @@ class NotifySlack(NotifyBase):
channel = channels.pop(0)
if channel is not None:
# Channel over-ride was specified
if not IS_VALID_TARGET_RE.match(channel):
_channel = validate_regex(
channel, r'[+#@]?([A-Z0-9_]{1,32})')
if not _channel:
# Channel over-ride was specified
self.logger.warning(
"The specified target {} is invalid;"
"skipping.".format(channel))
"skipping.".format(_channel))
# Mark our failure
has_error = True
continue
if len(channel) > 1 and channel[0] == '+':
if len(_channel) > 1 and _channel[0] == '+':
# Treat as encoded id if prefixed with a +
payload['channel'] = channel[1:]
payload['channel'] = _channel[1:]
elif len(channel) > 1 and channel[0] == '@':
elif len(_channel) > 1 and _channel[0] == '@':
# Treat @ value 'as is'
payload['channel'] = channel
payload['channel'] = _channel
else:
# Prefix with channel hash tag
payload['channel'] = '#%s' % channel
payload['channel'] = '#{}'.format(_channel)
# Acquire our to-be footer icon if configured to do so
image_url = None if not self.include_image \
@ -478,9 +457,9 @@ class NotifySlack(NotifyBase):
result = re.match(
r'^https?://hooks\.slack\.com/services/'
r'(?P<token_a>[A-Z0-9]{9})/'
r'(?P<token_b>[A-Z0-9]{9})/'
r'(?P<token_c>[A-Z0-9]{24})/?'
r'(?P<token_a>[A-Z0-9]+)/'
r'(?P<token_b>[A-Z0-9]+)/'
r'(?P<token_c>[A-Z0-9]+)/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:

View File

@ -47,12 +47,12 @@
# - https://push.techulus.com/ - Main Website
# - https://pushtechulus.docs.apiary.io - API Documentation
import re
import requests
from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
@ -60,9 +60,6 @@ from ..AppriseLocale import gettext_lazy as _
UUID4_RE = \
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
# API Key
VALIDATE_APIKEY = re.compile(UUID4_RE, re.I)
class NotifyTechulusPush(NotifyBase):
"""
@ -99,7 +96,7 @@ class NotifyTechulusPush(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (UUID4_RE, 'i'),
'regex': (r'^{}$'.format(UUID4_RE), 'i'),
},
})
@ -109,19 +106,14 @@ class NotifyTechulusPush(NotifyBase):
"""
super(NotifyTechulusPush, self).__init__(**kwargs)
if not apikey:
msg = 'The Techulus Push apikey is not specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_APIKEY.match(apikey.strip()):
msg = 'The Techulus Push apikey specified ({}) is invalid.'\
.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
# The apikey associated with the account
self.apikey = apikey.strip()
self.apikey = validate_regex(
apikey, *self.template_tokens['apikey']['regex'])
if not self.apikey:
msg = 'An invalid Techulus Push API key ' \
'({}) was specified.'.format(apikey)
self.logger.warning(msg)
raise TypeError(msg)
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""

View File

@ -61,17 +61,11 @@ from ..common import NotifyImageSize
from ..common import NotifyFormat
from ..utils import parse_bool
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
TELEGRAM_IMAGE_XY = NotifyImageSize.XY_256
# Token required as part of the API request
# allow the word 'bot' infront
VALIDATE_BOT_TOKEN = re.compile(
r'^(bot)?(?P<key>[0-9]+:[a-z0-9_-]+)/*$',
re.IGNORECASE,
)
# Chat ID is required
# If the Chat ID is positive, then it's addressed to a single person
# If the Chat ID is negative, then it's targeting a group
@ -119,14 +113,16 @@ class NotifyTelegram(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'(bot)?[0-9]+:[a-z0-9_-]+', 'i'),
# Token required as part of the API request, allow the word 'bot'
# infront of it
'regex': (r'^(bot)?(?P<key>[0-9]+:[a-z0-9_-]+)$', 'i'),
},
'target_user': {
'name': _('Target Chat ID'),
'type': 'string',
'map_to': 'targets',
'map_to': 'targets',
'regex': (r'((-?[0-9]{1,32})|([a-z_-][a-z0-9_-]+))', 'i'),
'regex': (r'^((-?[0-9]{1,32})|([a-z_-][a-z0-9_-]+))$', 'i'),
},
'targets': {
'name': _('Targets'),
@ -160,24 +156,15 @@ class NotifyTelegram(NotifyBase):
"""
super(NotifyTelegram, self).__init__(**kwargs)
try:
self.bot_token = bot_token.strip()
except AttributeError:
# Token was None
err = 'No Bot Token was specified.'
self.bot_token = validate_regex(
bot_token, *self.template_tokens['bot_token']['regex'],
fmt='{key}')
if not self.bot_token:
err = 'The Telegram Bot Token specified ({}) is invalid.'.format(
bot_token)
self.logger.warning(err)
raise TypeError(err)
result = VALIDATE_BOT_TOKEN.match(self.bot_token)
if not result:
err = 'The Bot Token specified (%s) is invalid.' % bot_token
self.logger.warning(err)
raise TypeError(err)
# Store our Bot Token
self.bot_token = result.group('key')
# Parse our list
self.targets = parse_list(targets)

View File

@ -48,13 +48,10 @@ from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Used to validate your personal access apikey
VALIDATE_AUTH_TOKEN = re.compile(r'^[a-f0-9]{32}$', re.I)
VALIDATE_ACCOUNT_SID = re.compile(r'^AC[a-f0-9]{32}$', re.I)
# Some Phone Number Detection
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
@ -108,33 +105,33 @@ class NotifyTwilio(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'AC[a-f0-9]{32}', 'i'),
'regex': (r'^AC[a-f0-9]+$', 'i'),
},
'auth_token': {
'name': _('Auth Token'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-f0-9]{32}', 'i'),
'regex': (r'^[a-f0-9]+$', 'i'),
},
'from_phone': {
'name': _('From Phone No'),
'type': 'string',
'required': True,
'regex': (r'\+?[0-9\s)(+-]+', 'i'),
'regex': (r'^\+?[0-9\s)(+-]+$', 'i'),
'map_to': 'source',
},
'target_phone': {
'name': _('Target Phone No'),
'type': 'string',
'prefix': '+',
'regex': (r'[0-9\s)(+-]+', 'i'),
'regex': (r'^[0-9\s)(+-]+$', 'i'),
'map_to': 'targets',
},
'short_code': {
'name': _('Target Short Code'),
'type': 'string',
'regex': (r'[0-9]{5,6}', 'i'),
'regex': (r'^[0-9]{5,6}$', 'i'),
'map_to': 'targets',
},
'targets': {
@ -166,35 +163,21 @@ class NotifyTwilio(NotifyBase):
"""
super(NotifyTwilio, self).__init__(**kwargs)
try:
# The Account SID associated with the account
self.account_sid = account_sid.strip()
except AttributeError:
# Token was None
msg = 'No Account SID was specified.'
# The Account SID associated with the account
self.account_sid = validate_regex(
account_sid, *self.template_tokens['account_sid']['regex'])
if not self.account_sid:
msg = 'An invalid Twilio Account SID ' \
'({}) was specified.'.format(account_sid)
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_ACCOUNT_SID.match(self.account_sid):
msg = 'The Account SID specified ({}) is invalid.' \
.format(account_sid)
self.logger.warning(msg)
raise TypeError(msg)
try:
# The authentication token associated with the account
self.auth_token = auth_token.strip()
except AttributeError:
# Token was None
msg = 'No Auth Token was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_AUTH_TOKEN.match(self.auth_token):
msg = 'The Auth Token specified ({}) is invalid.' \
.format(auth_token)
# The Authentication Token associated with the account
self.auth_token = validate_regex(
auth_token, *self.template_tokens['auth_token']['regex'])
if not self.auth_token:
msg = 'An invalid Twilio Authentication Token ' \
'({}) was specified.'.format(auth_token)
self.logger.warning(msg)
raise TypeError(msg)
@ -254,14 +237,16 @@ class NotifyTwilio(NotifyBase):
'({}) specified.'.format(target),
)
if len(self.targets) == 0:
msg = 'There are no valid targets identified to notify.'
if not self.targets:
if len(self.source) in (5, 6):
# raise a warning since we're a short-code. We need
# a number to message
msg = 'There are no valid Twilio targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Twilio Notification

View File

@ -37,6 +37,7 @@ from ..URLBase import PrivacyMode
from ..common import NotifyType
from ..utils import parse_list
from ..utils import parse_bool
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
IS_USER = re.compile(r'^\s*@?(?P<user>[A-Z0-9_]+)$', re.I)
@ -186,23 +187,27 @@ class NotifyTwitter(NotifyBase):
"""
super(NotifyTwitter, self).__init__(**kwargs)
if not ckey:
msg = 'An invalid Consumer API Key was specified.'
self.ckey = validate_regex(ckey)
if not self.ckey:
msg = 'An invalid Twitter Consumer Key was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not csecret:
msg = 'An invalid Consumer Secret API Key was specified.'
self.csecret = validate_regex(csecret)
if not self.csecret:
msg = 'An invalid Twitter Consumer Secret was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not akey:
msg = 'An invalid Access Token API Key was specified.'
self.akey = validate_regex(akey)
if not self.akey:
msg = 'An invalid Twitter Access Key was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not asecret:
msg = 'An invalid Access Token Secret API Key was specified.'
self.asecret = validate_regex(asecret)
if not self.asecret:
msg = 'An invalid Access Secret was specified.'
self.logger.warning(msg)
raise TypeError(msg)
@ -219,6 +224,9 @@ class NotifyTwitter(NotifyBase):
self.logger.warning(msg)
raise TypeError(msg)
# Track any errors
has_error = False
# Identify our targets
self.targets = []
for target in parse_list(targets):
@ -227,15 +235,19 @@ class NotifyTwitter(NotifyBase):
self.targets.append(match.group('user'))
continue
has_error = True
self.logger.warning(
'Dropped invalid user ({}) specified.'.format(target),
)
# Store our data
self.ckey = ckey
self.csecret = csecret
self.akey = akey
self.asecret = asecret
if has_error and not self.targets:
# We have specified that we want to notify one or more individual
# and we failed to load any of them. Since it's also valid to
# notify no one at all (which means we notify ourselves), it's
# important we don't switch from the users original intentions
msg = 'No Twitter targets to notify.'
self.logger.warning(msg)
raise TypeError(msg)
return
@ -297,7 +309,7 @@ class NotifyTwitter(NotifyBase):
}
}
# Lookup our users
# Lookup our users (otherwise we look up ourselves)
targets = self._whoami(lazy=self.cache) if not len(self.targets) \
else self._user_lookup(self.targets, lazy=self.cache)

View File

@ -63,11 +63,9 @@ from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_TOKEN = re.compile(r'[a-z0-9]{80}', re.I)
# Extend HTTP Error Messages
# Based on: https://developer.webex.com/docs/api/basics/rate-limiting
WEBEX_HTTP_ERROR_MAP = {
@ -119,7 +117,7 @@ class NotifyWebexTeams(NotifyBase):
'type': 'string',
'private': True,
'required': True,
'regex': (r'[a-z0-9]{80}', 'i'),
'regex': (r'^[a-z0-9]{80}$', 'i'),
},
})
@ -129,20 +127,15 @@ class NotifyWebexTeams(NotifyBase):
"""
super(NotifyWebexTeams, self).__init__(**kwargs)
if not token:
msg = 'The Webex Teams token is not specified.'
self.logger.warning(msg)
raise TypeError(msg)
if not VALIDATE_TOKEN.match(token.strip()):
# The token associated with the account
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'The Webex Teams token specified ({}) is invalid.'\
.format(token)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token = token.strip()
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Webex Teams Notification

View File

@ -157,7 +157,7 @@ class NotifyXMPP(NotifyBase):
'name': _('XEP'),
'type': 'list:string',
'prefix': 'xep-',
'regex': (r'[1-9][0-9]{0,3}', 'i'),
'regex': (r'^[1-9][0-9]{0,3}$', 'i'),
},
'jid': {
'name': _('Source JID'),

View File

@ -61,15 +61,13 @@ import requests
from .NotifyBase import NotifyBase
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
from ..utils import GET_EMAIL_RE
from ..AppriseLocale import gettext_lazy as _
# A Valid Bot Name
VALIDATE_BOTNAME = re.compile(r'(?P<name>[A-Z0-9_]{1,32})(-bot)?', re.I)
# A Valid Bot Token is 32 characters of alpha/numeric
VALIDATE_TOKEN = re.compile(r'[A-Z0-9]{32}', re.I)
# Organization required as part of the API request
VALIDATE_ORG = re.compile(
r'(?P<org>[A-Z0-9_-]{1,32})(\.(?P<hostname>[^\s]+))?', re.I)
@ -124,18 +122,20 @@ class NotifyZulip(NotifyBase):
'botname': {
'name': _('Bot Name'),
'type': 'string',
'regex': (r'^[A-Z0-9_]{1,32}(-bot)?$', 'i'),
},
'organization': {
'name': _('Organization'),
'type': 'string',
'required': True,
'regex': (r'^[A-Z0-9_-]{1,32})$', 'i')
},
'token': {
'name': _('Token'),
'type': 'string',
'required': True,
'private': True,
'regex': (r'[A-Z0-9]{32}', 'i'),
'regex': (r'^[A-Z0-9]{32}$', 'i'),
},
'target_user': {
'name': _('Target User'),
@ -208,20 +208,14 @@ class NotifyZulip(NotifyBase):
self.logger.warning(msg)
raise TypeError(msg)
try:
if not VALIDATE_TOKEN.match(token.strip()):
# let outer exception handle this
raise TypeError
except (TypeError, AttributeError):
self.token = validate_regex(
token, *self.template_tokens['token']['regex'])
if not self.token:
msg = 'The Zulip token specified ({}) is invalid.'\
.format(token)
self.logger.warning(msg)
raise TypeError(msg)
# The token associated with the account
self.token = token.strip()
self.targets = parse_list(targets)
if len(self.targets) == 0:
# No channels identified, use default

View File

@ -378,6 +378,16 @@ def details(plugin):
# Argument/Option Handling
for key in list(template_args.keys()):
if 'alias_of' in template_args[key]:
# Check if the mapped reference is a list; if it is, then
# we need to store a different delimiter
alias_of = template_tokens.get(template_args[key]['alias_of'], {})
if alias_of.get('type', '').startswith('list') \
and 'delim' not in template_args[key]:
# Set a default delimiter of a comma and/or space if one
# hasn't already been specified
template_args[key]['delim'] = (',', ' ')
# _lookup_default looks up what the default value
if '_lookup_default' in template_args[key]:
template_args[key]['default'] = getattr(

View File

@ -28,6 +28,7 @@ import six
import contextlib
import os
from os.path import expanduser
from functools import reduce
try:
# Python 2.7
@ -113,10 +114,17 @@ GET_EMAIL_RE = re.compile(
re.IGNORECASE,
)
# Regular expression used to extract a phone number
GET_PHONE_NO_RE = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
# Regular expression used to destinguish between multiple URLs
URL_DETECTION_RE = re.compile(
r'([a-z0-9]+?:\/\/.*?)[\s,]*(?=$|[a-z0-9]+?:\/\/)', re.I)
# validate_regex() utilizes this mapping to track and re-use pre-complied
# regular expressions
REGEX_VALIDATE_LOOKUP = {}
def is_hostname(hostname):
"""
@ -512,14 +520,6 @@ def parse_list(*args):
elif isinstance(arg, (set, list, tuple)):
result += parse_list(*arg)
elif arg is None:
# Ignore
continue
else:
# Convert whatever it is to a string and work with it
result += parse_list(str(arg))
#
# filter() eliminates any empty entries
#
@ -573,6 +573,11 @@ def is_exclusive_match(logic, data, match_all='all'):
# treat these entries as though all elements found
# must exist in the notification service
entries = set(parse_list(entry))
if not entries:
# We got a bogus set of tags to parse
# If there is no logic to apply then we're done early; we only
# match if there is also no data to match against
return not data
if len(entries.intersection(data.union({match_all}))) == len(entries):
# our set contains all of the entries found
@ -587,6 +592,82 @@ def is_exclusive_match(logic, data, match_all='all'):
return matched
def validate_regex(value, regex=r'[^\s]+', flags=re.I, strip=True, fmt=None):
"""
A lot of the tokens, secrets, api keys, etc all have some regular
expression validation they support. This hashes the regex after it's
compiled and returns it's content if matched, otherwise it returns None.
This function greatly increases performance as it prevents apprise modules
from having to pre-compile all of their regular expressions.
value is the element being tested
regex is the regular expression to be compiled and tested. By default
we extract the first chunk of code while eliminating surrounding
whitespace (if present)
flags is the regular expression flags that should be applied
format is used to alter the response format if the regular
expression matches. You identify your format using {tags}.
Effectively nesting your ID's between {}. Consider a regex of:
'(?P<year>[0-9]{2})[0-9]+(?P<value>[A-Z])'
to which you could set your format up as '{value}-{year}'. This
would substitute the matched groups and format a response.
"""
if flags:
# Regex String -> Flag Lookup Map
_map = {
# Ignore Case
'i': re.I,
# Multi Line
'm': re.M,
# Dot Matches All
's': re.S,
# Locale Dependant
'L': re.L,
# Unicode Matching
'u': re.U,
# Verbose
'x': re.X,
}
if isinstance(flags, six.string_types):
# Convert a string of regular expression flags into their
# respected integer (expected) Python values and perform
# a bit-wise or on each match found:
flags = reduce(
lambda x, y: x | y,
[0] + [_map[f] for f in flags if f in _map])
else:
# Handles None/False/'' cases
flags = 0
# A key is used to store our compiled regular expression
key = '{}{}'.format(regex, flags)
if key not in REGEX_VALIDATE_LOOKUP:
REGEX_VALIDATE_LOOKUP[key] = re.compile(regex, flags)
# Perform our lookup usig our pre-compiled result
try:
result = REGEX_VALIDATE_LOOKUP[key].match(value)
if not result:
# let outer exception handle this
raise TypeError
if fmt:
# Map our format back to our response
value = fmt.format(**result.groupdict())
except (TypeError, AttributeError):
return None
# Return our response
return value.strip() if strip else value
@contextlib.contextmanager
def environ(*remove, **update):
"""

View File

@ -68,11 +68,11 @@ def test_apprise():
a = Apprise()
# no items
assert(len(a) == 0)
assert len(a) == 0
# Apprise object can also be directly tested with 'if' keyword
# No entries results in a False response
assert(not a)
assert not a
# Create an Asset object
asset = AppriseAsset(theme='default')
@ -89,66 +89,62 @@ def test_apprise():
a = Apprise(servers=servers)
# 2 servers loaded
assert(len(a) == 2)
assert len(a) == 2
# Apprise object can also be directly tested with 'if' keyword
# At least one entry results in a True response
assert(a)
assert a
# We can retrieve our URLs this way:
assert(len(a.urls()) == 2)
assert len(a.urls()) == 2
# We can add another server
assert(
a.add('mmosts://mattermost.server.local/'
'3ccdd113474722377935511fc85d3dd4') is True)
assert(len(a) == 3)
assert a.add('mmosts://mattermost.server.local/'
'3ccdd113474722377935511fc85d3dd4') is True
assert len(a) == 3
# We can pop an object off of our stack by it's indexed value:
obj = a.pop(0)
assert(isinstance(obj, NotifyBase) is True)
assert(len(a) == 2)
assert isinstance(obj, NotifyBase) is True
assert len(a) == 2
# We can retrieve elements from our list too by reference:
assert(isinstance(a[0].url(), six.string_types) is True)
assert isinstance(a[0].url(), six.string_types) is True
# We can iterate over our list too:
count = 0
for o in a:
assert(isinstance(o.url(), six.string_types) is True)
assert isinstance(o.url(), six.string_types) is True
count += 1
# verify that we did indeed iterate over each element
assert(len(a) == count)
assert len(a) == count
# We can empty our set
a.clear()
assert(len(a) == 0)
assert len(a) == 0
# An invalid schema
assert(
a.add('this is not a parseable url at all') is False)
assert(len(a) == 0)
assert a.add('this is not a parseable url at all') is False
assert len(a) == 0
# An unsupported schema
assert(
a.add('invalid://we.just.do.not.support.this.plugin.type') is False)
assert(len(a) == 0)
assert a.add(
'invalid://we.just.do.not.support.this.plugin.type') is False
assert len(a) == 0
# A poorly formatted URL
assert(
a.add('json://user:@@@:bad?no.good') is False)
assert(len(a) == 0)
assert a.add('json://user:@@@:bad?no.good') is False
assert len(a) == 0
# Add a server with our asset we created earlier
assert(
a.add('mmosts://mattermost.server.local/'
'3ccdd113474722377935511fc85d3dd4', asset=asset) is True)
assert a.add('mmosts://mattermost.server.local/'
'3ccdd113474722377935511fc85d3dd4', asset=asset) is True
# Clear our server listings again
a.clear()
# No servers to notify
assert(a.notify(title="my title", body="my body") is False)
assert a.notify(title="my title", body="my body") is False
class BadNotification(NotifyBase):
def __init__(self, **kwargs):
@ -183,26 +179,26 @@ def test_apprise():
# Just to explain what is happening here, we would have parsed the
# url properly but failed when we went to go and create an instance
# of it.
assert(a.add('bad://localhost') is False)
assert(len(a) == 0)
assert a.add('bad://localhost') is False
assert len(a) == 0
assert(a.add('good://localhost') is True)
assert(len(a) == 1)
assert a.add('good://localhost') is True
assert len(a) == 1
# Bad Notification Type is still allowed as it is presumed the user
# know's what their doing
assert(a.notify(
title="my title", body="my body", notify_type='bad') is True)
assert a.notify(
title="my title", body="my body", notify_type='bad') is True
# No Title/Body combo's
assert(a.notify(title=None, body=None) is False)
assert(a.notify(title='', body=None) is False)
assert(a.notify(title=None, body='') is False)
assert a.notify(title=None, body=None) is False
assert a.notify(title='', body=None) is False
assert a.notify(title=None, body='') is False
# As long as one is present, we're good
assert(a.notify(title=None, body='present') is True)
assert(a.notify(title='present', body=None) is True)
assert(a.notify(title="present", body="present") is True)
assert a.notify(title=None, body='present') is True
assert a.notify(title='present', body=None) is True
assert a.notify(title="present", body="present") is True
# Clear our server listings again
a.clear()
@ -244,14 +240,14 @@ def test_apprise():
# Store our good notification in our schema map
SCHEMA_MAP['runtime'] = RuntimeNotification
assert(a.add('runtime://localhost') is True)
assert(a.add('throw://localhost') is True)
assert(a.add('fail://localhost') is True)
assert(len(a) == 3)
assert a.add('runtime://localhost') is True
assert a.add('throw://localhost') is True
assert a.add('fail://localhost') is True
assert len(a) == 3
# Test when our notify both throws an exception and or just
# simply returns False
assert(a.notify(title="present", body="present") is False)
assert a.notify(title="present", body="present") is False
# Create a Notification that throws an unexected exception
class ThrowInstantiateNotification(NotifyBase):
@ -267,7 +263,7 @@ def test_apprise():
# Reset our object
a.clear()
assert(len(a) == 0)
assert len(a) == 0
# Instantiate a bad object
plugin = a.instantiate(object, tag="bad_object")
@ -275,40 +271,37 @@ def test_apprise():
# Instantiate a good object
plugin = a.instantiate('good://localhost', tag="good")
assert(isinstance(plugin, NotifyBase))
assert isinstance(plugin, NotifyBase)
# Test simple tagging inside of the object
assert("good" in plugin)
assert("bad" not in plugin)
assert "good" in plugin
assert "bad" not in plugin
# the in (__contains__ override) is based on or'ed content; so although
# 'bad' isn't tagged as being in the plugin, 'good' is, so the return
# value of this is True
assert(["bad", "good"] in plugin)
assert(set(["bad", "good"]) in plugin)
assert(("bad", "good") in plugin)
assert ["bad", "good"] in plugin
assert set(["bad", "good"]) in plugin
assert ("bad", "good") in plugin
# We an add already substatiated instances into our Apprise object
a.add(plugin)
assert(len(a) == 1)
assert len(a) == 1
# We can add entries as a list too (to add more then one)
a.add([plugin, plugin, plugin])
assert(len(a) == 4)
assert len(a) == 4
# Reset our object again
a.clear()
try:
with pytest.raises(TypeError):
a.instantiate('throw://localhost', suppress_exceptions=False)
assert(False)
except TypeError:
assert(True)
assert(len(a) == 0)
assert len(a) == 0
assert(a.instantiate(
'throw://localhost', suppress_exceptions=True) is None)
assert(len(a) == 0)
assert a.instantiate(
'throw://localhost', suppress_exceptions=True) is None
assert len(a) == 0
#
# We rince and repeat the same tests as above, however we do them
@ -317,50 +310,53 @@ def test_apprise():
# Reset our object
a.clear()
assert(len(a) == 0)
assert len(a) == 0
# Instantiate a good object
plugin = a.instantiate({
'schema': 'good',
'host': 'localhost'}, tag="good")
assert(isinstance(plugin, NotifyBase))
assert isinstance(plugin, NotifyBase)
# Test simple tagging inside of the object
assert("good" in plugin)
assert("bad" not in plugin)
assert "good" in plugin
assert "bad" not in plugin
# the in (__contains__ override) is based on or'ed content; so although
# 'bad' isn't tagged as being in the plugin, 'good' is, so the return
# value of this is True
assert(["bad", "good"] in plugin)
assert(set(["bad", "good"]) in plugin)
assert(("bad", "good") in plugin)
assert ["bad", "good"] in plugin
assert set(["bad", "good"]) in plugin
assert ("bad", "good") in plugin
# We an add already substatiated instances into our Apprise object
a.add(plugin)
assert(len(a) == 1)
assert len(a) == 1
# We can add entries as a list too (to add more then one)
a.add([plugin, plugin, plugin])
assert(len(a) == 4)
assert len(a) == 4
# Reset our object again
a.clear()
try:
with pytest.raises(TypeError):
a.instantiate({
'schema': 'throw',
'host': 'localhost'}, suppress_exceptions=False)
assert(False)
except TypeError:
assert(True)
assert(len(a) == 0)
assert len(a) == 0
assert(a.instantiate({
assert a.instantiate({
'schema': 'throw',
'host': 'localhost'}, suppress_exceptions=True) is None)
assert(len(a) == 0)
'host': 'localhost'}, suppress_exceptions=True) is None
assert len(a) == 0
def test_apprise_pretty_print(tmpdir):
"""
API: Apprise() Pretty Print tests
"""
# Privacy Print
# PrivacyMode.Secret always returns the same thing to avoid guessing
assert URLBase.pprint(
@ -410,6 +406,10 @@ def test_apprise():
assert URLBase.pprint(
"abcdefghijk", privacy=True, mode=PrivacyMode.Tail) == '...hijk'
# Quoting settings
assert URLBase.pprint(" ", privacy=False, safe='') == '%20'
assert URLBase.pprint(" ", privacy=False, quote=False, safe='') == ' '
@mock.patch('requests.get')
@mock.patch('requests.post')
@ -437,90 +437,94 @@ def test_apprise_tagging(mock_post, mock_get):
a = Apprise()
# An invalid addition can't add the tag
assert(a.add('averyinvalidschema://localhost', tag='uhoh') is False)
assert(a.add({
assert a.add('averyinvalidschema://localhost', tag='uhoh') is False
assert a.add({
'schema': 'averyinvalidschema',
'host': 'localhost'}, tag='uhoh') is False)
'host': 'localhost'}, tag='uhoh') is False
# Add entry and assign it to a tag called 'awesome'
assert(a.add('json://localhost/path1/', tag='awesome') is True)
assert(a.add({
assert a.add('json://localhost/path1/', tag='awesome') is True
assert a.add({
'schema': 'json',
'host': 'localhost',
'fullpath': '/path1/'}, tag='awesome') is True)
'fullpath': '/path1/'}, tag='awesome') is True
# Add another notification and assign it to a tag called 'awesome'
# and another tag called 'local'
assert(a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True)
assert a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True
# notify the awesome tag; this would notify both services behind the
# scenes
assert(a.notify(title="my title", body="my body", tag='awesome') is True)
assert a.notify(title="my title", body="my body", tag='awesome') is True
# notify all of the tags
assert(a.notify(
title="my title", body="my body", tag=['awesome', 'mmost']) is True)
assert a.notify(
title="my title", body="my body", tag=['awesome', 'mmost']) is True
# When we query against our loaded notifications for a tag that simply
# isn't assigned to anything, we return None. None (different then False)
# tells us that we litterally had nothing to query. We didn't fail...
# but we also didn't do anything...
assert(a.notify(
title="my title", body="my body", tag='missing') is None)
assert a.notify(
title="my title", body="my body", tag='missing') is None
# Now to test the ability to and and/or notifications
a = Apprise()
# Add a tag by tuple
assert(a.add('json://localhost/tagA/', tag=("TagA", )) is True)
assert a.add('json://localhost/tagA/', tag=("TagA", )) is True
# Add 2 tags by string
assert(a.add('json://localhost/tagAB/', tag="TagA, TagB") is True)
assert a.add('json://localhost/tagAB/', tag="TagA, TagB") is True
# Add a tag using a set
assert(a.add('json://localhost/tagB/', tag=set(["TagB"])) is True)
assert a.add('json://localhost/tagB/', tag=set(["TagB"])) is True
# Add a tag by string (again)
assert(a.add('json://localhost/tagC/', tag="TagC") is True)
assert a.add('json://localhost/tagC/', tag="TagC") is True
# Add 2 tags using a list
assert(a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True)
assert a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True
# Add a tag by string (again)
assert(a.add('json://localhost/tagD/', tag="TagD") is True)
assert a.add('json://localhost/tagD/', tag="TagD") is True
# add a tag set by set (again)
assert(a.add('json://localhost/tagCDE/',
tag=set(["TagC", "TagD", "TagE"])) is True)
assert a.add('json://localhost/tagCDE/',
tag=set(["TagC", "TagD", "TagE"])) is True
# Expression: TagC and TagD
# Matches the following only:
# - json://localhost/tagCD/
# - json://localhost/tagCDE/
assert(a.notify(
title="my title", body="my body", tag=[('TagC', 'TagD')]) is True)
assert a.notify(
title="my title", body="my body", tag=[('TagC', 'TagD')]) is True
# Expression: (TagY and TagZ) or TagX
# Matches nothing, None is returned in this case
assert(a.notify(
assert a.notify(
title="my title", body="my body",
tag=[('TagY', 'TagZ'), 'TagX']) is None)
tag=[('TagY', 'TagZ'), 'TagX']) is None
# Expression: (TagY and TagZ) or TagA
# Matches the following only:
# - json://localhost/tagAB/
assert(a.notify(
assert a.notify(
title="my title", body="my body",
tag=[('TagY', 'TagZ'), 'TagA']) is True)
tag=[('TagY', 'TagZ'), 'TagA']) is True
# Expression: (TagE and TagD) or TagB
# Matches the following only:
# - json://localhost/tagCDE/
# - json://localhost/tagAB/
# - json://localhost/tagB/
assert(a.notify(
assert a.notify(
title="my title", body="my body",
tag=[('TagE', 'TagD'), 'TagB']) is True)
tag=[('TagE', 'TagD'), 'TagB']) is True
# Garbage Entries; we can't do anything with the tag so we have nothing to
# notify as a result. So we simply return None
assert(a.notify(
# Garbage Entries in tag field just get stripped out. the below
# is the same as notifying no tags at all. Since we have not added
# any entries that do not have tags (that we can match against)
# we fail. None is returned as a way of letting us know that we
# had Notifications to notify, but since none of them matched our tag
# none were notified.
assert a.notify(
title="my title", body="my body",
tag=[(object, ), ]) is None)
tag=[(object, ), ]) is None
def test_apprise_notify_formats(tmpdir):
@ -536,7 +540,7 @@ def test_apprise_notify_formats(tmpdir):
a = Apprise()
# no items
assert(len(a) == 0)
assert len(a) == 0
class TextNotification(NotifyBase):
# set our default notification format
@ -594,26 +598,29 @@ def test_apprise_notify_formats(tmpdir):
# defined plugin above was defined to default to HTML which triggers
# a markdown to take place if the body_format specified on the notify
# call
assert(a.add('html://localhost') is True)
assert(a.add('html://another.server') is True)
assert(a.add('html://and.another') is True)
assert(a.add('text://localhost') is True)
assert(a.add('text://another.server') is True)
assert(a.add('text://and.another') is True)
assert(a.add('markdown://localhost') is True)
assert(a.add('markdown://another.server') is True)
assert(a.add('markdown://and.another') is True)
assert a.add('html://localhost') is True
assert a.add('html://another.server') is True
assert a.add('html://and.another') is True
assert a.add('text://localhost') is True
assert a.add('text://another.server') is True
assert a.add('text://and.another') is True
assert a.add('markdown://localhost') is True
assert a.add('markdown://another.server') is True
assert a.add('markdown://and.another') is True
assert(len(a) == 9)
assert len(a) == 9
assert(a.notify(title="markdown", body="## Testing Markdown",
body_format=NotifyFormat.MARKDOWN) is True)
assert a.notify(
title="markdown", body="## Testing Markdown",
body_format=NotifyFormat.MARKDOWN) is True
assert(a.notify(title="text", body="Testing Text",
body_format=NotifyFormat.TEXT) is True)
assert a.notify(
title="text", body="Testing Text",
body_format=NotifyFormat.TEXT) is True
assert(a.notify(title="html", body="<b>HTML</b>",
body_format=NotifyFormat.HTML) is True)
assert a.notify(
title="html", body="<b>HTML</b>",
body_format=NotifyFormat.HTML) is True
def test_apprise_asset(tmpdir):
@ -623,7 +630,7 @@ def test_apprise_asset(tmpdir):
"""
a = AppriseAsset(theme=None)
# Default theme
assert(a.theme == 'default')
assert a.theme == 'default'
a = AppriseAsset(
theme='dark',
@ -634,58 +641,49 @@ def test_apprise_asset(tmpdir):
a.default_html_color = '#abcabc'
a.html_notify_map[NotifyType.INFO] = '#aaaaaa'
assert(a.color('invalid', tuple) == (171, 202, 188))
assert(a.color(NotifyType.INFO, tuple) == (170, 170, 170))
assert a.color('invalid', tuple) == (171, 202, 188)
assert a.color(NotifyType.INFO, tuple) == (170, 170, 170)
assert(a.color('invalid', int) == 11258556)
assert(a.color(NotifyType.INFO, int) == 11184810)
assert a.color('invalid', int) == 11258556
assert a.color(NotifyType.INFO, int) == 11184810
assert(a.color('invalid', None) == '#abcabc')
assert(a.color(NotifyType.INFO, None) == '#aaaaaa')
assert a.color('invalid', None) == '#abcabc'
assert a.color(NotifyType.INFO, None) == '#aaaaaa'
# None is the default
assert(a.color(NotifyType.INFO) == '#aaaaaa')
assert a.color(NotifyType.INFO) == '#aaaaaa'
# Invalid Type
try:
a.color(NotifyType.INFO, dict)
# We should not get here (exception should be thrown)
assert(False)
except ValueError:
with pytest.raises(ValueError):
# The exception we expect since dict is not supported
assert(True)
a.color(NotifyType.INFO, dict)
except Exception:
# Any other exception is not good
assert(False)
assert a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) == \
'http://localhost/dark/info-256x256.png'
assert(a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) ==
'http://localhost/dark/info-256x256.png')
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=False) == '/dark/info-256x256.png')
must_exist=False) == '/dark/info-256x256.png'
# This path doesn't exist so image_raw will fail (since we just
# randompyl picked it for testing)
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is None)
must_exist=True) is None
# Create a new object (with our default settings)
a = AppriseAsset()
# Our default configuration can access our file
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is not None)
must_exist=True) is not None
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
# Create a temporary directory
sub = tmpdir.mkdir("great.theme")
@ -703,14 +701,14 @@ def test_apprise_asset(tmpdir):
)
# We'll be able to read file we just created
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
# We can retrieve the filename at this point even with must_exist set
# to True
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is not None)
must_exist=True) is not None
# If we make the file un-readable however, we won't be able to read it
# This test is just showing that we won't throw an exception
@ -720,37 +718,37 @@ def test_apprise_asset(tmpdir):
pytest.skip('The Root user can not run file permission tests.')
chmod(dirname(sub.strpath), 0o000)
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
# Our path doesn't exist anymore using this logic
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is None)
must_exist=True) is None
# Return our permission so we don't have any problems with our cleanup
chmod(dirname(sub.strpath), 0o700)
# Our content is retrivable again
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
# our file path is accessible again too
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is not None)
must_exist=True) is not None
# We do the same test, but set the permission on the file
chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o000)
# our path will still exist in this case
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=True) is not None)
must_exist=True) is not None
# but we will not be able to open it
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
# Restore our permissions
chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o640)
@ -759,12 +757,12 @@ def test_apprise_asset(tmpdir):
a = AppriseAsset(image_path_mask=False, image_url_mask=False)
# We always return none in these calls now
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
assert(a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) is None)
assert(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
must_exist=False) is None)
assert(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
must_exist=True) is None)
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
assert a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) is None
assert a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
must_exist=False) is None
assert a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
must_exist=True) is None
# Test our default extension out
a = AppriseAsset(
@ -772,28 +770,28 @@ def test_apprise_asset(tmpdir):
image_url_mask='http://localhost/{THEME}/{TYPE}-{XY}{EXTENSION}',
default_extension='.jpeg',
)
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_256,
must_exist=False) == '/default/info-256x256.jpeg')
must_exist=False) == '/default/info-256x256.jpeg'
assert(a.image_url(
assert a.image_url(
NotifyType.INFO,
NotifyImageSize.XY_256) == 'http://localhost/'
'default/info-256x256.jpeg')
NotifyImageSize.XY_256) == \
'http://localhost/default/info-256x256.jpeg'
# extension support
assert(a.image_path(
assert a.image_path(
NotifyType.INFO,
NotifyImageSize.XY_128,
must_exist=False,
extension='.ico') == '/default/info-128x128.ico')
extension='.ico') == '/default/info-128x128.ico'
assert(a.image_url(
assert a.image_url(
NotifyType.INFO,
NotifyImageSize.XY_256,
extension='.test') == 'http://localhost/'
'default/info-256x256.test')
extension='.test') == \
'http://localhost/default/info-256x256.test'
def test_apprise_details():
@ -894,6 +892,11 @@ def test_apprise_details():
#
'_exists_if': 'always_true',
},
# alias_of testing
'test_alias_of': {
'alias_of': 'mylistB',
'delim': ('-', ' ')
}
})
def url(self):
@ -1116,10 +1119,11 @@ def test_apprise_details_plugin_verification():
assert '{} is an invalid regex'\
.format(arg['regex'][0])
# Regex should never start and/or end with ^/$; leave
# that up to the user making use of the regex instead
assert re.match(r'^[()\s]*\^', arg['regex'][0]) is None
assert re.match(r'[()\s$]*\$', arg['regex'][0]) is None
# Regex should always start and/or end with ^/$
assert re.match(
r'^\^.+?$', arg['regex'][0]) is not None
assert re.match(
r'^.+?\$$', arg['regex'][0]) is not None
if arg['type'].startswith('list'):
# Delimiters MUST be defined
@ -1132,8 +1136,55 @@ def test_apprise_details_plugin_verification():
assert isinstance(arg['alias_of'], six.string_types)
# Track our alias_of object
map_to_aliases.add(arg['alias_of'])
# 2 entries (name, and alias_of only!)
assert len(entry['details'][section][key]) == 1
# Ensure we're not already in the tokens section
# The alias_of object has no value here
assert section != 'tokens'
# We can't be an alias_of ourselves
if key == arg['alias_of']:
# This is acceptable as long as we exist in the tokens
# table because that is truely what we map back to
assert key in entry['details']['tokens']
else:
# Throw the problem into an assert tag for debugging
# purposes... the mapping is not acceptable
assert key != arg['alias_of']
# alias_of always references back to tokens
assert \
arg['alias_of'] in entry['details']['tokens'] or \
arg['alias_of'] in entry['details']['args']
# Find a list directive in our tokens
t_match = entry['details']['tokens']\
.get(arg['alias_of'], {})\
.get('type', '').startswith('list')
a_match = entry['details']['args']\
.get(arg['alias_of'], {})\
.get('type', '').startswith('list')
if not (t_match or a_match):
# Ensure the only token we have is the alias_of
assert len(entry['details'][section][key]) == 1
else:
# We're a list, we allow up to 2 variables
# Obviously we have the alias_of entry; that's why
# were at this part of the code. But we can
# additionally provide a 'delim' over-ride.
assert len(entry['details'][section][key]) <= 2
if len(entry['details'][section][key]) == 2:
# Verify that it is in fact the 'delim' tag
assert 'delim' in entry['details'][section][key]
# If we do have a delim value set, it must be of
# a list/set/tuple type
assert isinstance(
entry['details'][section][key]['delim'],
(tuple, set, list),
)
if six.PY2:
# inspect our object

View File

@ -25,6 +25,7 @@
import sys
import six
import pytest
from apprise.AppriseAsset import AppriseAsset
from apprise.config.ConfigBase import ConfigBase
from apprise.config import __load_matrix
@ -41,22 +42,12 @@ def test_config_base():
"""
# invalid types throw exceptions
try:
with pytest.raises(TypeError):
ConfigBase(**{'format': 'invalid'})
# We should never reach here as an exception should be thrown
assert(False)
except TypeError:
assert(True)
# Config format types are not the same as ConfigBase ones
try:
with pytest.raises(TypeError):
ConfigBase(**{'format': 'markdown'})
# We should never reach here as an exception should be thrown
assert(False)
except TypeError:
assert(True)
cb = ConfigBase(**{'format': 'yaml'})
assert isinstance(cb, ConfigBase)

View File

@ -274,17 +274,17 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
# Expected None but didn't get it
print('%s instantiated %s (but expected None)' % (
url, str(obj)))
assert(False)
assert False
assert(isinstance(obj, instance))
assert isinstance(obj, instance)
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)
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)
assert isinstance(
obj.url(privacy=True), six.string_types) is True
# Some Simple Invalid Instance Testing
assert instance.parse_url(None) is None
@ -307,14 +307,14 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
# assertion failure makes things easier to debug later on
print('TEST FAIL: {} regenerated as {}'.format(
url, obj.url()))
assert(False)
assert False
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))
assert(getattr(key, obj) == val)
assert hasattr(key, obj)
assert getattr(key, obj) == val
try:
if test_smtplib_exceptions is False:
@ -389,7 +389,7 @@ def test_webbase_lookup(mock_smtp, mock_smtpssl):
obj = Apprise.instantiate(
'mailto://user:pass@l2g.com', suppress_exceptions=True)
assert(isinstance(obj, plugins.NotifyEmail))
assert isinstance(obj, plugins.NotifyEmail)
assert len(obj.targets) == 1
assert 'user@l2g.com' in obj.targets
assert obj.from_addr == 'user@l2g.com'
@ -417,7 +417,7 @@ def test_smtplib_init_fail(mock_smtplib):
obj = Apprise.instantiate(
'mailto://user:pass@gmail.com', suppress_exceptions=False)
assert(isinstance(obj, plugins.NotifyEmail))
assert isinstance(obj, plugins.NotifyEmail)
# Support Exception handling of smtplib.SMTP
mock_smtplib.side_effect = RuntimeError('Test')
@ -443,7 +443,7 @@ def test_smtplib_send_okay(mock_smtplib):
# Defaults to HTML
obj = Apprise.instantiate(
'mailto://user:pass@gmail.com', suppress_exceptions=False)
assert(isinstance(obj, plugins.NotifyEmail))
assert isinstance(obj, plugins.NotifyEmail)
# Support an email simulation where we can correctly quit
mock_smtplib.starttls.return_value = True
@ -451,16 +451,16 @@ def test_smtplib_send_okay(mock_smtplib):
mock_smtplib.sendmail.return_value = True
mock_smtplib.quit.return_value = True
assert(obj.notify(
body='body', title='test', notify_type=NotifyType.INFO) is True)
assert obj.notify(
body='body', title='test', notify_type=NotifyType.INFO) is True
# Set Text
obj = Apprise.instantiate(
'mailto://user:pass@gmail.com?format=text', suppress_exceptions=False)
assert(isinstance(obj, plugins.NotifyEmail))
assert isinstance(obj, plugins.NotifyEmail)
assert(obj.notify(
body='body', title='test', notify_type=NotifyType.INFO) is True)
assert obj.notify(
body='body', title='test', notify_type=NotifyType.INFO) is True
def test_email_url_escaping():

View File

@ -26,7 +26,7 @@ import six
import mock
import requests
from apprise import plugins
# from apprise import AppriseAsset
from json import dumps
from datetime import datetime
@ -87,16 +87,6 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
mock_get.return_value = request
mock_post.return_value = request
# Variation Initializations (no token)
try:
obj = plugins.NotifyGitter(token=None, targets='apprise')
# No Token should throw an exception
assert False
except TypeError:
# We should get here
assert True
# Variation Initializations
obj = plugins.NotifyGitter(token=token, targets='apprise')
assert isinstance(obj, plugins.NotifyGitter) is True
@ -181,8 +171,22 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
assert obj.send(body="test") is True
# Variation Initializations
obj = plugins.NotifyGitter(token=token, targets='missing')
obj = plugins.NotifyGitter(token=token, targets='apprise')
assert isinstance(obj, plugins.NotifyGitter) is True
assert isinstance(obj.url(), six.string_types) is True
# missing room was found
# apprise room was not found
assert obj.send(body="test") is False
# Test exception handling
mock_post.side_effect = \
requests.ConnectionError(0, 'requests.ConnectionError()')
# Create temporary _room_mapping object so we will find the apprise
# channel on our second call to send()
obj._room_mapping = {
'apprise': {
'id': '5c981cecd73408ce4fbbad31',
'uri': 'apprise-notifications/community',
}
}
assert obj.send(body='test body', title='test title') is False

View File

@ -136,141 +136,149 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
# Create our instance (identify all supported types)
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj = apprise.Apprise.instantiate('kde://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj = apprise.Apprise.instantiate('qt://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj.duration = 0
# Check that it found our mocked environments
assert(obj._enabled is True)
assert obj._enabled is True
# Test our class loading using a series of arguments
try:
with pytest.raises(TypeError):
apprise.plugins.NotifyDBus(**{'schema': 'invalid'})
# We should not reach here as the invalid schema
# should force an exception
assert(False)
except TypeError:
# Expected behaviour
assert(True)
# Invalid URLs
assert apprise.plugins.NotifyDBus.parse_url('') is None
# Set our X and Y coordinate and try the notification
assert(
apprise.plugins.NotifyDBus(
x_axis=0, y_axis=0, **{'schema': 'dbus'})
assert apprise.plugins.NotifyDBus(
x_axis=0, y_axis=0, **{'schema': 'dbus'})\
.notify(title='', body='body',
notify_type=apprise.NotifyType.INFO) is True)
notify_type=apprise.NotifyType.INFO) is True
# test notifications
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True
# test notification without a title
assert(obj.notify(title='', body='body',
notify_type=apprise.NotifyType.INFO) is True)
assert obj.notify(
title='', body='body',
notify_type=apprise.NotifyType.INFO) is True
# Test our arguments through the instantiate call
obj = apprise.Apprise.instantiate(
'dbus://_/?image=True', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?image=False', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
# Test priority (alias to urgency) handling
obj = apprise.Apprise.instantiate(
'dbus://_/?priority=invalid', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?priority=high', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?priority=2', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
# Test urgency handling
obj = apprise.Apprise.instantiate(
'dbus://_/?urgency=invalid', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?urgency=high', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?urgency=2', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?urgency=', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
# Test x/y
obj = apprise.Apprise.instantiate(
'dbus://_/?x=5&y=5', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
obj = apprise.Apprise.instantiate(
'dbus://_/?x=invalid&y=invalid', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) 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)
assert isinstance(obj, apprise.plugins.NotifyDBus) 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
# If our underlining object throws for whatever reason, we will
# If our underlining object throws for whatever rea on, we will
# gracefully fail
mock_notify = mock.Mock()
mock_interface.return_value = mock_notify
mock_notify.Notify.side_effect = AttributeError()
assert(obj.notify(title='', body='body',
notify_type=apprise.NotifyType.INFO) is False)
assert obj.notify(
title='', body='body',
notify_type=apprise.NotifyType.INFO) is False
mock_notify.Notify.side_effect = None
# Test our loading of our icon exception; it will still allow the
# notification to be sent
mock_pixbuf.new_from_file.side_effect = AttributeError()
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True
# Undo our change
mock_pixbuf.new_from_file.side_effect = None
@ -278,8 +286,9 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
# 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)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is False
# Test the setting of a the urgency
apprise.plugins.NotifyDBus(urgency=0)
@ -306,15 +315,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
# Create our instance
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj.duration = 0
# Test url() call
assert(isinstance(obj.url(), six.string_types) is True)
assert isinstance(obj.url(), six.string_types) is True
# Our notification succeeds even though the gi library was not loaded
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True
# Verify this all works in the event a ValueError is also thronw
# out of the call to gi.require_version()
@ -336,15 +346,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
# Create our instance
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj.duration = 0
# Test url() call
assert(isinstance(obj.url(), six.string_types) is True)
assert isinstance(obj.url(), six.string_types) is True
# Our notification succeeds even though the gi library was not loaded
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True
# Force a global import error
_session_bus = sys.modules['dbus']
@ -358,15 +369,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
# Create our instance
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
obj.duration = 0
# Test url() call
assert(isinstance(obj.url(), six.string_types) is True)
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.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is False
# Since playing with the sys.modules is not such a good idea,
# let's just put it back now :)

View File

@ -211,7 +211,7 @@ def test_growl_plugin(mock_gntp):
try:
obj = Apprise.instantiate(url, suppress_exceptions=False)
assert(exception is None)
assert exception is None
if obj is None:
# We're done
@ -219,17 +219,17 @@ def test_growl_plugin(mock_gntp):
if instance is None:
# Expected None but didn't get it
assert(False)
assert False
assert(isinstance(obj, instance) is True)
assert isinstance(obj, instance) is True
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)
assert isinstance(obj.url(), six.string_types) is True
# Test our privacy=True flag
assert(isinstance(
obj.url(privacy=True), six.string_types) is True)
assert isinstance(
obj.url(privacy=True), six.string_types) is True
# Instantiate the exact same object again using the URL from
# the one that was already created properly
@ -243,14 +243,14 @@ def test_growl_plugin(mock_gntp):
# assertion failure makes things easier to debug later on
print('TEST FAIL: {} regenerated as {}'.format(
url, obj.url()))
assert(False)
assert False
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))
assert(getattr(key, obj) == val)
assert hasattr(key, obj)
assert getattr(key, obj) == val
try:
if test_growl_notify_exceptions is False:
@ -292,5 +292,5 @@ def test_growl_plugin(mock_gntp):
except Exception as e:
# Handle our exception
print('%s / %s' % (url, str(e)))
assert(exception is not None)
assert(isinstance(e, exception))
assert exception is not None
assert isinstance(e, exception)

View File

@ -23,6 +23,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import six
import pytest
from datetime import datetime
from datetime import timedelta
@ -43,22 +44,12 @@ def test_notify_base():
"""
# invalid types throw exceptions
try:
with pytest.raises(TypeError):
NotifyBase(**{'format': 'invalid'})
# We should never reach here as an exception should be thrown
assert(False)
except TypeError:
assert(True)
# invalid types throw exceptions
try:
with pytest.raises(TypeError):
NotifyBase(**{'overflow': 'invalid'})
# We should never reach here as an exception should be thrown
assert(False)
except TypeError:
assert(True)
# Bad port information
nb = NotifyBase(port='invalid')
@ -216,7 +207,7 @@ def test_notify_base():
# Test invalid data
assert NotifyBase.parse_list(None) == []
assert NotifyBase.parse_list(42) == ['42', ]
assert NotifyBase.parse_list(42) == []
result = NotifyBase.parse_list(
',path,?name=Dr%20Disrespect', unquote=False)

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@
# THE SOFTWARE.
import mock
import pytest
import requests
from apprise import plugins
from apprise import Apprise
@ -45,7 +46,7 @@ def test_object_initialization():
"""
# Initializes the plugin with a valid access, but invalid access key
try:
with pytest.raises(TypeError):
# No access_key_id specified
plugins.NotifySNS(
access_key_id=None,
@ -53,14 +54,8 @@ def test_object_initialization():
region_name=TEST_REGION,
targets='+1800555999',
)
# The entries above are invalid, our code should never reach here
assert(False)
except TypeError:
# Exception correctly caught
assert(True)
try:
with pytest.raises(TypeError):
# No secret_access_key specified
plugins.NotifySNS(
access_key_id=TEST_ACCESS_KEY_ID,
@ -68,14 +63,8 @@ def test_object_initialization():
region_name=TEST_REGION,
targets='+1800555999',
)
# The entries above are invalid, our code should never reach here
assert(False)
except TypeError:
# Exception correctly caught
assert(True)
try:
with pytest.raises(TypeError):
# No region_name specified
plugins.NotifySNS(
access_key_id=TEST_ACCESS_KEY_ID,
@ -83,14 +72,8 @@ def test_object_initialization():
region_name=None,
targets='+1800555999',
)
# The entries above are invalid, our code should never reach here
assert(False)
except TypeError:
# Exception correctly caught
assert(True)
try:
with pytest.raises(TypeError):
# No recipients
plugins.NotifySNS(
access_key_id=TEST_ACCESS_KEY_ID,
@ -98,14 +81,8 @@ def test_object_initialization():
region_name=TEST_REGION,
targets=None,
)
# Still valid even without recipients
assert(True)
except TypeError:
# Exception correctly caught
assert(False)
try:
with pytest.raises(TypeError):
# No recipients - garbage recipients object
plugins.NotifySNS(
access_key_id=TEST_ACCESS_KEY_ID,
@ -113,14 +90,8 @@ def test_object_initialization():
region_name=TEST_REGION,
targets=object(),
)
# Still valid even without recipients
assert(True)
except TypeError:
# Exception correctly caught
assert(False)
try:
with pytest.raises(TypeError):
# The phone number is invalid, and without it, there is nothing
# to notify
plugins.NotifySNS(
@ -129,15 +100,8 @@ def test_object_initialization():
region_name=TEST_REGION,
targets='+1809',
)
# The recipient is invalid, but it's still okay; this Notification
# still becomes pretty much useless at this point though
assert(True)
except TypeError:
# Exception correctly caught
assert(False)
try:
with pytest.raises(TypeError):
# The phone number is invalid, and without it, there is nothing
# to notify; we
plugins.NotifySNS(
@ -146,13 +110,6 @@ def test_object_initialization():
region_name=TEST_REGION,
targets='#(invalid-topic-because-of-the-brackets)',
)
# The recipient is invalid, but it's still okay; this Notification
# still becomes pretty much useless at this point though
assert(True)
except TypeError:
# Exception correctly caught
assert(False)
def test_url_parsing():
@ -169,13 +126,13 @@ def test_url_parsing():
)
# Confirm that there were no recipients found
assert(len(results['targets']) == 0)
assert('region_name' in results)
assert(TEST_REGION == results['region_name'])
assert('access_key_id' in results)
assert(TEST_ACCESS_KEY_ID == results['access_key_id'])
assert('secret_access_key' in results)
assert(TEST_ACCESS_KEY_SECRET == results['secret_access_key'])
assert len(results['targets']) == 0
assert 'region_name' in results
assert TEST_REGION == results['region_name']
assert 'access_key_id' in results
assert TEST_ACCESS_KEY_ID == results['access_key_id']
assert 'secret_access_key' in results
assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
# Detect recipients
results = plugins.NotifySNS.parse_url('sns://%s/%s/%s/%s/%s/' % (
@ -188,15 +145,15 @@ def test_url_parsing():
)
# Confirm that our recipients were found
assert(len(results['targets']) == 2)
assert('+18001234567' in results['targets'])
assert('MyTopic' in results['targets'])
assert('region_name' in results)
assert(TEST_REGION == results['region_name'])
assert('access_key_id' in results)
assert(TEST_ACCESS_KEY_ID == results['access_key_id'])
assert('secret_access_key' in results)
assert(TEST_ACCESS_KEY_SECRET == results['secret_access_key'])
assert len(results['targets']) == 2
assert '+18001234567' in results['targets']
assert 'MyTopic' in results['targets']
assert 'region_name' in results
assert TEST_REGION == results['region_name']
assert 'access_key_id' in results
assert TEST_ACCESS_KEY_ID == results['access_key_id']
assert 'secret_access_key' in results
assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
def test_object_parsing():
@ -209,20 +166,20 @@ def test_object_parsing():
a = Apprise()
# Now test failing variations of our URL
assert(a.add('sns://') is False)
assert(a.add('sns://nosecret') is False)
assert(a.add('sns://nosecret/noregion/') is False)
assert a.add('sns://') is False
assert a.add('sns://nosecret') is False
assert a.add('sns://nosecret/noregion/') is False
# This is valid, but a rather useless URL; there is nothing to notify
assert(a.add('sns://norecipient/norecipient/us-west-2') is True)
assert(len(a) == 1)
# This is valid but without valid recipients, the URL is actually useless
assert a.add('sns://norecipient/norecipient/us-west-2') is False
assert len(a) == 0
# Parse a good one
assert(a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True)
assert(len(a) == 2)
assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
assert len(a) == 1
assert(a.add('sns://oh/yeah/us-west-2/12223334444') is True)
assert(len(a) == 3)
assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
assert len(a) == 2
def test_aws_response_handling():
@ -232,25 +189,25 @@ def test_aws_response_handling():
"""
# Not a string
response = plugins.NotifySNS.aws_response_to_dict(None)
assert(response['type'] is None)
assert(response['request_id'] is None)
assert response['type'] is None
assert response['request_id'] is None
# Invalid XML
response = plugins.NotifySNS.aws_response_to_dict(
'<Bad Response xmlns="http://sns.amazonaws.com/doc/2010-03-31/">')
assert(response['type'] is None)
assert(response['request_id'] is None)
assert response['type'] is None
assert response['request_id'] is None
# Single Element in XML
response = plugins.NotifySNS.aws_response_to_dict(
'<SingleElement></SingleElement>')
assert(response['type'] == 'SingleElement')
assert(response['request_id'] is None)
assert response['type'] == 'SingleElement'
assert response['request_id'] is None
# Empty String
response = plugins.NotifySNS.aws_response_to_dict('')
assert(response['type'] is None)
assert(response['request_id'] is None)
assert response['type'] is None
assert response['request_id'] is None
response = plugins.NotifySNS.aws_response_to_dict(
"""
@ -263,9 +220,9 @@ def test_aws_response_handling():
</ResponseMetadata>
</PublishResponse>
""")
assert(response['type'] == 'PublishResponse')
assert(response['request_id'] == 'dc258024-d0e6-56bb-af1b-d4fe5f4181a4')
assert(response['message_id'] == '5e16935a-d1fb-5a31-a716-c7805e5c1d2e')
assert response['type'] == 'PublishResponse'
assert response['request_id'] == 'dc258024-d0e6-56bb-af1b-d4fe5f4181a4'
assert response['message_id'] == '5e16935a-d1fb-5a31-a716-c7805e5c1d2e'
response = plugins.NotifySNS.aws_response_to_dict(
"""
@ -278,9 +235,9 @@ def test_aws_response_handling():
</ResponseMetadata>
</CreateTopicResponse>
""")
assert(response['type'] == 'CreateTopicResponse')
assert(response['request_id'] == '604bef0f-369c-50c5-a7a4-bbd474c83d6a')
assert(response['topic_arn'] == 'arn:aws:sns:us-east-1:000000000000:abcd')
assert response['type'] == 'CreateTopicResponse'
assert response['request_id'] == '604bef0f-369c-50c5-a7a4-bbd474c83d6a'
assert response['topic_arn'] == 'arn:aws:sns:us-east-1:000000000000:abcd'
response = plugins.NotifySNS.aws_response_to_dict(
"""
@ -294,12 +251,12 @@ def test_aws_response_handling():
<RequestId>b5614883-babe-56ca-93b2-1c592ba6191e</RequestId>
</ErrorResponse>
""")
assert(response['type'] == 'ErrorResponse')
assert(response['request_id'] == 'b5614883-babe-56ca-93b2-1c592ba6191e')
assert(response['error_type'] == 'Sender')
assert(response['error_code'] == 'InvalidParameter')
assert(response['error_message'].startswith('Invalid parameter:'))
assert(response['error_message'].endswith('required parameter'))
assert response['type'] == 'ErrorResponse'
assert response['request_id'] == 'b5614883-babe-56ca-93b2-1c592ba6191e'
assert response['error_type'] == 'Sender'
assert response['error_code'] == 'InvalidParameter'
assert response['error_message'].startswith('Invalid parameter:')
assert response['error_message'].endswith('required parameter')
@mock.patch('requests.post')
@ -356,7 +313,7 @@ def test_aws_topic_handling(mock_post):
'12223334444/TopicA'])
# CreateTopic fails
assert(a.notify(title='', body='test') is False)
assert a.notify(title='', body='test') is False
def post(url, data, **kwargs):
"""
@ -383,7 +340,7 @@ def test_aws_topic_handling(mock_post):
mock_post.side_effect = post
# Publish fails
assert(a.notify(title='', body='test') is False)
assert a.notify(title='', body='test') is False
# Disable our side effect
mock_post.side_effect = None
@ -395,14 +352,14 @@ def test_aws_topic_handling(mock_post):
# Assign ourselves a new function
mock_post.return_value = robj
assert(a.notify(title='', body='test') is False)
assert a.notify(title='', body='test') is False
# Handle case where we fails get a bad response
robj = mock.Mock()
robj.content = ''
robj.status_code = requests.codes.bad_request
mock_post.return_value = robj
assert(a.notify(title='', body='test') is False)
assert a.notify(title='', body='test') is False
# Handle case where we get a valid response and TopicARN
robj = mock.Mock()
@ -410,4 +367,4 @@ def test_aws_topic_handling(mock_post):
robj.status_code = requests.codes.ok
mock_post.return_value = robj
# We would have failed to make Post
assert(a.notify(title='', body='test') is True)
assert a.notify(title='', body='test') is True

View File

@ -25,6 +25,7 @@
import six
import mock
import pytest
import requests
from json import dumps
from datetime import datetime
@ -41,57 +42,40 @@ def test_twitter_plugin_init():
"""
try:
with pytest.raises(TypeError):
plugins.NotifyTwitter(
ckey=None, csecret=None, akey=None, asecret=None)
assert False
except TypeError:
# All keys set to none
assert True
try:
with pytest.raises(TypeError):
plugins.NotifyTwitter(
ckey='value', csecret=None, akey=None, asecret=None)
assert False
except TypeError:
# csecret not set
assert True
try:
with pytest.raises(TypeError):
plugins.NotifyTwitter(
ckey='value', csecret='value', akey=None, asecret=None)
assert False
except TypeError:
# akey not set
assert True
try:
with pytest.raises(TypeError):
plugins.NotifyTwitter(
ckey='value', csecret='value', akey='value', asecret=None)
assert False
except TypeError:
# asecret not set
assert True
try:
assert isinstance(
plugins.NotifyTwitter(
ckey='value', csecret='value', akey='value', asecret='value')
assert True
except TypeError:
# user not set; but this is okay
# We should not reach here
assert False
ckey='value', csecret='value', akey='value', asecret='value'),
plugins.NotifyTwitter,
)
try:
assert isinstance(
plugins.NotifyTwitter(
ckey='value', csecret='value', akey='value', asecret='value',
user='l2gnux')
# We should initialize properly
assert True
user='l2gnux'),
plugins.NotifyTwitter,
)
except TypeError:
# We should not reach here
assert False
# Invalid Target User
with pytest.raises(TypeError):
plugins.NotifyTwitter(
ckey='value', csecret='value', akey='value', asecret='value',
targets='%G@rB@g3')
@mock.patch('requests.get')

View File

@ -45,293 +45,290 @@ def test_parse_qsd():
"utils: parse_qsd() testing """
result = utils.parse_qsd('a=1&b=&c&d=abcd')
assert(isinstance(result, dict) is True)
assert(len(result) == 3)
assert isinstance(result, dict) is True
assert len(result) == 3
assert 'qsd' in result
assert 'qsd+' in result
assert 'qsd-' in result
assert(len(result['qsd']) == 4)
assert len(result['qsd']) == 4
assert 'a' in result['qsd']
assert 'b' in result['qsd']
assert 'c' in result['qsd']
assert 'd' in result['qsd']
assert(len(result['qsd-']) == 0)
assert(len(result['qsd+']) == 0)
assert len(result['qsd-']) == 0
assert len(result['qsd+']) == 0
def test_parse_url():
"utils: parse_url() testing """
result = utils.parse_url('http://hostname')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] is None)
assert(result['path'] is None)
assert(result['query'] is None)
assert(result['url'] == 'http://hostname')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] is None
assert result['path'] is None
assert result['query'] is None
assert result['url'] == 'http://hostname'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('http://hostname/')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname/')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname/'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('hostname')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] is None)
assert(result['path'] is None)
assert(result['query'] is None)
assert(result['url'] == 'http://hostname')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] is None
assert result['path'] is None
assert result['query'] is None
assert result['url'] == 'http://hostname'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('http://hostname/?-KeY=Value')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname/')
assert('-key' in result['qsd'])
assert(unquote(result['qsd']['-key']) == 'Value')
assert('KeY' in result['qsd-'])
assert(unquote(result['qsd-']['KeY']) == 'Value')
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname/'
assert '-key' in result['qsd']
assert unquote(result['qsd']['-key']) == 'Value'
assert 'KeY' in result['qsd-']
assert unquote(result['qsd-']['KeY']) == 'Value'
assert result['qsd+'] == {}
result = utils.parse_url('http://hostname/?+KeY=Value')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname/')
assert('+key' in result['qsd'])
assert('KeY' in result['qsd+'])
assert(result['qsd+']['KeY'] == 'Value')
assert(result['qsd-'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname/'
assert '+key' in result['qsd']
assert 'KeY' in result['qsd+']
assert result['qsd+']['KeY'] == 'Value'
assert result['qsd-'] == {}
result = utils.parse_url(
'http://hostname/?+KeY=ValueA&-kEy=ValueB&KEY=Value%20+C')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname/')
assert('+key' in result['qsd'])
assert('-key' in result['qsd'])
assert('key' in result['qsd'])
assert('KeY' in result['qsd+'])
assert(result['qsd+']['KeY'] == 'ValueA')
assert('kEy' in result['qsd-'])
assert(result['qsd-']['kEy'] == 'ValueB')
assert(result['qsd']['key'] == 'Value C')
assert(result['qsd']['+key'] == result['qsd+']['KeY'])
assert(result['qsd']['-key'] == result['qsd-']['kEy'])
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname/'
assert '+key' in result['qsd']
assert '-key' in result['qsd']
assert 'key' in result['qsd']
assert 'KeY' in result['qsd+']
assert result['qsd+']['KeY'] == 'ValueA'
assert 'kEy' in result['qsd-']
assert result['qsd-']['kEy'] == 'ValueB'
assert result['qsd']['key'] == 'Value C'
assert result['qsd']['+key'] == result['qsd+']['KeY']
assert result['qsd']['-key'] == result['qsd-']['kEy']
result = utils.parse_url('http://hostname////')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname/')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname/'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('http://hostname:40////')
assert(result['schema'] == 'http')
assert(result['host'] == 'hostname')
assert(result['port'] == 40)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/')
assert(result['path'] == '/')
assert(result['query'] is None)
assert(result['url'] == 'http://hostname:40/')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'hostname'
assert result['port'] == 40
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/'
assert result['path'] == '/'
assert result['query'] is None
assert result['url'] == 'http://hostname:40/'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('HTTP://HoStNaMe:40/test.php')
assert(result['schema'] == 'http')
assert(result['host'] == 'HoStNaMe')
assert(result['port'] == 40)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/test.php')
assert(result['path'] == '/')
assert(result['query'] == 'test.php')
assert(result['url'] == 'http://HoStNaMe:40/test.php')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'HoStNaMe'
assert result['port'] == 40
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/test.php'
assert result['path'] == '/'
assert result['query'] == 'test.php'
assert result['url'] == 'http://HoStNaMe:40/test.php'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url('HTTPS://user@hostname/test.py')
assert(result['schema'] == 'https')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] == 'user')
assert(result['password'] is None)
assert(result['fullpath'] == '/test.py')
assert(result['path'] == '/')
assert(result['query'] == 'test.py')
assert(result['url'] == 'https://user@hostname/test.py')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'https'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] == 'user'
assert result['password'] is None
assert result['fullpath'] == '/test.py'
assert result['path'] == '/'
assert result['query'] == 'test.py'
assert result['url'] == 'https://user@hostname/test.py'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url(' HTTPS://///user@@@hostname///test.py ')
assert(result['schema'] == 'https')
assert(result['host'] == 'hostname')
assert(result['port'] is None)
assert(result['user'] == 'user')
assert(result['password'] is None)
assert(result['fullpath'] == '/test.py')
assert(result['path'] == '/')
assert(result['query'] == 'test.py')
assert(result['url'] == 'https://user@hostname/test.py')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'https'
assert result['host'] == 'hostname'
assert result['port'] is None
assert result['user'] == 'user'
assert result['password'] is None
assert result['fullpath'] == '/test.py'
assert result['path'] == '/'
assert result['query'] == 'test.py'
assert result['url'] == 'https://user@hostname/test.py'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
result = utils.parse_url(
'HTTPS://user:password@otherHost/full///path/name/',
)
assert(result['schema'] == 'https')
assert(result['host'] == 'otherHost')
assert(result['port'] is None)
assert(result['user'] == 'user')
assert(result['password'] == 'password')
assert(result['fullpath'] == '/full/path/name/')
assert(result['path'] == '/full/path/name/')
assert(result['query'] is None)
assert(result['url'] == 'https://user:password@otherHost/full/path/name/')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'https'
assert result['host'] == 'otherHost'
assert result['port'] is None
assert result['user'] == 'user'
assert result['password'] == 'password'
assert result['fullpath'] == '/full/path/name/'
assert result['path'] == '/full/path/name/'
assert result['query'] is None
assert result['url'] == 'https://user:password@otherHost/full/path/name/'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
# Handle garbage
assert(utils.parse_url(None) is None)
assert utils.parse_url(None) is None
result = utils.parse_url(
'mailto://user:password@otherHost/lead2gold@gmail.com' +
'?from=test@test.com&name=Chris%20Caron&format=text'
)
assert(result['schema'] == 'mailto')
assert(result['host'] == 'otherHost')
assert(result['port'] is None)
assert(result['user'] == 'user')
assert(result['password'] == 'password')
assert(unquote(result['fullpath']) == '/lead2gold@gmail.com')
assert(result['path'] == '/')
assert(unquote(result['query']) == 'lead2gold@gmail.com')
assert(unquote(
result['url']) ==
'mailto://user:password@otherHost/lead2gold@gmail.com')
assert(len(result['qsd']) == 3)
assert('name' in result['qsd'])
assert(unquote(result['qsd']['name']) == 'Chris Caron')
assert('from' in result['qsd'])
assert(unquote(result['qsd']['from']) == 'test@test.com')
assert('format' in result['qsd'])
assert(unquote(result['qsd']['format']) == 'text')
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'mailto'
assert result['host'] == 'otherHost'
assert result['port'] is None
assert result['user'] == 'user'
assert result['password'] == 'password'
assert unquote(result['fullpath']) == '/lead2gold@gmail.com'
assert result['path'] == '/'
assert unquote(result['query']) == 'lead2gold@gmail.com'
assert unquote(result['url']) == \
'mailto://user:password@otherHost/lead2gold@gmail.com'
assert len(result['qsd']) == 3
assert 'name' in result['qsd']
assert unquote(result['qsd']['name']) == 'Chris Caron'
assert 'from' in result['qsd']
assert unquote(result['qsd']['from']) == 'test@test.com'
assert 'format' in result['qsd']
assert unquote(result['qsd']['format']) == 'text'
assert result['qsd-'] == {}
assert result['qsd+'] == {}
# Test Passwords with question marks ?; not supported
result = utils.parse_url(
'http://user:pass.with.?question@host'
)
assert(result is None)
assert result is None
# just hostnames
result = utils.parse_url(
'nuxref.com'
)
assert(result['schema'] == 'http')
assert(result['host'] == 'nuxref.com')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] is None)
assert(result['path'] is None)
assert(result['query'] is None)
assert(result['url'] == 'http://nuxref.com')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'nuxref.com'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] is None
assert result['path'] is None
assert result['query'] is None
assert result['url'] == 'http://nuxref.com'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
# just host and path
result = utils.parse_url(
'invalid/host'
)
assert(result['schema'] == 'http')
assert(result['host'] == 'invalid')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] == '/host')
assert(result['path'] == '/')
assert(result['query'] == 'host')
assert(result['url'] == 'http://invalid/host')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
result = utils.parse_url('invalid/host')
assert result['schema'] == 'http'
assert result['host'] == 'invalid'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] == '/host'
assert result['path'] == '/'
assert result['query'] == 'host'
assert result['url'] == 'http://invalid/host'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
# just all out invalid
assert(utils.parse_url('?') is None)
assert(utils.parse_url('/') is None)
assert utils.parse_url('?') is None
assert utils.parse_url('/') is None
# A default port of zero is still considered valid, but
# is removed in the response.
result = utils.parse_url('http://nuxref.com:0')
assert(result['schema'] == 'http')
assert(result['host'] == 'nuxref.com')
assert(result['port'] is None)
assert(result['user'] is None)
assert(result['password'] is None)
assert(result['fullpath'] is None)
assert(result['path'] is None)
assert(result['query'] is None)
assert(result['url'] == 'http://nuxref.com')
assert(result['qsd'] == {})
assert(result['qsd-'] == {})
assert(result['qsd+'] == {})
assert result['schema'] == 'http'
assert result['host'] == 'nuxref.com'
assert result['port'] is None
assert result['user'] is None
assert result['password'] is None
assert result['fullpath'] is None
assert result['path'] is None
assert result['query'] is None
assert result['url'] == 'http://nuxref.com'
assert result['qsd'] == {}
assert result['qsd-'] == {}
assert result['qsd+'] == {}
# Test some illegal strings
result = utils.parse_url(object, verify_host=False)
@ -345,7 +342,7 @@ def test_parse_url():
# Do it again without host validation
result = utils.parse_url('test://', verify_host=False)
assert(result['schema'] == 'test')
assert result['schema'] == 'test'
# It's worth noting that the hostname is an empty string and is NEVER set
# to None if it wasn't specified.
assert result['host'] == ''
@ -423,10 +420,10 @@ def test_parse_url():
assert result['port'] is None
assert result['user'] == ''
assert result['password'] == ''
assert(unquote(result['fullpath']) == '/_/@^&/jack.json')
assert(unquote(result['path']) == '/_/@^&/')
assert unquote(result['fullpath']) == '/_/@^&/jack.json'
assert unquote(result['path']) == '/_/@^&/'
assert result['query'] == 'jack.json'
assert(unquote(result['url']) == 'crazy://:@/_/@^&/jack.json')
assert unquote(result['url']) == 'crazy://:@/_/@^&/jack.json'
assert result['qsd'] == {}
assert result['qsd-'] == {}
@ -434,42 +431,42 @@ def test_parse_url():
def test_parse_bool():
"utils: parse_bool() testing """
assert(utils.parse_bool('Enabled', None) is True)
assert(utils.parse_bool('Disabled', None) is False)
assert(utils.parse_bool('Allow', None) is True)
assert(utils.parse_bool('Deny', None) is False)
assert(utils.parse_bool('Yes', None) is True)
assert(utils.parse_bool('YES', None) is True)
assert(utils.parse_bool('Always', None) is True)
assert(utils.parse_bool('No', None) is False)
assert(utils.parse_bool('NO', None) is False)
assert(utils.parse_bool('NEVER', None) is False)
assert(utils.parse_bool('TrUE', None) is True)
assert(utils.parse_bool('tRUe', None) is True)
assert(utils.parse_bool('FAlse', None) is False)
assert(utils.parse_bool('F', None) is False)
assert(utils.parse_bool('T', None) is True)
assert(utils.parse_bool('0', None) is False)
assert(utils.parse_bool('1', None) is True)
assert(utils.parse_bool('True', None) is True)
assert(utils.parse_bool('Yes', None) is True)
assert(utils.parse_bool(1, None) is True)
assert(utils.parse_bool(0, None) is False)
assert(utils.parse_bool(True, None) is True)
assert(utils.parse_bool(False, None) is False)
assert utils.parse_bool('Enabled', None) is True
assert utils.parse_bool('Disabled', None) is False
assert utils.parse_bool('Allow', None) is True
assert utils.parse_bool('Deny', None) is False
assert utils.parse_bool('Yes', None) is True
assert utils.parse_bool('YES', None) is True
assert utils.parse_bool('Always', None) is True
assert utils.parse_bool('No', None) is False
assert utils.parse_bool('NO', None) is False
assert utils.parse_bool('NEVER', None) is False
assert utils.parse_bool('TrUE', None) is True
assert utils.parse_bool('tRUe', None) is True
assert utils.parse_bool('FAlse', None) is False
assert utils.parse_bool('F', None) is False
assert utils.parse_bool('T', None) is True
assert utils.parse_bool('0', None) is False
assert utils.parse_bool('1', None) is True
assert utils.parse_bool('True', None) is True
assert utils.parse_bool('Yes', None) is True
assert utils.parse_bool(1, None) is True
assert utils.parse_bool(0, None) is False
assert utils.parse_bool(True, None) is True
assert utils.parse_bool(False, None) is False
# only the int of 0 will return False since the function
# casts this to a boolean
assert(utils.parse_bool(2, None) is True)
assert utils.parse_bool(2, None) is True
# An empty list is still false
assert(utils.parse_bool([], None) is False)
assert utils.parse_bool([], None) is False
# But a list that contains something is True
assert(utils.parse_bool(['value', ], None) is True)
assert utils.parse_bool(['value', ], None) is True
# Use Default (which is False)
assert(utils.parse_bool('OhYeah') is False)
assert utils.parse_bool('OhYeah') is False
# Adjust Default and get a different result
assert(utils.parse_bool('OhYeah', True) is True)
assert utils.parse_bool('OhYeah', True) is True
def test_is_hostname():
@ -608,14 +605,15 @@ def test_parse_list():
results = utils.parse_list(
'.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg,.mpeg,.vob,.iso')
assert(results == sorted([
assert results == sorted([
'.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
'.xvid', '.wmv', '.mp4',
]))
])
class StrangeObject(object):
def __str__(self):
return '.avi'
# Now 2 lists with lots of duplicates and other delimiters
results = utils.parse_list(
'.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg .mpeg,.vob,,; ;',
@ -623,10 +621,13 @@ def test_parse_list():
'.vob,.iso', ['.vob', ['.vob', '.mkv', StrangeObject(), ], ],
StrangeObject())
assert(results == sorted([
assert results == sorted([
'.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
'.xvid', '.wmv', '.mp4',
]))
])
# Garbage in is removed
assert utils.parse_list(object(), 42, None) == []
# Now a list with extras we want to add as strings
# empty entries are removed
@ -634,10 +635,10 @@ def test_parse_list():
'.divx', '.iso', '.mkv', '.mov', '', ' ', '.avi', '.mpeg', '.vob',
'.xvid', '.mp4'], '.mov,.wmv,.mp4,.mpg')
assert(results == sorted([
assert results == sorted([
'.divx', '.wmv', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.vob',
'.xvid', '.mpeg', '.mp4',
]))
])
def test_exclusive_match():
@ -735,6 +736,46 @@ def test_exclusive_match():
logic='match_me', data=data, match_all='match_me') is True
def test_apprise_validate_regex(tmpdir):
"""
API: Apprise() Validate Regex tests
"""
assert utils.validate_regex(None) is None
assert utils.validate_regex(object) is None
assert utils.validate_regex(42) is None
assert utils.validate_regex("") is None
assert utils.validate_regex(" ") is None
assert utils.validate_regex("abc") == "abc"
# value is a keyword that is extracted (if found)
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[^-]+)-', fmt="{value}") == "abcd"
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[^-]+)-', strip=False,
fmt="{value}") == " abcd "
# String flags supported in addition to numeric
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[^-]+)-', 'i', fmt="{value}") == "abcd"
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[^-]+)-', re.I, fmt="{value}") == "abcd"
# Test multiple flag settings
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[^-]+)-', 'isax', fmt="{value}") == "abcd"
# Invalid flags are just ignored. The below fails to match
# because the default value of 'i' is over-ridden by what is
# identfied below, and no flag is set at the end of the day
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[ABCD]+)-', '-%2gb', fmt="{value}") is None
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[ABCD]+)-', '', fmt="{value}") is None
assert utils.validate_regex(
"- abcd -", r'-(?P<value>[ABCD]+)-', None, fmt="{value}") is None
def test_environ_temporary_change():
"""utils: environ() testing
"""

View File

@ -113,37 +113,41 @@ def test_windows_plugin():
obj.duration = 0
# Test URL functionality
assert(isinstance(obj.url(), six.string_types) is True)
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)
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)
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)
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)
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) 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
# loads okay
assert obj.duration == 1
@ -165,20 +169,23 @@ def test_windows_plugin():
# 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)
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)
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)
assert obj.notify(
title='title', body='body',
notify_type=apprise.NotifyType.INFO) is False