mirror of https://github.com/caronc/apprise
100% test coverage
parent
cc79763b3f
commit
a03525a859
|
@ -15,12 +15,17 @@
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU Lesser General Public License for more details.
|
# GNU Lesser General Public License for more details.
|
||||||
|
import re
|
||||||
from .pushjet import errors
|
from .pushjet import errors
|
||||||
from .pushjet import pushjet
|
from .pushjet import pushjet
|
||||||
|
|
||||||
from ..NotifyBase import NotifyBase
|
from ..NotifyBase import NotifyBase
|
||||||
|
|
||||||
|
PUBLIC_KEY_RE = re.compile(
|
||||||
|
r'^[a-z0-9]{4}-[a-z0-9]{6}-[a-z0-9]{12}-[a-z0-9]{5}-[a-z0-9]{9}$', re.I)
|
||||||
|
|
||||||
|
SECRET_KEY_RE = re.compile(r'^[a-z0-9]{32}$', re.I)
|
||||||
|
|
||||||
|
|
||||||
class NotifyPushjet(NotifyBase):
|
class NotifyPushjet(NotifyBase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -74,16 +74,16 @@ class NotifyRocketChat(NotifyBase):
|
||||||
# Initialize channels list
|
# Initialize channels list
|
||||||
self.channels = list()
|
self.channels = list()
|
||||||
|
|
||||||
# Initialize room_id list
|
# Initialize room list
|
||||||
self.room_ids = list()
|
self.rooms = list()
|
||||||
|
|
||||||
if recipients is None:
|
if recipients is None:
|
||||||
recipients = []
|
recipients = []
|
||||||
|
|
||||||
elif compat_is_basestring(recipients):
|
elif compat_is_basestring(recipients):
|
||||||
recipients = filter(bool, LIST_DELIM.split(
|
recipients = [x for x in filter(bool, LIST_DELIM.split(
|
||||||
recipients,
|
recipients,
|
||||||
))
|
))]
|
||||||
|
|
||||||
elif not isinstance(recipients, (set, tuple, list)):
|
elif not isinstance(recipients, (set, tuple, list)):
|
||||||
recipients = []
|
recipients = []
|
||||||
|
@ -98,62 +98,88 @@ class NotifyRocketChat(NotifyBase):
|
||||||
|
|
||||||
result = IS_ROOM_ID.match(recipient)
|
result = IS_ROOM_ID.match(recipient)
|
||||||
if result:
|
if result:
|
||||||
# store valid room_id
|
# store valid room
|
||||||
self.channels.append(result.group('name'))
|
self.rooms.append(result.group('name'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid channel/room_id ' +
|
'Dropped invalid channel/room ' +
|
||||||
'(%s) specified.' % recipient,
|
'(%s) specified.' % recipient,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(self.room_ids) == 0 and len(self.channels) == 0:
|
if len(self.rooms) == 0 and len(self.channels) == 0:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
'No Rocket.Chat room_id and/or channels specified to notify.'
|
'No Rocket.Chat room and/or channels specified to notify.'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used to track token headers upon authentication (if successful)
|
# Used to track token headers upon authentication (if successful)
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
||||||
# Track whether we authenticated okay
|
|
||||||
self.authenticated = self.login()
|
|
||||||
|
|
||||||
if not self.authenticated:
|
|
||||||
raise TypeError(
|
|
||||||
'Authentication to Rocket.Chat server failed.'
|
|
||||||
)
|
|
||||||
|
|
||||||
def notify(self, title, body, notify_type, **kwargs):
|
def notify(self, title, body, notify_type, **kwargs):
|
||||||
"""
|
"""
|
||||||
wrapper to send_notification since we can alert more then one channel
|
wrapper to send_notification since we can alert more then one channel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Track whether we authenticated okay
|
||||||
|
|
||||||
|
if not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
# Prepare our message
|
# Prepare our message
|
||||||
text = '*%s*\r\n%s' % (title.replace('*', '\*'), body)
|
text = '*%s*\r\n%s' % (title.replace('*', '\*'), body)
|
||||||
|
|
||||||
# Send all our defined channels
|
# Initiaize our error tracking
|
||||||
for channel in self.channels:
|
has_error = False
|
||||||
self.send_notification({
|
|
||||||
'text': text,
|
# Create a copy of our rooms and channels to notify against
|
||||||
'channel': channel,
|
channels = list(self.channels)
|
||||||
}, notify_type=notify_type, **kwargs)
|
rooms = list(self.rooms)
|
||||||
|
|
||||||
|
while len(channels) > 0:
|
||||||
|
# Get Channel
|
||||||
|
channel = channels.pop(0)
|
||||||
|
|
||||||
|
if not self.send_notification(
|
||||||
|
{
|
||||||
|
'text': text,
|
||||||
|
'channel': channel,
|
||||||
|
}, notify_type=notify_type, **kwargs):
|
||||||
|
|
||||||
|
# toggle flag
|
||||||
|
has_error = True
|
||||||
|
|
||||||
|
if len(channels) + len(rooms) > 0:
|
||||||
|
# Prevent thrashing requests
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
# Send all our defined room id's
|
# Send all our defined room id's
|
||||||
for room_id in self.room_ids:
|
while len(rooms):
|
||||||
self.send_notification({
|
# Get Room
|
||||||
'text': text,
|
room = rooms.pop(0)
|
||||||
'roomId': room_id,
|
|
||||||
}, notify_type=notify_type, **kwargs)
|
if not self.send_notification(
|
||||||
|
{
|
||||||
|
'text': text,
|
||||||
|
'roomId': room,
|
||||||
|
}, notify_type=notify_type, **kwargs):
|
||||||
|
|
||||||
|
# toggle flag
|
||||||
|
has_error = True
|
||||||
|
|
||||||
|
if len(rooms) > 0:
|
||||||
|
# Prevent thrashing requests
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
# logout
|
||||||
|
self.logout()
|
||||||
|
|
||||||
|
return not has_error
|
||||||
|
|
||||||
def send_notification(self, payload, notify_type, **kwargs):
|
def send_notification(self, payload, notify_type, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Notify Rocket.Chat Notification
|
Perform Notify Rocket.Chat Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.authenticated:
|
|
||||||
# We couldn't authenticate; we're done
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.logger.debug('Rocket.Chat POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Rocket.Chat POST URL: %s (cert_verify=%r)' % (
|
||||||
self.api_url + 'chat.postMessage', self.verify_certificate,
|
self.api_url + 'chat.postMessage', self.verify_certificate,
|
||||||
))
|
))
|
||||||
|
@ -173,6 +199,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
'%s (error=%s).' % (
|
'%s (error=%s).' % (
|
||||||
RC_HTTP_ERROR_MAP[r.status_code],
|
RC_HTTP_ERROR_MAP[r.status_code],
|
||||||
r.status_code))
|
r.status_code))
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Rocket.Chat notification ' +
|
'Failed to send Rocket.Chat notification ' +
|
||||||
|
@ -200,6 +227,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
def login(self):
|
def login(self):
|
||||||
"""
|
"""
|
||||||
login to our server
|
login to our server
|
||||||
|
|
||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
'username': self.user,
|
'username': self.user,
|
||||||
|
@ -220,7 +248,8 @@ class NotifyRocketChat(NotifyBase):
|
||||||
'%s (error=%s).' % (
|
'%s (error=%s).' % (
|
||||||
RC_HTTP_ERROR_MAP[r.status_code],
|
RC_HTTP_ERROR_MAP[r.status_code],
|
||||||
r.status_code))
|
r.status_code))
|
||||||
except IndexError:
|
|
||||||
|
except KeyError:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to authenticate with Rocket.Chat server ' +
|
'Failed to authenticate with Rocket.Chat server ' +
|
||||||
'(error=%s).' % (
|
'(error=%s).' % (
|
||||||
|
@ -238,15 +267,12 @@ class NotifyRocketChat(NotifyBase):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set our headers for further communication
|
# Set our headers for further communication
|
||||||
self.headers['X-Auth-Token'] = \
|
self.headers['X-Auth-Token'] = response.get(
|
||||||
response.get('data').get('authToken')
|
'data', {'authToken': None}).get('authToken')
|
||||||
self.headers['X-User-Id'] = \
|
self.headers['X-User-Id'] = response.get(
|
||||||
response.get('data').get('userId')
|
'data', {'userId': None}).get('userId')
|
||||||
|
|
||||||
# We're authenticated now
|
except requests.RequestException as e:
|
||||||
self.authenticated = True
|
|
||||||
|
|
||||||
except requests.ConnectionError as e:
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured authenticating to the ' +
|
'A Connection error occured authenticating to the ' +
|
||||||
'Rocket.Chat server.')
|
'Rocket.Chat server.')
|
||||||
|
@ -259,10 +285,6 @@ class NotifyRocketChat(NotifyBase):
|
||||||
"""
|
"""
|
||||||
logout of our server
|
logout of our server
|
||||||
"""
|
"""
|
||||||
if not self.authenticated:
|
|
||||||
# Nothing to do
|
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
self.api_url + 'logout',
|
self.api_url + 'logout',
|
||||||
|
@ -278,7 +300,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
RC_HTTP_ERROR_MAP[r.status_code],
|
RC_HTTP_ERROR_MAP[r.status_code],
|
||||||
r.status_code))
|
r.status_code))
|
||||||
|
|
||||||
except IndexError:
|
except KeyError:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to log off Rocket.Chat server ' +
|
'Failed to log off Rocket.Chat server ' +
|
||||||
'(error=%s).' % (
|
'(error=%s).' % (
|
||||||
|
@ -292,15 +314,13 @@ class NotifyRocketChat(NotifyBase):
|
||||||
'Rocket.Chat log off successful; response %s.' % (
|
'Rocket.Chat log off successful; response %s.' % (
|
||||||
r.text))
|
r.text))
|
||||||
|
|
||||||
except requests.ConnectionError as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured logging off the ' +
|
'A Connection error occured logging off the ' +
|
||||||
'Rocket.Chat server')
|
'Rocket.Chat server')
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# We're no longer authenticated now
|
|
||||||
self.authenticated = False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -68,18 +68,11 @@ class NotifyTwitter(NotifyBase):
|
||||||
'No user was specified.'
|
'No user was specified.'
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
# Store our data
|
||||||
# Attempt to Establish a connection to Twitter
|
self.ckey = ckey
|
||||||
self.auth = tweepy.OAuthHandler(ckey, csecret)
|
self.csecret = csecret
|
||||||
|
self.akey = akey
|
||||||
# Apply our Access Tokens
|
self.asecret = asecret
|
||||||
self.auth.set_access_token(akey, asecret)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
raise TypeError(
|
|
||||||
'Twitter authentication failed; '
|
|
||||||
'please verify your configuration.'
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -88,6 +81,20 @@ class NotifyTwitter(NotifyBase):
|
||||||
Perform Twitter Notification
|
Perform Twitter Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to Establish a connection to Twitter
|
||||||
|
self.auth = tweepy.OAuthHandler(self.ckey, self.csecret)
|
||||||
|
|
||||||
|
# Apply our Access Tokens
|
||||||
|
self.auth.set_access_token(self.akey, self.asecret)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.logger.warning(
|
||||||
|
'Twitter authentication failed; '
|
||||||
|
'please verify your configuration.'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
text = '%s\r\n%s' % (title, body)
|
text = '%s\r\n%s' % (title, body)
|
||||||
try:
|
try:
|
||||||
# Get our API
|
# Get our API
|
||||||
|
@ -128,18 +135,19 @@ class NotifyTwitter(NotifyBase):
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
try:
|
try:
|
||||||
consumer_secret, access_token_key, access_token_secret = \
|
consumer_secret, access_token_key, access_token_secret = \
|
||||||
filter(bool, NotifyBase.split_path(results['fullpath']))[0:3]
|
[x for x in filter(bool, NotifyBase.split_path(
|
||||||
|
results['fullpath']))][0:3]
|
||||||
|
|
||||||
except (AttributeError, IndexError):
|
except (ValueError, AttributeError, IndexError):
|
||||||
# Force some bad values that will get caught
|
# Force some bad values that will get caught
|
||||||
# in parsing later
|
# in parsing later
|
||||||
consumer_secret = None
|
consumer_secret = None
|
||||||
access_token_key = None
|
access_token_key = None
|
||||||
access_token_secret = None
|
access_token_secret = None
|
||||||
|
|
||||||
results['ckey'] = consumer_key,
|
results['ckey'] = consumer_key
|
||||||
results['csecret'] = consumer_secret,
|
results['csecret'] = consumer_secret
|
||||||
results['akey'] = access_token_key,
|
results['akey'] = access_token_key
|
||||||
results['asecret'] = access_token_secret,
|
results['asecret'] = access_token_secret
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -26,21 +26,23 @@ from .NotifyFaast import NotifyFaast
|
||||||
from .NotifyGrowl.NotifyGrowl import NotifyGrowl
|
from .NotifyGrowl.NotifyGrowl import NotifyGrowl
|
||||||
from .NotifyGrowl import gntp
|
from .NotifyGrowl import gntp
|
||||||
from .NotifyJSON import NotifyJSON
|
from .NotifyJSON import NotifyJSON
|
||||||
|
from .NotifyMatterMost import NotifyMatterMost
|
||||||
from .NotifyMyAndroid import NotifyMyAndroid
|
from .NotifyMyAndroid import NotifyMyAndroid
|
||||||
from .NotifyProwl import NotifyProwl
|
from .NotifyProwl import NotifyProwl
|
||||||
from .NotifyPushalot import NotifyPushalot
|
from .NotifyPushalot import NotifyPushalot
|
||||||
from .NotifyPushBullet import NotifyPushBullet
|
from .NotifyPushBullet import NotifyPushBullet
|
||||||
|
from .NotifyPushjet.NotifyPushjet import NotifyPushjet
|
||||||
|
from .NotifyPushjet import pushjet
|
||||||
from .NotifyPushover import NotifyPushover
|
from .NotifyPushover import NotifyPushover
|
||||||
from .NotifyRocketChat import NotifyRocketChat
|
from .NotifyRocketChat import NotifyRocketChat
|
||||||
|
from .NotifyTelegram import NotifyTelegram
|
||||||
from .NotifyToasty import NotifyToasty
|
from .NotifyToasty import NotifyToasty
|
||||||
from .NotifyTwitter.NotifyTwitter import NotifyTwitter
|
from .NotifyTwitter.NotifyTwitter import NotifyTwitter
|
||||||
|
from .NotifyTwitter import tweepy
|
||||||
from .NotifyXBMC import NotifyXBMC
|
from .NotifyXBMC import NotifyXBMC
|
||||||
from .NotifyXML import NotifyXML
|
from .NotifyXML import NotifyXML
|
||||||
from .NotifySlack import NotifySlack
|
from .NotifySlack import NotifySlack
|
||||||
from .NotifyJoin import NotifyJoin
|
from .NotifyJoin import NotifyJoin
|
||||||
from .NotifyTelegram import NotifyTelegram
|
|
||||||
from .NotifyMatterMost import NotifyMatterMost
|
|
||||||
from .NotifyPushjet.NotifyPushjet import NotifyPushjet
|
|
||||||
|
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NOTIFY_IMAGE_SIZES
|
from ..common import NOTIFY_IMAGE_SIZES
|
||||||
|
@ -61,6 +63,12 @@ __all__ = [
|
||||||
# NotifyEmail Base References (used for Testing)
|
# NotifyEmail Base References (used for Testing)
|
||||||
'NotifyEmailBase',
|
'NotifyEmailBase',
|
||||||
|
|
||||||
# gntp (used for Testing)
|
# gntp (used for NotifyGrowl Testing)
|
||||||
'gntp',
|
'gntp',
|
||||||
|
|
||||||
|
# pushjet (used for NotifyPushjet Testing)
|
||||||
|
'pushjet',
|
||||||
|
|
||||||
|
# tweepy (used for NotifyTwitter Testing)
|
||||||
|
'tweepy',
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# NotifyPushjet - Unit Tests
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
||||||
|
#
|
||||||
|
# This file is part of apprise.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
from apprise import plugins
|
||||||
|
from apprise import NotifyType
|
||||||
|
from apprise import Apprise
|
||||||
|
import mock
|
||||||
|
|
||||||
|
TEST_URLS = (
|
||||||
|
##################################
|
||||||
|
# NotifyPushjet
|
||||||
|
##################################
|
||||||
|
('pjet://', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('pjets://', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
# Default query (uses pushjet server)
|
||||||
|
('pjet://%s' % ('a' * 32), {
|
||||||
|
'instance': plugins.NotifyPushjet,
|
||||||
|
}),
|
||||||
|
# Specify your own server
|
||||||
|
('pjet://%s@localhost' % ('a' * 32), {
|
||||||
|
'instance': plugins.NotifyPushjet,
|
||||||
|
}),
|
||||||
|
# Specify your own server with port
|
||||||
|
('pjets://%s@localhost:8080' % ('a' * 32), {
|
||||||
|
'instance': plugins.NotifyPushjet,
|
||||||
|
}),
|
||||||
|
('pjet://:@/', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('pjet://%s@localhost:8081' % ('a' * 32), {
|
||||||
|
'instance': plugins.NotifyPushjet,
|
||||||
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
|
# is set and tests that we gracfully handle them
|
||||||
|
'test_notify_exceptions': True,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('apprise.plugins.pushjet.pushjet.Service.send')
|
||||||
|
@mock.patch('apprise.plugins.pushjet.pushjet.Service.refresh')
|
||||||
|
def test_plugin(mock_refresh, mock_send):
|
||||||
|
"""
|
||||||
|
API: NotifyPushjet Plugin() (pt1)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# iterate over our dictionary and test it out
|
||||||
|
for (url, meta) in TEST_URLS:
|
||||||
|
|
||||||
|
# Our expected instance
|
||||||
|
instance = meta.get('instance', None)
|
||||||
|
|
||||||
|
# Our expected server objects
|
||||||
|
self = meta.get('self', None)
|
||||||
|
|
||||||
|
# Our expected Query response (True, False, or exception type)
|
||||||
|
response = meta.get('response', True)
|
||||||
|
|
||||||
|
# Allow us to force the server response code to be something other then
|
||||||
|
# the defaults
|
||||||
|
response = meta.get(
|
||||||
|
'response', True if response else False)
|
||||||
|
|
||||||
|
test_notify_exceptions = meta.get(
|
||||||
|
'test_notify_exceptions', False)
|
||||||
|
|
||||||
|
test_exceptions = (
|
||||||
|
plugins.pushjet.errors.AccessError(
|
||||||
|
0, 'pushjet.AccessError() not handled'),
|
||||||
|
plugins.pushjet.errors.NonexistentError(
|
||||||
|
0, 'pushjet.NonexistentError() not handled'),
|
||||||
|
plugins.pushjet.errors.SubscriptionError(
|
||||||
|
0, 'gntp.SubscriptionError() not handled'),
|
||||||
|
plugins.pushjet.errors.RequestError(
|
||||||
|
'pushjet.RequestError() not handled'),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||||
|
|
||||||
|
if instance is None:
|
||||||
|
# Check that we got what we came for
|
||||||
|
assert obj is instance
|
||||||
|
continue
|
||||||
|
|
||||||
|
assert(isinstance(obj, instance))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if test_notify_exceptions is False:
|
||||||
|
# Store our response
|
||||||
|
mock_send.return_value = response
|
||||||
|
mock_send.side_effect = None
|
||||||
|
|
||||||
|
# check that we're as expected
|
||||||
|
assert obj.notify(
|
||||||
|
title='test', body='body',
|
||||||
|
notify_type=NotifyType.INFO) == response
|
||||||
|
|
||||||
|
else:
|
||||||
|
for exception in test_exceptions:
|
||||||
|
mock_send.side_effect = exception
|
||||||
|
mock_send.return_value = None
|
||||||
|
try:
|
||||||
|
assert obj.notify(
|
||||||
|
title='test', body='body',
|
||||||
|
notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
except AssertionError:
|
||||||
|
# Don't mess with these entries
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# We can't handle this exception type
|
||||||
|
assert False
|
||||||
|
|
||||||
|
except AssertionError:
|
||||||
|
# Don't mess with these entries
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Check that we were expecting this exception to happen
|
||||||
|
assert isinstance(e, response)
|
||||||
|
|
||||||
|
except AssertionError:
|
||||||
|
# Don't mess with these entries
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Handle our exception
|
||||||
|
assert(instance is not None)
|
||||||
|
assert(isinstance(e, instance))
|
|
@ -20,6 +20,7 @@ from apprise import plugins
|
||||||
from apprise import NotifyType
|
from apprise import NotifyType
|
||||||
from apprise import Apprise
|
from apprise import Apprise
|
||||||
from apprise import AppriseAsset
|
from apprise import AppriseAsset
|
||||||
|
from apprise.utils import compat_is_basestring
|
||||||
from json import dumps
|
from json import dumps
|
||||||
import requests
|
import requests
|
||||||
import mock
|
import mock
|
||||||
|
@ -37,9 +38,8 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# An invalid access and secret key specified
|
# An invalid access and secret key specified
|
||||||
('boxcar://access.key/secret.key/', {
|
('boxcar://access.key/secret.key/', {
|
||||||
'instance': plugins.NotifyBoxcar,
|
|
||||||
# Thrown because there were no recipients specified
|
# Thrown because there were no recipients specified
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# Provide both an access and a secret
|
# Provide both an access and a secret
|
||||||
('boxcar://%s/%s' % ('a' * 64, 'b' * 64), {
|
('boxcar://%s/%s' % ('a' * 64, 'b' * 64), {
|
||||||
|
@ -136,9 +136,8 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# Invalid APIKey
|
# Invalid APIKey
|
||||||
('join://%s' % ('a' * 24), {
|
('join://%s' % ('a' * 24), {
|
||||||
'instance': None,
|
|
||||||
# Missing a channel
|
# Missing a channel
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey + device
|
# APIKey + device
|
||||||
('join://%s/%s' % ('a' * 32, 'd' * 32), {
|
('join://%s/%s' % ('a' * 32, 'd' * 32), {
|
||||||
|
@ -336,14 +335,12 @@ TEST_URLS = (
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMatterMost,
|
||||||
}),
|
}),
|
||||||
('mmosts://localhost', {
|
('mmosts://localhost', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
|
||||||
# Thrown because there was no webhook id specified
|
# Thrown because there was no webhook id specified
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/bad-web-hook', {
|
('mmost://localhost/bad-web-hook', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
|
||||||
# Thrown because the webhook is not in a valid format
|
# Thrown because the webhook is not in a valid format
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('mmost://:@/', {
|
('mmost://:@/', {
|
||||||
'instance': None,
|
'instance': None,
|
||||||
|
@ -379,7 +376,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# Invalid APIKey
|
# Invalid APIKey
|
||||||
('nma://%s' % ('a' * 24), {
|
('nma://%s' % ('a' * 24), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey
|
# APIKey
|
||||||
('nma://%s' % ('a' * 48), {
|
('nma://%s' % ('a' * 48), {
|
||||||
|
@ -401,7 +398,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# APIKey + Invalid DevAPI Key
|
# APIKey + Invalid DevAPI Key
|
||||||
('nma://%s/%s' % ('a' * 48, 'b' * 24), {
|
('nma://%s/%s' % ('a' * 48, 'b' * 24), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey + DevAPI Key
|
# APIKey + DevAPI Key
|
||||||
('nma://%s/%s' % ('a' * 48, 'b' * 48), {
|
('nma://%s/%s' % ('a' * 48, 'b' * 48), {
|
||||||
|
@ -462,7 +459,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# Invalid APIKey
|
# Invalid APIKey
|
||||||
('prowl://%s' % ('a' * 24), {
|
('prowl://%s' % ('a' * 24), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey
|
# APIKey
|
||||||
('prowl://%s' % ('a' * 40), {
|
('prowl://%s' % ('a' * 40), {
|
||||||
|
@ -484,7 +481,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# APIKey + Invalid Provider Key
|
# APIKey + Invalid Provider Key
|
||||||
('prowl://%s/%s' % ('a' * 40, 'b' * 24), {
|
('prowl://%s/%s' % ('a' * 40, 'b' * 24), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey + No Provider Key (empty)
|
# APIKey + No Provider Key (empty)
|
||||||
('prowl://%s///' % ('a' * 40), {
|
('prowl://%s///' % ('a' * 40), {
|
||||||
|
@ -539,9 +536,8 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# Invalid AuthToken
|
# Invalid AuthToken
|
||||||
('palot://%s' % ('a' * 24), {
|
('palot://%s' % ('a' * 24), {
|
||||||
'instance': None,
|
|
||||||
# Missing a channel
|
# Missing a channel
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# AuthToken + bad url
|
# AuthToken + bad url
|
||||||
('palot://:@/', {
|
('palot://:@/', {
|
||||||
|
@ -635,15 +631,15 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# APIkey; no user
|
# APIkey; no user
|
||||||
('pover://%s' % ('a' * 30), {
|
('pover://%s' % ('a' * 30), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIkey; invalid user
|
# APIkey; invalid user
|
||||||
('pover://%s@%s' % ('u' * 20, 'a' * 30), {
|
('pover://%s@%s' % ('u' * 20, 'a' * 30), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# Invalid APIKey; valid User
|
# Invalid APIKey; valid User
|
||||||
('pover://%s@%s' % ('u' * 30, 'a' * 24), {
|
('pover://%s@%s' % ('u' * 30, 'a' * 24), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# APIKey + Valid User
|
# APIKey + Valid User
|
||||||
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
('pover://%s@%s' % ('u' * 30, 'a' * 30), {
|
||||||
|
@ -706,6 +702,122 @@ TEST_URLS = (
|
||||||
'test_requests_exceptions': True,
|
'test_requests_exceptions': True,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# NotifyRocketChat
|
||||||
|
##################################
|
||||||
|
('rocket://', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('rockets://', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
# No username or pass
|
||||||
|
('rocket://localhost', {
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
# No room or channel
|
||||||
|
('rocket://user:pass@localhost', {
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
# No valid rooms or channels
|
||||||
|
('rocket://user:pass@localhost/#/!/@', {
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
# A room and port identifier
|
||||||
|
('rocket://user:pass@localhost:8080/room/', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_text': {
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'authToken': 'abcd',
|
||||||
|
'userId': 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
# A channel
|
||||||
|
('rockets://user:pass@localhost/#channel', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_text': {
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'authToken': 'abcd',
|
||||||
|
'userId': 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
# Several channels
|
||||||
|
('rocket://user:pass@localhost/#channel1/#channel2/', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_text': {
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'authToken': 'abcd',
|
||||||
|
'userId': 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
# Several Rooms
|
||||||
|
('rocket://user:pass@localhost/room1/room2', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_text': {
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'authToken': 'abcd',
|
||||||
|
'userId': 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
# A room and channel
|
||||||
|
('rocket://user:pass@localhost/room/#channel', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_text': {
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'authToken': 'abcd',
|
||||||
|
'userId': 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
('rocket://:@/', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
# A room and channel
|
||||||
|
('rockets://user:pass@localhost/rooma/#channela', {
|
||||||
|
# The response text is expected to be the following on a success
|
||||||
|
'requests_response_code': requests.codes.ok,
|
||||||
|
'requests_response_text': {
|
||||||
|
# return something other then a success message type
|
||||||
|
'status': 'failure',
|
||||||
|
},
|
||||||
|
# Exception is thrown in this case
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# Notifications will fail in this event
|
||||||
|
'response': False,
|
||||||
|
}),
|
||||||
|
('rocket://user:pass@localhost:8081/room1/room2', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# force a failure
|
||||||
|
'response': False,
|
||||||
|
'requests_response_code': requests.codes.internal_server_error,
|
||||||
|
}),
|
||||||
|
('rocket://user:pass@localhost:8082/#channel', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# throw a bizzare code forcing us to fail to look it up
|
||||||
|
'response': False,
|
||||||
|
'requests_response_code': 999,
|
||||||
|
}),
|
||||||
|
('rocket://user:pass@localhost:8083/#chan1/#chan2/room', {
|
||||||
|
'instance': plugins.NotifyRocketChat,
|
||||||
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
|
# is set and tests that we gracfully handle them
|
||||||
|
'test_requests_exceptions': True,
|
||||||
|
}),
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
# NotifySlack
|
# NotifySlack
|
||||||
##################################
|
##################################
|
||||||
|
@ -741,19 +853,19 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ', {
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ', {
|
||||||
# Missing a channel
|
# Missing a channel
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('slack://username@INVALID/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
|
('slack://username@INVALID/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
|
||||||
# invalid 1st Token
|
# invalid 1st Token
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('slack://username@T1JJ3T3L2/INVALID/TIiajkdnlazkcOXrIdevi7FQ/#great', {
|
('slack://username@T1JJ3T3L2/INVALID/TIiajkdnlazkcOXrIdevi7FQ/#great', {
|
||||||
# invalid 2rd Token
|
# invalid 2rd Token
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/INVALID/#channel', {
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/INVALID/#channel', {
|
||||||
# invalid 3rd Token
|
# invalid 3rd Token
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('slack://l2g@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#usenet', {
|
('slack://l2g@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#usenet', {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
|
@ -906,7 +1018,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
# No username specified but contains a device
|
# No username specified but contains a device
|
||||||
('toasty://%s' % ('d' * 32), {
|
('toasty://%s' % ('d' * 32), {
|
||||||
'exception': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# User + 1 device
|
# User + 1 device
|
||||||
('toasty://user@device', {
|
('toasty://user@device', {
|
||||||
|
@ -1067,9 +1179,6 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
# Our expected instance
|
# Our expected instance
|
||||||
instance = meta.get('instance', None)
|
instance = meta.get('instance', None)
|
||||||
|
|
||||||
# Our expected exception
|
|
||||||
exception = meta.get('exception', None)
|
|
||||||
|
|
||||||
# Our expected server objects
|
# Our expected server objects
|
||||||
self = meta.get('self', None)
|
self = meta.get('self', None)
|
||||||
|
|
||||||
|
@ -1083,6 +1192,13 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
requests.codes.ok if response else requests.codes.not_found,
|
requests.codes.ok if response else requests.codes.not_found,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Allow us to force the server response text to be something other then
|
||||||
|
# the defaults
|
||||||
|
requests_response_text = meta.get('requests_response_text')
|
||||||
|
if not compat_is_basestring(requests_response_text):
|
||||||
|
# Convert to string
|
||||||
|
requests_response_text = dumps(requests_response_text)
|
||||||
|
|
||||||
# Allow notification type override, otherwise default to INFO
|
# Allow notification type override, otherwise default to INFO
|
||||||
notify_type = meta.get('notify_type', NotifyType.INFO)
|
notify_type = meta.get('notify_type', NotifyType.INFO)
|
||||||
|
|
||||||
|
@ -1112,6 +1228,12 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
# Handle our default response
|
# Handle our default response
|
||||||
mock_post.return_value.status_code = requests_response_code
|
mock_post.return_value.status_code = requests_response_code
|
||||||
mock_get.return_value.status_code = requests_response_code
|
mock_get.return_value.status_code = requests_response_code
|
||||||
|
|
||||||
|
# Handle our default text response
|
||||||
|
mock_get.return_value.text = requests_response_text
|
||||||
|
mock_post.return_value.text = requests_response_text
|
||||||
|
|
||||||
|
# Ensure there is no side effect set
|
||||||
mock_post.side_effect = None
|
mock_post.side_effect = None
|
||||||
mock_get.side_effect = None
|
mock_get.side_effect = None
|
||||||
|
|
||||||
|
@ -1135,20 +1257,11 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
obj = Apprise.instantiate(
|
obj = Apprise.instantiate(
|
||||||
url, asset=asset, suppress_exceptions=False)
|
url, asset=asset, suppress_exceptions=False)
|
||||||
|
|
||||||
# Make sure we weren't expecting an exception and just didn't get
|
|
||||||
# one.
|
|
||||||
assert exception is None
|
|
||||||
|
|
||||||
if obj is None:
|
if obj is None:
|
||||||
# We're done (assuming this is what we were expecting)
|
# We're done (assuming this is what we were expecting)
|
||||||
assert instance is None
|
assert instance is None
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if instance is None:
|
|
||||||
# Expected None but didn't get it
|
|
||||||
print('%s instantiated %s' % (url, str(obj)))
|
|
||||||
assert(False)
|
|
||||||
|
|
||||||
assert(isinstance(obj, instance))
|
assert(isinstance(obj, instance))
|
||||||
|
|
||||||
# Disable throttling to speed up unit tests
|
# Disable throttling to speed up unit tests
|
||||||
|
@ -1172,6 +1285,7 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
for _exception in test_requests_exceptions:
|
for _exception in test_requests_exceptions:
|
||||||
mock_post.side_effect = _exception
|
mock_post.side_effect = _exception
|
||||||
mock_get.side_effect = _exception
|
mock_get.side_effect = _exception
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='test', body='body',
|
title='test', body='body',
|
||||||
|
@ -1203,8 +1317,8 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle our exception
|
# Handle our exception
|
||||||
print('%s / %s' % (url, str(e)))
|
print('%s / %s' % (url, str(e)))
|
||||||
assert(exception is not None)
|
assert(instance is not None)
|
||||||
assert(isinstance(e, exception))
|
assert(isinstance(e, instance))
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.get')
|
@mock.patch('requests.get')
|
||||||
|
@ -1466,6 +1580,133 @@ def test_notify_pushover_plugin(mock_post, mock_get):
|
||||||
assert(plugins.NotifyPushover.parse_url(42) is None)
|
assert(plugins.NotifyPushover.parse_url(42) is None)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('requests.get')
|
||||||
|
@mock.patch('requests.post')
|
||||||
|
def test_notify_rocketchat_plugin(mock_post, mock_get):
|
||||||
|
"""
|
||||||
|
API: NotifyRocketChat() Extra Checks
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Chat ID
|
||||||
|
recipients = 'l2g, lead2gold, #channel, #channel2'
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_get.return_value = requests.Request()
|
||||||
|
mock_post.return_value = requests.Request()
|
||||||
|
mock_post.return_value.status_code = requests.codes.ok
|
||||||
|
mock_get.return_value.status_code = requests.codes.ok
|
||||||
|
mock_post.return_value.text = ''
|
||||||
|
mock_get.return_value.text = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyRocketChat(recipients=None)
|
||||||
|
# invalid recipients list (None)
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no recipients were
|
||||||
|
# specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyRocketChat(recipients=object())
|
||||||
|
# invalid recipients list (object)
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no recipients were
|
||||||
|
# specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyRocketChat(recipients=set())
|
||||||
|
# invalid recipient list/set (no entries)
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no recipients were
|
||||||
|
# specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
obj = plugins.NotifyRocketChat(recipients=recipients)
|
||||||
|
assert(isinstance(obj, plugins.NotifyRocketChat))
|
||||||
|
assert(len(obj.channels) == 2)
|
||||||
|
assert(len(obj.rooms) == 2)
|
||||||
|
|
||||||
|
# Disable throttling to speed up unit tests
|
||||||
|
obj.throttle_attempt = 0
|
||||||
|
|
||||||
|
#
|
||||||
|
# Logout
|
||||||
|
#
|
||||||
|
assert obj.logout() is True
|
||||||
|
|
||||||
|
# Support the handling of an empty and invalid URL strings
|
||||||
|
assert plugins.NotifyRocketChat.parse_url(None) is None
|
||||||
|
assert plugins.NotifyRocketChat.parse_url('') is None
|
||||||
|
assert plugins.NotifyRocketChat.parse_url(42) is None
|
||||||
|
|
||||||
|
# Prepare Mock to fail
|
||||||
|
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||||
|
mock_get.return_value.status_code = requests.codes.internal_server_error
|
||||||
|
mock_post.return_value.text = ''
|
||||||
|
mock_get.return_value.text = ''
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send Notification
|
||||||
|
#
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
|
assert obj.send_notification(
|
||||||
|
payload='test', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
#
|
||||||
|
# Logout
|
||||||
|
#
|
||||||
|
assert obj.logout() is False
|
||||||
|
|
||||||
|
# KeyError handling
|
||||||
|
mock_post.return_value.status_code = 999
|
||||||
|
mock_get.return_value.status_code = 999
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send Notification
|
||||||
|
#
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
|
assert obj.send_notification(
|
||||||
|
payload='test', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
#
|
||||||
|
# Logout
|
||||||
|
#
|
||||||
|
assert obj.logout() is False
|
||||||
|
|
||||||
|
mock_post.return_value.text = ''
|
||||||
|
# Generate exceptions
|
||||||
|
mock_get.side_effect = requests.ConnectionError(
|
||||||
|
0, 'requests.ConnectionError() not handled')
|
||||||
|
mock_post.side_effect = mock_get.side_effect
|
||||||
|
mock_get.return_value.text = ''
|
||||||
|
mock_post.return_value.text = ''
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send Notification
|
||||||
|
#
|
||||||
|
assert obj.send_notification(
|
||||||
|
payload='test', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# Attempt the check again but fake a successful login
|
||||||
|
obj.login = mock.Mock()
|
||||||
|
obj.login.return_value = True
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
|
#
|
||||||
|
# Logout
|
||||||
|
#
|
||||||
|
assert obj.logout() is False
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.get')
|
@mock.patch('requests.get')
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
def test_notify_toasty_plugin(mock_post, mock_get):
|
def test_notify_toasty_plugin(mock_post, mock_get):
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# NotifyTwitter - Unit Tests
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
||||||
|
#
|
||||||
|
# This file is part of apprise.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
from apprise import plugins
|
||||||
|
from apprise import NotifyType
|
||||||
|
from apprise import Apprise
|
||||||
|
import mock
|
||||||
|
|
||||||
|
|
||||||
|
TEST_URLS = (
|
||||||
|
##################################
|
||||||
|
# NotifyPushjet
|
||||||
|
##################################
|
||||||
|
('tweet://', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('tweet://consumer_key', {
|
||||||
|
# Missing Keys
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
('tweet://consumer_key/consumer_key/', {
|
||||||
|
# Missing Keys
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
('tweet://consumer_key/consumer_key/access_token/', {
|
||||||
|
# Missing Access Secret
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
('tweet://consumer_key/consumer_key/access_token/access_secret', {
|
||||||
|
# Missing User
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
|
('tweet://user@consumer_key/consumer_key/access_token/access_secret', {
|
||||||
|
# We're good!
|
||||||
|
'instance': plugins.NotifyTwitter,
|
||||||
|
}),
|
||||||
|
('tweet://:@/', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('apprise.plugins.tweepy.API')
|
||||||
|
@mock.patch('apprise.plugins.tweepy.OAuthHandler')
|
||||||
|
def test_plugin(mock_oauth, mock_api):
|
||||||
|
"""
|
||||||
|
API: NotifyTwitter Plugin() (pt1)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# iterate over our dictionary and test it out
|
||||||
|
for (url, meta) in TEST_URLS:
|
||||||
|
|
||||||
|
# Our expected instance
|
||||||
|
instance = meta.get('instance', None)
|
||||||
|
|
||||||
|
# Our expected server objects
|
||||||
|
self = meta.get('self', None)
|
||||||
|
|
||||||
|
# Our expected Query response (True, False, or exception type)
|
||||||
|
response = meta.get('response', True)
|
||||||
|
|
||||||
|
# Allow us to force the server response code to be something other then
|
||||||
|
# the defaults
|
||||||
|
response = meta.get(
|
||||||
|
'response', True if response else False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||||
|
|
||||||
|
if instance is None:
|
||||||
|
# Check that we got what we came for
|
||||||
|
assert obj is instance
|
||||||
|
continue
|
||||||
|
|
||||||
|
assert(isinstance(obj, instance))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# check that we're as expected
|
||||||
|
assert obj.notify(
|
||||||
|
title='test', body='body',
|
||||||
|
notify_type=NotifyType.INFO) == response
|
||||||
|
|
||||||
|
except AssertionError:
|
||||||
|
# Don't mess with these entries
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Handle our exception
|
||||||
|
assert(instance is not None)
|
||||||
|
assert(isinstance(e, instance))
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('apprise.plugins.tweepy.API.send_direct_message')
|
||||||
|
@mock.patch('apprise.plugins.tweepy.OAuthHandler.set_access_token')
|
||||||
|
def test_twitter_plugin_init(set_access_token, send_direct_message):
|
||||||
|
"""
|
||||||
|
API: NotifyTwitter Plugin() (pt2)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugins.NotifyTwitter(
|
||||||
|
ckey=None, csecret=None, akey=None, asecret=None)
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
# All keys set to none
|
||||||
|
assert True
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugins.NotifyTwitter(
|
||||||
|
ckey='value', csecret=None, akey=None, asecret=None)
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
# csecret not set
|
||||||
|
assert True
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugins.NotifyTwitter(
|
||||||
|
ckey='value', csecret='value', akey=None, asecret=None)
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
# akey not set
|
||||||
|
assert True
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugins.NotifyTwitter(
|
||||||
|
ckey='value', csecret='value', akey='value', asecret=None)
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
# asecret not set
|
||||||
|
assert True
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugins.NotifyTwitter(
|
||||||
|
ckey='value', csecret='value', akey='value', asecret='value')
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
# user not set
|
||||||
|
assert True
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTwitter(
|
||||||
|
ckey='value', csecret='value', akey='value', asecret='value',
|
||||||
|
user='l2g')
|
||||||
|
# We should initialize properly
|
||||||
|
assert True
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# We should not reach here
|
||||||
|
assert False
|
||||||
|
|
||||||
|
set_access_token.side_effect = TypeError('Invalid')
|
||||||
|
|
||||||
|
assert obj.notify(
|
||||||
|
title='test', body='body',
|
||||||
|
notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# Make it so we can pass authentication, but fail on message
|
||||||
|
# delivery
|
||||||
|
set_access_token.side_effect = None
|
||||||
|
set_access_token.return_value = True
|
||||||
|
send_direct_message.side_effect = plugins.tweepy.error.TweepError(
|
||||||
|
0, 'pushjet.TweepyError() not handled'),
|
||||||
|
|
||||||
|
assert obj.notify(
|
||||||
|
title='test', body='body',
|
||||||
|
notify_type=NotifyType.INFO) is False
|
Loading…
Reference in New Issue