more unit tests; 90% coverage

pull/5/head
Chris Caron 2017-12-22 22:47:46 -05:00
parent f8c3d35f8c
commit cc79763b3f
14 changed files with 967 additions and 274 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -41,12 +41,14 @@ class NotifyImageSize(object):
A list of pre-defined image sizes to make it easier to work with defined
plugins.
"""
XY_32 = '32x32'
XY_72 = '72x72'
XY_128 = '128x128'
XY_256 = '256x256'
NOTIFY_IMAGE_SIZES = (
NotifyImageSize.XY_32,
NotifyImageSize.XY_72,
NotifyImageSize.XY_128,
NotifyImageSize.XY_256,

View File

@ -183,13 +183,9 @@ class NotifyMyAndroid(NotifyBase):
if 'format' in results['qsd'] and len(results['qsd']['format']):
# Extract email format (Text/Html)
try:
format = NotifyBase.unquote(results['qsd']['format']).lower()
if len(format) > 0 and format[0] == 't':
results['notify_format'] = NotifyFormat.TEXT
except AttributeError:
pass
format = NotifyBase.unquote(results['qsd']['format']).lower()
if len(format) > 0 and format[0] == 't':
results['notify_format'] = NotifyFormat.TEXT
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {

View File

@ -31,7 +31,7 @@ VALIDATE_PROVIDERKEY = re.compile(r'[A-Za-z0-9]{40}')
# Priorities
class ProwlPriority(object):
VERY_LOW = -2
LOW = -2
MODERATE = -1
NORMAL = 0
HIGH = 1
@ -39,7 +39,7 @@ class ProwlPriority(object):
PROWL_PRIORITIES = (
ProwlPriority.VERY_LOW,
ProwlPriority.LOW,
ProwlPriority.MODERATE,
ProwlPriority.NORMAL,
ProwlPriority.HIGH,
@ -65,8 +65,7 @@ class NotifyProwl(NotifyBase):
# Prowl uses the http protocol with JSON requests
notify_url = 'https://api.prowlapp.com/publicapi/add'
def __init__(self, apikey, providerkey=None, priority=ProwlPriority.NORMAL,
**kwargs):
def __init__(self, apikey, providerkey=None, priority=None, **kwargs):
"""
Initialize Prowl Object
"""
@ -146,7 +145,7 @@ class NotifyProwl(NotifyBase):
PROWL_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Prowl notification '
'(error=%s).' % (
@ -159,7 +158,7 @@ class NotifyProwl(NotifyBase):
else:
self.logger.info('Sent Prowl notification.')
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Prowl notification.')
self.logger.debug('Socket Exception: %s' % str(e))
@ -186,15 +185,33 @@ class NotifyProwl(NotifyBase):
# optionally find the provider key
try:
providerkey = filter(
bool, NotifyBase.split_path(results['fullpath']))[0]
if not providerkey:
providerkey = None
providerkey = [x for x in filter(
bool, NotifyBase.split_path(results['fullpath']))][0]
except (AttributeError, IndexError):
providerkey = None
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': ProwlPriority.LOW,
'-2': ProwlPriority.LOW,
'm': ProwlPriority.MODERATE,
'-1': ProwlPriority.MODERATE,
'n': ProwlPriority.NORMAL,
'0': ProwlPriority.NORMAL,
'h': ProwlPriority.HIGH,
'1': ProwlPriority.HIGH,
'e': ProwlPriority.EMERGENCY,
'2': ProwlPriority.EMERGENCY,
}
try:
results['priority'] = \
_map[results['qsd']['priority'][0].lower()]
except KeyError:
# No priority was set
pass
results['apikey'] = results['host']
results['providerkey'] = providerkey

View File

@ -60,11 +60,10 @@ class NotifyPushBullet(NotifyBase):
self.accesstoken = accesstoken
if compat_is_basestring(recipients):
self.recipients = filter(bool, RECIPIENTS_LIST_DELIM.split(
recipients,
))
self.recipients = [x for x in filter(
bool, RECIPIENTS_LIST_DELIM.split(recipients))]
elif isinstance(recipients, (tuple, list)):
elif isinstance(recipients, (set, tuple, list)):
self.recipients = recipients
else:
@ -138,7 +137,7 @@ class NotifyPushBullet(NotifyBase):
PUSHBULLET_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send PushBullet notification '
'(error=%s).' % r.status_code)
@ -148,7 +147,7 @@ class NotifyPushBullet(NotifyBase):
# Return; we're done
has_error = True
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending PushBullet '
'notification.'
@ -176,11 +175,7 @@ class NotifyPushBullet(NotifyBase):
return results
# Apply our settings now
try:
recipients = NotifyBase.unquote(results['fullpath'])
except AttributeError:
recipients = ''
recipients = NotifyBase.unquote(results['fullpath'])
results['accesstoken'] = results['host']
results['recipients'] = recipients

View File

@ -43,10 +43,7 @@ class NotifyPushalot(NotifyBase):
A wrapper for Pushalot Notifications
"""
# The default protocol
protocol = 'palot'
# The default secure protocol
# The default protocol is always secured
secure_protocol = 'palot'
# Pushalot uses the http protocol with JSON requests
@ -117,7 +114,7 @@ class NotifyPushalot(NotifyBase):
PUSHALOT_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Pushalot notification '
'(error=%s).' % r.status_code)
@ -128,7 +125,7 @@ class NotifyPushalot(NotifyBase):
else:
self.logger.info('Sent Pushalot notification.')
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Pushalot notification.')
self.logger.debug('Socket Exception: %s' % str(e))

View File

@ -27,18 +27,18 @@ from .NotifyBase import HTTP_ERROR_MAP
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
# Used to validate API Key
VALIDATE_TOKEN = re.compile(r'[A-Za-z0-9]{30}')
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{30}$', re.I)
# Used to detect a User and/or Group
VALIDATE_USERGROUP = re.compile(r'[A-Za-z0-9]{30}')
VALIDATE_USERGROUP = re.compile(r'^[a-z0-9]{30}$', re.I)
# Used to detect a User and/or Group
VALIDATE_DEVICE = re.compile(r'[A-Za-z0-9_]{1,25}')
VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I)
# Priorities
class PushoverPriority(object):
VERY_LOW = -2
LOW = -2
MODERATE = -1
NORMAL = 0
HIGH = 1
@ -46,7 +46,7 @@ class PushoverPriority(object):
PUSHOVER_PRIORITIES = (
PushoverPriority.VERY_LOW,
PushoverPriority.LOW,
PushoverPriority.MODERATE,
PushoverPriority.NORMAL,
PushoverPriority.HIGH,
@ -68,24 +68,29 @@ class NotifyPushover(NotifyBase):
A wrapper for Pushover Notifications
"""
# The default protocol
protocol = 'pover'
# The default secure protocol
# All pushover requests are secure
secure_protocol = 'pover'
# Pushover uses the http protocol with JSON requests
notify_url = 'https://api.pushover.net/1/messages.json'
def __init__(self, token, devices=None,
priority=PushoverPriority.NORMAL, **kwargs):
def __init__(self, token, devices=None, priority=None, **kwargs):
"""
Initialize Pushover Object
"""
super(NotifyPushover, self).__init__(
title_maxlen=250, body_maxlen=512, **kwargs)
if not VALIDATE_TOKEN.match(token.strip()):
try:
# The token associated with the account
self.token = token.strip()
except AttributeError:
# Token was None
self.logger.warning('No API Token was specified.')
raise TypeError('No API Token was specified.')
if not VALIDATE_TOKEN.match(self.token):
self.logger.warning(
'The API Token specified (%s) is invalid.' % token,
)
@ -93,13 +98,10 @@ class NotifyPushover(NotifyBase):
'The API Token specified (%s) is invalid.' % token,
)
# The token associated with the account
self.token = token.strip()
if compat_is_basestring(devices):
self.devices = filter(bool, DEVICE_LIST_DELIM.split(
self.devices = [x for x in filter(bool, DEVICE_LIST_DELIM.split(
devices,
))
))]
elif isinstance(devices, (set, tuple, list)):
self.devices = devices
@ -121,10 +123,6 @@ class NotifyPushover(NotifyBase):
self.logger.warning('No user was specified.')
raise TypeError('No user was specified.')
if not self.token:
self.logger.warning('No token was specified.')
raise TypeError('No token was specified.')
if not VALIDATE_USERGROUP.match(self.user):
self.logger.warning(
'The user/group specified (%s) is invalid.' % self.user,
@ -152,6 +150,13 @@ class NotifyPushover(NotifyBase):
while len(devices):
device = devices.pop(0)
if VALIDATE_DEVICE.match(device) is None:
self.logger.warning(
'The device specified (%s) is invalid.' % device,
)
has_error = True
continue
# prepare JSON Object
payload = {
'token': self.token,
@ -159,18 +164,9 @@ class NotifyPushover(NotifyBase):
'priority': str(self.priority),
'title': title,
'message': body,
'device': device,
}
if device != PUSHOVER_SEND_TO_ALL:
if not VALIDATE_DEVICE.match(device):
self.logger.warning(
'The device specified (%s) is invalid.' % device,
)
has_error = True
continue
payload['device'] = device
self.logger.debug('Pushover POST URL: %s (cert_verify=%r)' % (
self.notify_url, self.verify_certificate,
))
@ -193,7 +189,7 @@ class NotifyPushover(NotifyBase):
PUSHOVER_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Pushover:%s '
'notification (error=%s).' % (
@ -205,7 +201,7 @@ class NotifyPushover(NotifyBase):
# Return; we're done
has_error = True
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Pushover:%s ' % (
device) + 'notification.'
@ -217,7 +213,7 @@ class NotifyPushover(NotifyBase):
# Prevent thrashing requests
self.throttle()
return has_error
return not has_error
@staticmethod
def parse_url(url):
@ -233,11 +229,28 @@ class NotifyPushover(NotifyBase):
return results
# Apply our settings now
try:
devices = NotifyBase.unquote(results['fullpath'])
devices = NotifyBase.unquote(results['fullpath'])
except AttributeError:
devices = ''
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': PushoverPriority.LOW,
'-2': PushoverPriority.LOW,
'm': PushoverPriority.MODERATE,
'-1': PushoverPriority.MODERATE,
'n': PushoverPriority.NORMAL,
'0': PushoverPriority.NORMAL,
'h': PushoverPriority.HIGH,
'1': PushoverPriority.HIGH,
'e': PushoverPriority.EMERGENCY,
'2': PushoverPriority.EMERGENCY,
}
try:
results['priority'] = \
_map[results['qsd']['priority'][0].lower()]
except KeyError:
# No priority was set
pass
results['token'] = results['host']
results['devices'] = devices

View File

@ -173,7 +173,7 @@ class NotifyRocketChat(NotifyBase):
'%s (error=%s).' % (
RC_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Rocket.Chat notification ' +
'(error=%s).' % (
@ -186,7 +186,7 @@ class NotifyRocketChat(NotifyBase):
self.logger.debug('Rocket.Chat Server Response: %s.' % r.text)
self.logger.info('Sent Rocket.Chat notification.')
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Rocket.Chat ' +
'notification.')
@ -277,6 +277,7 @@ class NotifyRocketChat(NotifyBase):
'%s (error=%s).' % (
RC_HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
self.logger.warning(
'Failed to log off Rocket.Chat server ' +
@ -316,10 +317,6 @@ class NotifyRocketChat(NotifyBase):
return results
# Apply our settings now
try:
results['recipients'] = NotifyBase.unquote(results['fullpath'])
except AttributeError:
return None
results['recipients'] = NotifyBase.unquote(results['fullpath'])
return results

View File

@ -49,13 +49,13 @@ from json import dumps
from .NotifyBase import NotifyBase
from .NotifyBase import NotifyFormat
from .NotifyBase import HTTP_ERROR_MAP
from ..common import NotifyImageSize
from ..utils import compat_is_basestring
# 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-Za-z0-9_-]+)/*$',
r'^(bot)?(?P<key>[0-9]+:[a-z0-9_-]+)/*$',
re.IGNORECASE,
)
@ -63,7 +63,7 @@ VALIDATE_BOT_TOKEN = re.compile(
# 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
IS_CHAT_ID_RE = re.compile(
r'(@*(?P<idno>-?[0-9]{1,32})|(?P<name>[a-z_-][a-z0-9_-]*))',
r'^(@*(?P<idno>-?[0-9]{1,32})|(?P<name>[a-z_-][a-z0-9_-]+))$',
re.IGNORECASE,
)
@ -71,7 +71,7 @@ IS_CHAT_ID_RE = re.compile(
# The stickers/images are kind of big and consume a lot of space
# It's not as appealing as just having the post not contain
# an image at all.
TELEGRAM_IMAGE_XY = None
TELEGRAM_IMAGE_XY = NotifyImageSize.XY_32
# Used to break path apart into list of chat identifiers
CHAT_ID_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
@ -88,32 +88,37 @@ class NotifyTelegram(NotifyBase):
# Telegram uses the http protocol with JSON requests
notify_url = 'https://api.telegram.org/bot'
def __init__(self, bot_token, chat_ids, **kwargs):
def __init__(self, bot_token, chat_ids, notify_format=NotifyFormat.HTML,
**kwargs):
"""
Initialize Telegram Object
"""
super(NotifyTelegram, self).__init__(
title_maxlen=250, body_maxlen=4096,
image_size=TELEGRAM_IMAGE_XY, **kwargs)
image_size=TELEGRAM_IMAGE_XY, notify_format=notify_format,
**kwargs)
if bot_token is None:
raise TypeError(
'The Bot Token specified is invalid.'
)
try:
self.bot_token = bot_token.strip()
result = VALIDATE_BOT_TOKEN.match(bot_token.strip())
except AttributeError:
# Token was None
self.logger.warning('No Bot Token was specified.')
raise TypeError('No Bot Token was specified.')
result = VALIDATE_BOT_TOKEN.match(self.bot_token)
if not result:
raise TypeError(
'The Bot Token specified (%s) is invalid.' % bot_token,
)
# Store our API Key
# Store our Bot Token
self.bot_token = result.group('key')
if compat_is_basestring(chat_ids):
self.chat_ids = filter(bool, CHAT_ID_LIST_DELIM.split(
self.chat_ids = [x for x in filter(bool, CHAT_ID_LIST_DELIM.split(
chat_ids,
))
))]
elif isinstance(chat_ids, (set, tuple, list)):
self.chat_ids = list(chat_ids)
@ -125,51 +130,54 @@ class NotifyTelegram(NotifyBase):
# Treat this as a channel too
self.chat_ids.append(self.user)
# Bot's can't send messages to themselves which is fair enough
# but if or when they can, this code will allow a default fallback
# solution if no chat_id and/or channel is specified
# if len(self.chat_ids) == 0:
#
# chat_id = self._get_chat_id()
# if chat_id is not None:
# self.logger.warning(
# 'No chat_id or @channel was specified; ' +\
# 'using detected bot_chat_id (%d).' % chat_id,
# )
# self.chat_ids.append(str(chat_id))
if len(self.chat_ids) == 0:
self.logger.warning('No chat_id(s) were specified.')
raise TypeError('No chat_id(s) were specified.')
def _get_chat_id(self):
def notify_image(self, chat_id, notify_type, **kwargs):
"""
This function retrieves the chat id belonging to the key specified
"""
headers = {
'User-Agent': self.app_id,
'Content-Type': 'application/json',
}
Sends the notification image based on the specified chat id
"""
image_content = self.image_raw(notify_type)
if image_content is None:
# Nothing to do
return True
# prepare our image URL
url = '%s%s/%s' % (
self.notify_url,
self.bot_token,
'getMe'
'sendPhoto'
)
self.logger.debug('Telegram (Detection) GET URL: %s' % url)
# Set up our upload
files = {'photo': ('%s.png' % notify_type, image_content)}
payload = {
'chat_id': chat_id,
'disable_notification': True,
}
self.logger.debug(
'Telegram (image) POST URL: %s (cert_verify=%r)' % (
url, self.verify_certificate))
self.logger.debug(
'Telegram (image) Payload: %s' % str(payload))
chat_id = None
try:
r = requests.post(url, headers=headers)
if r.status_code == requests.codes.ok:
# Extract our chat ID
result = loads(r.text)
if result.get('ok', False) is True:
chat_id = result['result'].get('id')
if chat_id <= 0:
chat_id = None
else:
r = requests.post(
url,
data=payload,
headers={
'User-Agent': self.app_id,
},
files=files,
verify=self.verify_certificate,
)
if r.status_code != requests.codes.ok:
# We had a problem
try:
# Try to get the error message if we can:
@ -181,28 +189,38 @@ class NotifyTelegram(NotifyBase):
try:
if error_msg:
self.logger.warning(
'Failed to lookup Telegram chat_id from '
'apikey: (%s) %s.' % (r.status_code, error_msg))
'Failed to send Telegram Image:%s '
'notification: (%s) %s.' % (
payload['chat_id'],
r.status_code, error_msg))
else:
self.logger.warning(
'Failed to lookup Telegram chat_id from '
'apikey: %s (error=%s).' % (
'Failed to send Telegram Image:%s '
'notification: %s (error=%s).' % (
payload['chat_id'],
HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to lookup Telegram chat_id from '
'apikey: (error=%s).' % r.status_code)
'Failed to send Telegram Image:%s '
'notification (error=%s).' % (
payload['chat_id'],
r.status_code))
except requests.ConnectionError as e:
return False
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured looking up Telegram chat_id '
'from apikey.')
'A Connection error occured sending Telegram:%s ' % (
payload['chat_id']) + 'notification.'
)
self.logger.debug('Socket Exception: %s' % str(e))
return False
return chat_id
# We were successful
return True
def notify(self, title, body, notify_type, **kwargs):
"""
@ -217,19 +235,6 @@ class NotifyTelegram(NotifyBase):
# error tracking (used for function return)
has_error = False
image_url = None
image_content = self.image_raw(notify_type)
if image_content is not None:
# prepare our image URL
image_url = '%s%s/%s' % (
self.notify_url,
self.bot_token,
'sendPhoto'
)
# Set up our upload
files = {'photo': ('%s.png' % notify_type, image_content)}
url = '%s%s/%s' % (
self.notify_url,
self.bot_token,
@ -263,6 +268,7 @@ class NotifyTelegram(NotifyBase):
chat_id,
)
)
has_error = True
continue
if chat_id.group('name') is not None:
@ -273,72 +279,19 @@ class NotifyTelegram(NotifyBase):
# ID
payload['chat_id'] = chat_id.group('idno')
if image_url is not None:
image_payload = {
'chat_id': payload['chat_id'],
'disable_notification': True,
}
if not self.notify_image(
chat_id=payload['chat_id'], notify_type=notify_type):
# Uh oh... The image failed to post if we get here
self.logger.debug(
'Telegram (image) POST URL: %s (cert_verify=%r)' % (
image_url, self.verify_certificate))
if len(chat_ids) > 0:
# Prevent thrashing requests
self.throttle()
self.logger.debug(
'Telegram (image) Payload: %s' % str(image_payload))
# Flag our error
has_error = True
try:
r = requests.post(
image_url,
data=image_payload,
headers={
'User-Agent': self.app_id,
},
files=files,
verify=self.verify_certificate,
)
if r.status_code != requests.codes.ok:
# We had a problem
try:
# Try to get the error message if we can:
error_msg = loads(r.text)['description']
except:
error_msg = None
try:
if error_msg:
self.logger.warning(
'Failed to send Telegram Image:%s '
'notification: (%s) %s.' % (
payload['chat_id'],
r.status_code, error_msg))
else:
self.logger.warning(
'Failed to send Telegram Image:%s '
'notification: %s (error=%s).' % (
payload['chat_id'],
HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
self.logger.warning(
'Failed to send Telegram Image:%s '
'notification (error=%s).' % (
payload['chat_id'],
r.status_code))
has_error = True
continue
except requests.ConnectionError as e:
self.logger.warning(
'A Connection error occured sending Telegram:%s ' % (
payload['chat_id']) + 'notification.'
)
self.logger.debug('Socket Exception: %s' % str(e))
has_error = True
continue
# Move along
continue
self.logger.debug('Telegram POST URL: %s' % url)
self.logger.debug('Telegram POST URL: %s (cert_verify=%r)' % (
@ -353,12 +306,14 @@ class NotifyTelegram(NotifyBase):
headers=headers,
verify=self.verify_certificate,
)
if r.status_code != requests.codes.ok:
# We had a problem
try:
# Try to get the error message if we can:
error_msg = loads(r.text)['description']
except:
error_msg = None
@ -378,7 +333,7 @@ class NotifyTelegram(NotifyBase):
HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Telegram:%s '
'notification (error=%s).' % (
@ -386,10 +341,10 @@ class NotifyTelegram(NotifyBase):
# self.logger.debug('Response Details: %s' % r.raw.read())
# Return; we're done
# Flag our error
has_error = True
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Telegram:%s ' % (
payload['chat_id']) + 'notification.'
@ -397,11 +352,12 @@ class NotifyTelegram(NotifyBase):
self.logger.debug('Socket Exception: %s' % str(e))
has_error = True
if len(chat_ids):
# Prevent thrashing requests
self.throttle()
finally:
if len(chat_ids):
# Prevent thrashing requests
self.throttle()
return has_error
return not has_error
@staticmethod
def parse_url(url):
@ -410,14 +366,6 @@ class NotifyTelegram(NotifyBase):
us to substantiate this object.
"""
# super() is formatted slightly different when dealing with
# static method inheritance
results = NotifyBase.parse_url(url)
if results:
# We're done early
return results
# This is a dirty hack; but it's the only work around to
# tgram:// messages since the bot_token has a colon in it.
# It invalidates an normal URL.
@ -427,11 +375,16 @@ class NotifyTelegram(NotifyBase):
# alternative is to ask users to actually change the colon
# into a slash (which will work too), but it's more likely
# to cause confusion... So this is the next best thing
tgram = re.match(
r'(?P<protocol>%s://)(bot)?(?P<prefix>([a-z0-9_-]+)'
r'(:[a-z0-9_-]+)?@)?(?P<btoken_a>[0-9]+):+'
r'(?P<remaining>.*)$' % 'tgram',
url, re.I)
try:
tgram = re.match(
r'(?P<protocol>%s://)(bot)?(?P<prefix>([a-z0-9_-]+)'
r'(:[a-z0-9_-]+)?@)?(?P<btoken_a>[0-9]+):+'
r'(?P<remaining>.*)$' % NotifyTelegram.secure_protocol,
url, re.I)
except (TypeError, AttributeError):
# url is bad; force tgram to be None
tgram = None
if not tgram:
# Content is simply not parseable
@ -439,7 +392,7 @@ class NotifyTelegram(NotifyBase):
if tgram.group('prefix'):
# Try again
result = NotifyBase.parse_url(
results = NotifyBase.parse_url(
'%s%s%s/%s' % (
tgram.group('protocol'),
tgram.group('prefix'),
@ -450,7 +403,7 @@ class NotifyTelegram(NotifyBase):
else:
# Try again
result = NotifyBase.parse_url(
results = NotifyBase.parse_url(
'%s%s/%s' % (
tgram.group('protocol'),
tgram.group('btoken_a'),
@ -459,30 +412,22 @@ class NotifyTelegram(NotifyBase):
)
# The first token is stored in the hostnamee
bot_token_a = result['host']
bot_token_a = results['host']
# Now fetch the remaining tokens
try:
bot_token_b = filter(
bool, NotifyBase.split_path(result['fullpath']))[0]
bot_token_b = [x for x in filter(
bool, NotifyBase.split_path(results['fullpath']))][0]
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
except (AttributeError, IndexError):
# Force a bad value that will get caught in parsing later
bot_token = None
chat_ids = ','.join(
[x for x in filter(
bool, NotifyBase.split_path(results['fullpath']))][1:])
try:
chat_ids = ','.join(
filter(bool, NotifyBase.split_path(result['fullpath']))[1:])
# Store our bot token
results['bot_token'] = bot_token
except (AttributeError, IndexError):
# Force some bad values that will get caught
# in parsing later
chat_ids = None
# Store our chat ids
results['chat_ids'] = chat_ids
# Return our results
return result + {
'bot_token': bot_token,
'chat_ids': chat_ids,
}.items()
return results

View File

@ -52,14 +52,17 @@ class NotifyToasty(NotifyBase):
**kwargs)
if compat_is_basestring(devices):
self.devices = filter(bool, DEVICES_LIST_DELIM.split(
self.devices = [x for x in filter(bool, DEVICES_LIST_DELIM.split(
devices,
))
))]
elif isinstance(devices, (tuple, list)):
elif isinstance(devices, (set, tuple, list)):
self.devices = devices
else:
self.devices = list()
if len(devices) == 0:
raise TypeError('You must specify at least 1 device.')
if not self.user:
@ -118,7 +121,7 @@ class NotifyToasty(NotifyBase):
HTTP_ERROR_MAP[r.status_code],
r.status_code))
except IndexError:
except KeyError:
self.logger.warning(
'Failed to send Toasty:%s '
'notification (error=%s).' % (
@ -130,7 +133,7 @@ class NotifyToasty(NotifyBase):
# Return; we're done
has_error = True
except requests.ConnectionError as e:
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Toasty:%s ' % (
device) + 'notification.'
@ -142,7 +145,7 @@ class NotifyToasty(NotifyBase):
# Prevent thrashing requests
self.throttle()
return has_error
return not has_error
@staticmethod
def parse_url(url):
@ -158,11 +161,7 @@ class NotifyToasty(NotifyBase):
return results
# Apply our settings now
try:
devices = NotifyBase.unquote(results['fullpath'])
except AttributeError:
devices = ''
devices = NotifyBase.unquote(results['fullpath'])
# Store our devices
results['devices'] = '%s/%s' % (results['host'], devices)

View File

@ -20,10 +20,10 @@ from apprise import plugins
from apprise import NotifyType
from apprise import Apprise
from apprise import AppriseAsset
from json import dumps
import requests
import mock
TEST_URLS = (
##################################
# NotifyBoxcar
@ -176,7 +176,6 @@ TEST_URLS = (
'response': False,
'requests_response_code': 999,
}),
# apikey = a
('join://%s' % ('a' * 32), {
'instance': plugins.NotifyJoin,
# Throws a series of connection and transfer exceptions when this flag
@ -380,8 +379,6 @@ TEST_URLS = (
}),
# Invalid APIKey
('nma://%s' % ('a' * 24), {
'instance': None,
# Missing a channel
'exception': TypeError,
}),
# APIKey
@ -390,6 +387,42 @@ TEST_URLS = (
# don't include an image by default
'include_image': False,
}),
# APIKey + priority setting
('nma://%s?priority=high' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# APIKey + invalid priority setting
('nma://%s?priority=invalid' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# APIKey + priority setting (empty)
('nma://%s?priority=' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# APIKey + Invalid DevAPI Key
('nma://%s/%s' % ('a' * 48, 'b' * 24), {
'exception': TypeError,
}),
# APIKey + DevAPI Key
('nma://%s/%s' % ('a' * 48, 'b' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# Testing valid format
('nma://%s?format=text' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# Testing valid format
('nma://%s?format=html' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# Testing invalid format (fall's back to html)
('nma://%s?format=invalid' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# Testing empty format (falls back to html)
('nma://%s?format=' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
}),
# APIKey + with image
('nma://%s' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
@ -410,7 +443,6 @@ TEST_URLS = (
'response': False,
'requests_response_code': 999,
}),
# apikey = a
('nma://%s' % ('a' * 48), {
'instance': plugins.NotifyMyAndroid,
# Throws a series of connection and transfer exceptions when this flag
@ -418,6 +450,262 @@ TEST_URLS = (
'test_requests_exceptions': True,
}),
##################################
# NotifyProwl
##################################
('prowl://', {
'instance': None,
}),
# APIkey; no device
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# Invalid APIKey
('prowl://%s' % ('a' * 24), {
'exception': TypeError,
}),
# APIKey
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
# don't include an image by default
'include_image': False,
}),
# APIKey + priority setting
('prowl://%s?priority=high' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# APIKey + invalid priority setting
('prowl://%s?priority=invalid' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# APIKey + priority setting (empty)
('prowl://%s?priority=' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# APIKey + Invalid Provider Key
('prowl://%s/%s' % ('a' * 40, 'b' * 24), {
'exception': TypeError,
}),
# APIKey + No Provider Key (empty)
('prowl://%s///' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# APIKey + Provider Key
('prowl://%s/%s' % ('a' * 40, 'b' * 40), {
'instance': plugins.NotifyProwl,
}),
# APIKey + with image
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
}),
# bad url
('prowl://:@/', {
'instance': None,
}),
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('prowl://%s' % ('a' * 40), {
'instance': plugins.NotifyProwl,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
##################################
# NotifyPushalot
##################################
('palot://', {
'instance': None,
}),
# AuthToken
('palot://%s' % ('a' * 32), {
'instance': plugins.NotifyPushalot,
}),
# AuthToken, no image
('palot://%s' % ('a' * 32), {
'instance': plugins.NotifyPushalot,
# don't include an image by default
'include_image': False,
}),
# Invalid AuthToken
('palot://%s' % ('a' * 24), {
'instance': None,
# Missing a channel
'exception': TypeError,
}),
# AuthToken + bad url
('palot://:@/', {
'instance': None,
}),
('palot://%s' % ('a' * 32), {
'instance': plugins.NotifyPushalot,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('palot://%s' % ('a' * 32), {
'instance': plugins.NotifyPushalot,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('palot://%s' % ('a' * 32), {
'instance': plugins.NotifyPushalot,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
##################################
# NotifyPushBullet
##################################
('pbul://', {
'instance': None,
}),
# APIkey
('pbul://%s' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + channel
('pbul://%s/#channel/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + 2 channels
('pbul://%s/#channel1/#channel2' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + device
('pbul://%s/device/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + 2 devices
('pbul://%s/device1/device2/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + email
('pbul://%s/user@example.com/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + 2 emails
('pbul://%s/user@example.com/abc@def.com/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + Combo
('pbul://%s/device/#channel/user@example.com/' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
}),
# APIKey + bad url
('pbul://:@/', {
'instance': None,
}),
('pbul://%s' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('pbul://%s' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('pbul://%s' % ('a' * 32), {
'instance': plugins.NotifyPushBullet,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
##################################
# NotifyPushover
##################################
('pover://', {
'instance': None,
}),
# APIkey; no user
('pover://%s' % ('a' * 30), {
'exception': TypeError,
}),
# APIkey; invalid user
('pover://%s@%s' % ('u' * 20, 'a' * 30), {
'exception': TypeError,
}),
# Invalid APIKey; valid User
('pover://%s@%s' % ('u' * 30, 'a' * 24), {
'exception': TypeError,
}),
# APIKey + Valid User
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
# don't include an image by default
'include_image': False,
}),
# APIKey + Valid User + 1 Device
('pover://%s@%s/DEVICE' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
}),
# APIKey + Valid User + 2 Devices
('pover://%s@%s/DEVICE1/DEVICE2/' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
}),
# APIKey + Valid User + invalid device
('pover://%s@%s/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), {
'instance': plugins.NotifyPushover,
# Notify will return False since there is a bad device in our list
'response': False,
}),
# APIKey + Valid User + device + invalid device
('pover://%s@%s/DEVICE1/%s/' % ('u' * 30, 'a' * 30, 'd' * 30), {
'instance': plugins.NotifyPushover,
# Notify will return False since there is a bad device in our list
'response': False,
}),
# APIKey + priority setting
('pover://%s@%s?priority=high' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
}),
# APIKey + invalid priority setting
('pover://%s@%s?priority=invalid' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
}),
# APIKey + priority setting (empty)
('pover://%s@%s?priority=' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
}),
# bad url
('pover://:@/', {
'instance': None,
}),
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
'instance': plugins.NotifyPushover,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
##################################
# NotifySlack
##################################
@ -486,6 +774,173 @@ TEST_URLS = (
'test_requests_exceptions': True,
}),
##################################
# NotifyTelegram
##################################
('tgram://', {
'instance': None,
}),
# Simple Message
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
}),
# Simple Message (no images)
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# don't include an image by default
'include_image': False,
}),
# Simple Message with multiple chat names
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
'instance': plugins.NotifyTelegram,
}),
# Simple Message with an invalid chat ID
('tgram://123456789:abcdefg_hijklmnop/%$/', {
'instance': plugins.NotifyTelegram,
# Notify will fail
'response': False,
}),
# Simple Message with multiple chat ids
('tgram://123456789:abcdefg_hijklmnop/id1/id2/23423/-30/', {
'instance': plugins.NotifyTelegram,
}),
# Simple Message with multiple chat ids (no images)
('tgram://123456789:abcdefg_hijklmnop/id1/id2/23423/-30/', {
'instance': plugins.NotifyTelegram,
# don't include an image by default
'include_image': False,
}),
# Support bot keyword prefix
('tgram://bottest@123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
}),
# Testing valid format
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=text', {
'instance': plugins.NotifyTelegram,
}),
# Testing valid format
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=html', {
'instance': plugins.NotifyTelegram,
}),
# Testing invalid format (fall's back to text)
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=invalid', {
'instance': plugins.NotifyTelegram,
}),
# Testing empty format (falls back to text)
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=', {
'instance': plugins.NotifyTelegram,
}),
# Simple Message without image
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# don't include an image by default
'include_image': False,
}),
# Invalid Bot Token
('tgram://alpha:abcdefg_hijklmnop/lead2gold/', {
'instance': None,
}),
# AuthToken + bad url
('tgram://:@/', {
'instance': None,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# force a failure without an image specified
'include_image': False,
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
'instance': plugins.NotifyTelegram,
# force a failure with multiple chat_ids
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('tgram://123456789:abcdefg_hijklmnop/id1/id2/', {
'instance': plugins.NotifyTelegram,
# force a failure without an image specified
'include_image': False,
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# throw a bizzare code forcing us to fail to look it up without
# having an image included
'include_image': False,
'response': False,
'requests_response_code': 999,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
'instance': plugins.NotifyTelegram,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them without images set
'include_image': False,
'test_requests_exceptions': True,
}),
##################################
# NotifyToasty (SuperToasty)
##################################
('toasty://', {
'instance': None,
}),
# No username specified but contains a device
('toasty://%s' % ('d' * 32), {
'exception': TypeError,
}),
# User + 1 device
('toasty://user@device', {
'instance': plugins.NotifyToasty,
}),
# User + 3 devices
('toasty://user@device0/device1/device2/', {
'instance': plugins.NotifyToasty,
# don't include an image by default
'include_image': False,
}),
# bad url
('toasty://:@/', {
'instance': None,
}),
('toasty://user@device', {
'instance': plugins.NotifyToasty,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('toasty://user@device', {
'instance': plugins.NotifyToasty,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('toasty://user@device', {
'instance': plugins.NotifyToasty,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
##################################
# NotifyKODI
##################################
@ -696,6 +1151,9 @@ def test_rest_plugins(mock_post, mock_get):
assert(isinstance(obj, instance))
# Disable throttling to speed up unit tests
obj.throttle_attempt = 0
if self:
# Iterate over our expected entries inside of our object
for key, val in self.items():
@ -711,9 +1169,9 @@ def test_rest_plugins(mock_post, mock_get):
notify_type=notify_type) == response
else:
for exception in test_requests_exceptions:
mock_post.side_effect = exception
mock_get.side_effect = exception
for _exception in test_requests_exceptions:
mock_post.side_effect = _exception
mock_get.side_effect = _exception
try:
assert obj.notify(
title='test', body='body',
@ -805,6 +1263,10 @@ def test_notify_boxcar_plugin(mock_post, mock_get):
# Test notifications without a body or a title
p = plugins.NotifyBoxcar(access=access, secret=secret, recipients=None)
# Disable throttling to speed up unit tests
p.throttle_attempt = 0
p.notify(body=None, title=None, notify_type=NotifyType.INFO) is True
@ -835,6 +1297,9 @@ def test_notify_join_plugin(mock_post, mock_get):
mock_post.return_value.status_code = requests.codes.created
mock_get.return_value.status_code = requests.codes.created
# Disable throttling to speed up unit tests
p.throttle_attempt = 0
# Test notifications without a body or a title; nothing to send
# so we return False
p.notify(body=None, title=None, notify_type=NotifyType.INFO) is False
@ -859,6 +1324,8 @@ def test_notify_slack_plugin(mock_post, mock_get):
obj = plugins.NotifySlack(
token_a=token_a, token_b=token_b, token_c=token_c, channels=channels)
assert(len(obj.channels) == 4)
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok
@ -880,6 +1347,271 @@ def test_notify_slack_plugin(mock_post, mock_get):
token_a=token_a, token_b=token_b, token_c=token_c, channels=channels,
include_image=True)
# Disable throttling to speed up unit tests
obj.throttle_attempt = 0
# This call includes an image with it's payload:
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
@mock.patch('requests.get')
@mock.patch('requests.post')
def test_notify_pushbullet_plugin(mock_post, mock_get):
"""
API: NotifyPushBullet() Extra Checks
"""
# Initialize some generic (but valid) tokens
accesstoken = 'a' * 32
# Support strings
recipients = '#chan1,#chan2,device,user@example.com,,,'
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok
mock_get.return_value.status_code = requests.codes.ok
obj = plugins.NotifyPushBullet(
accesstoken=accesstoken, recipients=recipients)
assert(isinstance(obj, plugins.NotifyPushBullet))
assert(len(obj.recipients) == 4)
obj = plugins.NotifyPushBullet(accesstoken=accesstoken)
assert(isinstance(obj, plugins.NotifyPushBullet))
# Default is to send to all devices, so there will be a
# recipient here
assert(len(obj.recipients) == 1)
obj = plugins.NotifyPushBullet(accesstoken=accesstoken, recipients=set())
assert(isinstance(obj, plugins.NotifyPushBullet))
# Default is to send to all devices, so there will be a
# recipient here
assert(len(obj.recipients) == 1)
# Support the handling of an empty and invalid URL strings
assert(plugins.NotifyPushBullet.parse_url(None) is None)
assert(plugins.NotifyPushBullet.parse_url('') is None)
assert(plugins.NotifyPushBullet.parse_url(42) is None)
@mock.patch('requests.get')
@mock.patch('requests.post')
def test_notify_pushover_plugin(mock_post, mock_get):
"""
API: NotifyPushover() Extra Checks
"""
# Initialize some generic (but valid) tokens
token = 'a' * 30
user = 'u' * 30
invalid_device = 'd' * 35
# Support strings
devices = 'device1,device2,,,,%s' % invalid_device
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok
mock_get.return_value.status_code = requests.codes.ok
try:
obj = plugins.NotifyPushover(user=user, token=None)
# No token specified
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
obj = plugins.NotifyPushover(user=user, token=token, devices=devices)
assert(isinstance(obj, plugins.NotifyPushover))
assert(len(obj.devices) == 3)
# Disable throttling to speed up unit tests
obj.throttle_attempt = 0
# This call fails because there is 1 invalid device
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is False
obj = plugins.NotifyPushover(user=user, token=token)
assert(isinstance(obj, plugins.NotifyPushover))
# Default is to send to all devices, so there will be a
# device defined here
assert(len(obj.devices) == 1)
# Disable throttling to speed up unit tests
obj.throttle_attempt = 0
# This call succeeds because all of the devices are valid
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
obj = plugins.NotifyPushover(user=user, token=token, devices=set())
assert(isinstance(obj, plugins.NotifyPushover))
# Default is to send to all devices, so there will be a
# device defined here
assert(len(obj.devices) == 1)
# Support the handling of an empty and invalid URL strings
assert(plugins.NotifyPushover.parse_url(None) is None)
assert(plugins.NotifyPushover.parse_url('') is None)
assert(plugins.NotifyPushover.parse_url(42) is None)
@mock.patch('requests.get')
@mock.patch('requests.post')
def test_notify_toasty_plugin(mock_post, mock_get):
"""
API: NotifyToasty() Extra Checks
"""
# Support strings
devices = 'device1,device2,,,,'
# User
user = 'l2g'
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok
mock_get.return_value.status_code = requests.codes.ok
try:
obj = plugins.NotifyToasty(user=user, devices=None)
# No devices specified
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
try:
obj = plugins.NotifyToasty(user=user, devices=set())
# No devices specified
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
obj = plugins.NotifyToasty(user=user, devices=devices)
assert(isinstance(obj, plugins.NotifyToasty))
assert(len(obj.devices) == 2)
# Support the handling of an empty and invalid URL strings
assert(plugins.NotifyToasty.parse_url(None) is None)
assert(plugins.NotifyToasty.parse_url('') is None)
assert(plugins.NotifyToasty.parse_url(42) is None)
@mock.patch('requests.get')
@mock.patch('requests.post')
def test_notify_telegram_plugin(mock_post, mock_get):
"""
API: NotifyTelegram() Extra Checks
"""
# Bot Token
bot_token = '123456789:abcdefg_hijklmnop'
invalid_bot_token = 'abcd:123'
# Chat ID
chat_ids = 'l2g, lead2gold'
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok
mock_get.return_value.status_code = requests.codes.ok
try:
obj = plugins.NotifyTelegram(bot_token=None, chat_ids=chat_ids)
# invalid bot token (None)
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
try:
obj = plugins.NotifyTelegram(
bot_token=invalid_bot_token, chat_ids=chat_ids)
# invalid bot token
assert(False)
except TypeError:
# Exception should be thrown about the fact an invalid token was
# specified
assert(True)
try:
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
# No chat_ids specified
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
try:
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=set())
# No chat_ids specified
assert(False)
except TypeError:
# Exception should be thrown about the fact no token was specified
assert(True)
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=chat_ids)
assert(isinstance(obj, plugins.NotifyTelegram))
assert(len(obj.chat_ids) == 2)
# Support the handling of an empty and invalid URL strings
assert(plugins.NotifyTelegram.parse_url(None) is None)
assert(plugins.NotifyTelegram.parse_url('') is None)
assert(plugins.NotifyTelegram.parse_url(42) is None)
# Prepare Mock to fail
response = mock.Mock()
response.status_code = requests.codes.internal_server_error
# a error response
response.text = dumps({
'description': 'test',
})
mock_get.return_value = response
mock_post.return_value = response
# No image asset
nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=chat_ids)
nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)
# Disable throttling to speed up unit tests
nimg_obj.throttle_attempt = 0
obj.throttle_attempt = 0
# This tests erroneous messages involving multiple chat ids
assert obj.notify(
title='title', body='body', notify_type=NotifyType.INFO) is False
assert nimg_obj.notify(
title='title', body='body', notify_type=NotifyType.INFO) is False
# This tests erroneous messages involving a single chat id
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids='l2g')
nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids='l2g')
nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)
assert obj.notify(
title='title', body='body', notify_type=NotifyType.INFO) is False
assert nimg_obj.notify(
title='title', body='body', notify_type=NotifyType.INFO) is False