mirror of https://github.com/caronc/apprise
Added support for Slack email address targets (#345)
parent
ca3629f2e1
commit
23957a3337
|
@ -43,7 +43,7 @@
|
||||||
# to add a 'Bot User'. Give it a name and choose 'Add Bot User'.
|
# to add a 'Bot User'. Give it a name and choose 'Add Bot User'.
|
||||||
# 4. Now you can choose 'Install App' to which you can choose 'Install App
|
# 4. Now you can choose 'Install App' to which you can choose 'Install App
|
||||||
# to Workspace'.
|
# to Workspace'.
|
||||||
# 5. You will need to authorize the app which you get promopted to do.
|
# 5. You will need to authorize the app which you get prompted to do.
|
||||||
# 6. Finally you'll get some important information providing you your
|
# 6. Finally you'll get some important information providing you your
|
||||||
# 'OAuth Access Token' and 'Bot User OAuth Access Token' such as:
|
# 'OAuth Access Token' and 'Bot User OAuth Access Token' such as:
|
||||||
# slack://{Oauth Access Token}
|
# slack://{Oauth Access Token}
|
||||||
|
@ -53,6 +53,21 @@
|
||||||
# ... or:
|
# ... or:
|
||||||
# slack://xoxb-1234-1234-4ddbc191d40ee098cbaae6f3523ada2d
|
# slack://xoxb-1234-1234-4ddbc191d40ee098cbaae6f3523ada2d
|
||||||
#
|
#
|
||||||
|
# You must at least give your bot the following access for it to
|
||||||
|
# be useful:
|
||||||
|
# - chat:write - MUST be set otherwise you can not post into
|
||||||
|
# a channel
|
||||||
|
# - users:read.email - Required if you want to be able to lookup
|
||||||
|
# users by their email address.
|
||||||
|
#
|
||||||
|
# The easiest way to bring a bot into a channel (so that it can send
|
||||||
|
# a message to it is to invite it. At this time Apprise does not support
|
||||||
|
# an auto-join functionality. To do this:
|
||||||
|
# - In the 'Details' section of your channel
|
||||||
|
# - Click on the 'More' [...] (elipse icon)
|
||||||
|
# - Click 'Add apps'
|
||||||
|
# - You will be able to select the Bot App you previously created
|
||||||
|
# - Your bot will join your channel.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
@ -64,6 +79,7 @@ from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..common import NotifyFormat
|
from ..common import NotifyFormat
|
||||||
|
from ..utils import is_email
|
||||||
from ..utils import parse_bool
|
from ..utils import parse_bool
|
||||||
from ..utils import parse_list
|
from ..utils import parse_list
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
|
@ -202,6 +218,11 @@ class NotifySlack(NotifyBase):
|
||||||
'prefix': '+',
|
'prefix': '+',
|
||||||
'map_to': 'targets',
|
'map_to': 'targets',
|
||||||
},
|
},
|
||||||
|
'target_email': {
|
||||||
|
'name': _('Target Email'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
'target_user': {
|
'target_user': {
|
||||||
'name': _('Target User'),
|
'name': _('Target User'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
@ -237,6 +258,10 @@ class NotifySlack(NotifyBase):
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
|
'token': {
|
||||||
|
'name': _('Token'),
|
||||||
|
'alias_of': ('access_token', 'token_a', 'token_b', 'token_c'),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, access_token=None, token_a=None, token_b=None,
|
def __init__(self, access_token=None, token_a=None, token_b=None,
|
||||||
|
@ -287,6 +312,11 @@ class NotifySlack(NotifyBase):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'No user was specified; using "%s".' % self.app_id)
|
'No user was specified; using "%s".' % self.app_id)
|
||||||
|
|
||||||
|
# Look the users up by their email address and map them back to their
|
||||||
|
# id here for future queries (if needed). This allows people to
|
||||||
|
# specify a full email as a recipient via slack
|
||||||
|
self._lookup_users = {}
|
||||||
|
|
||||||
# Build list of channels
|
# Build list of channels
|
||||||
self.channels = parse_list(targets)
|
self.channels = parse_list(targets)
|
||||||
if len(self.channels) == 0:
|
if len(self.channels) == 0:
|
||||||
|
@ -382,30 +412,42 @@ class NotifySlack(NotifyBase):
|
||||||
channel = channels.pop(0)
|
channel = channels.pop(0)
|
||||||
|
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
_channel = validate_regex(
|
channel = validate_regex(channel, r'[+#@]?[A-Z0-9_]{1,32}')
|
||||||
channel, r'[+#@]?(?P<value>[A-Z0-9_]{1,32})')
|
if not channel:
|
||||||
|
|
||||||
if not _channel:
|
|
||||||
# Channel over-ride was specified
|
# Channel over-ride was specified
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"The specified target {} is invalid;"
|
"The specified target {} is invalid;"
|
||||||
"skipping.".format(_channel))
|
"skipping.".format(channel))
|
||||||
|
|
||||||
# Mark our failure
|
# Mark our failure
|
||||||
has_error = True
|
has_error = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(_channel) > 1 and _channel[0] == '+':
|
if channel[0] == '+':
|
||||||
# Treat as encoded id if prefixed with a +
|
# Treat as encoded id if prefixed with a +
|
||||||
payload['channel'] = _channel[1:]
|
payload['channel'] = channel[1:]
|
||||||
|
|
||||||
elif len(_channel) > 1 and _channel[0] == '@':
|
elif channel[0] == '@':
|
||||||
# Treat @ value 'as is'
|
# Treat @ value 'as is'
|
||||||
payload['channel'] = _channel
|
payload['channel'] = channel
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Prefix with channel hash tag
|
# We'll perform a user lookup if we detect an email
|
||||||
payload['channel'] = '#{}'.format(_channel)
|
email = is_email(channel)
|
||||||
|
if email:
|
||||||
|
payload['channel'] = \
|
||||||
|
self.lookup_userid(email['full_email'])
|
||||||
|
|
||||||
|
if not payload['channel']:
|
||||||
|
# Move along; any notifications/logging would have
|
||||||
|
# come from lookup_userid()
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Prefix with channel hash tag (if not already)
|
||||||
|
payload['channel'] = \
|
||||||
|
channel if channel[0] == '#' \
|
||||||
|
else '#{}'.format(channel)
|
||||||
|
|
||||||
# Store the valid and massaged payload that is recognizable by
|
# Store the valid and massaged payload that is recognizable by
|
||||||
# slack. This list is used for sending attachments later.
|
# slack. This list is used for sending attachments later.
|
||||||
|
@ -465,6 +507,162 @@ class NotifySlack(NotifyBase):
|
||||||
|
|
||||||
return not has_error
|
return not has_error
|
||||||
|
|
||||||
|
def lookup_userid(self, email):
|
||||||
|
"""
|
||||||
|
Takes an email address and attempts to resolve/acquire it's user
|
||||||
|
id for notification purposes.
|
||||||
|
"""
|
||||||
|
if email in self._lookup_users:
|
||||||
|
# We're done as entry has already been retrieved
|
||||||
|
return self._lookup_users[email]
|
||||||
|
|
||||||
|
if self.mode is not SlackMode.BOT:
|
||||||
|
# You can not look up
|
||||||
|
self.logger.warning(
|
||||||
|
'Emails can not be resolved to Slack User IDs unless you '
|
||||||
|
'have a bot configured.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
lookup_url = self.api_url.format('users.lookupByEmail')
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Authorization': 'Bearer {}'.format(self.access_token),
|
||||||
|
}
|
||||||
|
|
||||||
|
# we pass in our email address as the argument
|
||||||
|
params = {
|
||||||
|
'email': email,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug('Slack User Lookup POST URL: %s (cert_verify=%r)' % (
|
||||||
|
lookup_url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('Slack User Lookup Parameters: %s' % str(params))
|
||||||
|
|
||||||
|
# Initialize our HTTP JSON response
|
||||||
|
response = {'ok': False}
|
||||||
|
|
||||||
|
# Initialize our detected user id (also the response to this function)
|
||||||
|
user_id = None
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
try:
|
||||||
|
r = requests.get(
|
||||||
|
lookup_url,
|
||||||
|
headers=headers,
|
||||||
|
params=params,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attachment posts return a JSON string
|
||||||
|
try:
|
||||||
|
response = loads(r.content)
|
||||||
|
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
# ValueError = r.content is Unparsable
|
||||||
|
# TypeError = r.content is None
|
||||||
|
# AttributeError = r is None
|
||||||
|
pass
|
||||||
|
|
||||||
|
# We can get a 200 response, but still fail. A failure message
|
||||||
|
# might look like this (missing bot permissions):
|
||||||
|
# {
|
||||||
|
# 'ok': False,
|
||||||
|
# 'error': 'missing_scope',
|
||||||
|
# 'needed': 'users:read.email',
|
||||||
|
# 'provided': 'calls:write,chat:write'
|
||||||
|
# }
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok \
|
||||||
|
or not (response and response.get('ok', False)):
|
||||||
|
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifySlack.http_response_code_lookup(
|
||||||
|
r.status_code, SLACK_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Slack User Lookup:'
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If we reach here, then we were successful in looking up
|
||||||
|
# the user. A response generally looks like this:
|
||||||
|
# {
|
||||||
|
# 'ok': True,
|
||||||
|
# 'user': {
|
||||||
|
# 'id': 'J1ZQB9T9Y',
|
||||||
|
# 'team_id': 'K1WR6TML2',
|
||||||
|
# 'name': 'l2g',
|
||||||
|
# 'deleted': False,
|
||||||
|
# 'color': '9f69e7',
|
||||||
|
# 'real_name': 'Chris C',
|
||||||
|
# 'tz': 'America/New_York',
|
||||||
|
# 'tz_label': 'Eastern Standard Time',
|
||||||
|
# 'tz_offset': -18000,
|
||||||
|
# 'profile': {
|
||||||
|
# 'title': '',
|
||||||
|
# 'phone': '',
|
||||||
|
# 'skype': '',
|
||||||
|
# 'real_name': 'Chris C',
|
||||||
|
# 'real_name_normalized':
|
||||||
|
# 'Chris C',
|
||||||
|
# 'display_name': 'l2g',
|
||||||
|
# 'display_name_normalized': 'l2g',
|
||||||
|
# 'fields': None,
|
||||||
|
# 'status_text': '',
|
||||||
|
# 'status_emoji': '',
|
||||||
|
# 'status_expiration': 0,
|
||||||
|
# 'avatar_hash': 'g785e9c0ddf6',
|
||||||
|
# 'email': 'lead2gold@gmail.com',
|
||||||
|
# 'first_name': 'Chris',
|
||||||
|
# 'last_name': 'C',
|
||||||
|
# 'image_24': 'https://secure.gravatar.com/...',
|
||||||
|
# 'image_32': 'https://secure.gravatar.com/...',
|
||||||
|
# 'image_48': 'https://secure.gravatar.com/...',
|
||||||
|
# 'image_72': 'https://secure.gravatar.com/...',
|
||||||
|
# 'image_192': 'https://secure.gravatar.com/...',
|
||||||
|
# 'image_512': 'https://secure.gravatar.com/...',
|
||||||
|
# 'status_text_canonical': '',
|
||||||
|
# 'team': 'K1WR6TML2'
|
||||||
|
# },
|
||||||
|
# 'is_admin': True,
|
||||||
|
# 'is_owner': True,
|
||||||
|
# 'is_primary_owner': True,
|
||||||
|
# 'is_restricted': False,
|
||||||
|
# 'is_ultra_restricted': False,
|
||||||
|
# 'is_bot': False,
|
||||||
|
# 'is_app_user': False,
|
||||||
|
# 'updated': 1603904274
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# We're only interested in the id
|
||||||
|
user_id = response['user']['id']
|
||||||
|
|
||||||
|
# Cache it for future
|
||||||
|
self._lookup_users[email] = user_id
|
||||||
|
self.logger.info(
|
||||||
|
'Email %s resolves to the Slack User ID: %s.', email, user_id)
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred looking up Slack User.',
|
||||||
|
)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
# Return; we're done
|
||||||
|
return None
|
||||||
|
|
||||||
|
return user_id
|
||||||
|
|
||||||
def _send(self, url, payload, attach=None, **kwargs):
|
def _send(self, url, payload, attach=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Wrapper to the requests (post) object
|
Wrapper to the requests (post) object
|
||||||
|
@ -477,6 +675,7 @@ class NotifySlack(NotifyBase):
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
|
'Accept': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
if not attach:
|
if not attach:
|
||||||
|
@ -486,7 +685,7 @@ class NotifySlack(NotifyBase):
|
||||||
headers['Authorization'] = 'Bearer {}'.format(self.access_token)
|
headers['Authorization'] = 'Bearer {}'.format(self.access_token)
|
||||||
|
|
||||||
# Our response object
|
# Our response object
|
||||||
response = None
|
response = {'ok': False}
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
# Always call throttle before any remote server i/o is made
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
@ -508,7 +707,28 @@ class NotifySlack(NotifyBase):
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.status_code != requests.codes.ok:
|
# Posts return a JSON string
|
||||||
|
try:
|
||||||
|
response = loads(r.content)
|
||||||
|
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
# ValueError = r.content is Unparsable
|
||||||
|
# TypeError = r.content is None
|
||||||
|
# AttributeError = r is None
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Another response type is:
|
||||||
|
# {
|
||||||
|
# 'ok': False,
|
||||||
|
# 'error': 'not_in_channel',
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# The text 'ok' is returned if this is a Webhook request
|
||||||
|
# So the below captures that as well.
|
||||||
|
status_okay = (response and response.get('ok', False)) \
|
||||||
|
if self.mode is SlackMode.BOT else r.text == 'ok'
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok or not status_okay:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifySlack.http_response_code_lookup(
|
NotifySlack.http_response_code_lookup(
|
||||||
|
@ -526,30 +746,6 @@ class NotifySlack(NotifyBase):
|
||||||
'Response Details:\r\n{}'.format(r.content))
|
'Response Details:\r\n{}'.format(r.content))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif attach:
|
|
||||||
# Attachment posts return a JSON string
|
|
||||||
try:
|
|
||||||
response = loads(r.content)
|
|
||||||
|
|
||||||
except (AttributeError, TypeError, ValueError):
|
|
||||||
# ValueError = r.content is Unparsable
|
|
||||||
# TypeError = r.content is None
|
|
||||||
# AttributeError = r is None
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not (response and response.get('ok', True)):
|
|
||||||
# Bare minimum requirements not met
|
|
||||||
self.logger.warning(
|
|
||||||
'Failed to send {}to Slack: error={}.'.format(
|
|
||||||
attach.name if attach else '',
|
|
||||||
r.status_code))
|
|
||||||
|
|
||||||
self.logger.debug(
|
|
||||||
'Response Details:\r\n{}'.format(r.content))
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
response = r.content
|
|
||||||
|
|
||||||
# Message Post Response looks like this:
|
# Message Post Response looks like this:
|
||||||
# {
|
# {
|
||||||
# "attachments": [
|
# "attachments": [
|
||||||
|
@ -658,14 +854,14 @@ class NotifySlack(NotifyBase):
|
||||||
# Extend our parameters
|
# Extend our parameters
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
if self.mode == SlackMode.WEBHOOK:
|
# Determine if there is a botname present
|
||||||
# Determine if there is a botname present
|
botname = ''
|
||||||
botname = ''
|
if self.user:
|
||||||
if self.user:
|
botname = '{botname}@'.format(
|
||||||
botname = '{botname}@'.format(
|
botname=NotifySlack.quote(self.user, safe=''),
|
||||||
botname=NotifySlack.quote(self.user, safe=''),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
if self.mode == SlackMode.WEBHOOK:
|
||||||
return '{schema}://{botname}{token_a}/{token_b}/{token_c}/'\
|
return '{schema}://{botname}{token_a}/{token_b}/{token_c}/'\
|
||||||
'{targets}/?{params}'.format(
|
'{targets}/?{params}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
|
@ -679,9 +875,10 @@ class NotifySlack(NotifyBase):
|
||||||
params=NotifySlack.urlencode(params),
|
params=NotifySlack.urlencode(params),
|
||||||
)
|
)
|
||||||
# else -> self.mode == SlackMode.BOT:
|
# else -> self.mode == SlackMode.BOT:
|
||||||
return '{schema}://{access_token}/{targets}/'\
|
return '{schema}://{botname}{access_token}/{targets}/'\
|
||||||
'?{params}'.format(
|
'?{params}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
|
botname=botname,
|
||||||
access_token=self.pprint(self.access_token, privacy, safe=''),
|
access_token=self.pprint(self.access_token, privacy, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[NotifySlack.quote(x, safe='') for x in self.channels]),
|
[NotifySlack.quote(x, safe='') for x in self.channels]),
|
||||||
|
@ -714,25 +911,36 @@ class NotifySlack(NotifyBase):
|
||||||
else:
|
else:
|
||||||
# We're dealing with a webhook
|
# We're dealing with a webhook
|
||||||
results['token_a'] = token
|
results['token_a'] = token
|
||||||
|
results['token_b'] = entries.pop(0) if entries else None
|
||||||
# Now fetch the remaining tokens
|
results['token_c'] = entries.pop(0) if entries else None
|
||||||
try:
|
|
||||||
results['token_b'] = entries.pop(0)
|
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
# We're done
|
|
||||||
results['token_b'] = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
results['token_c'] = entries.pop(0)
|
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
# We're done
|
|
||||||
results['token_c'] = None
|
|
||||||
|
|
||||||
# assign remaining entries to the channels we wish to notify
|
# assign remaining entries to the channels we wish to notify
|
||||||
results['targets'] = entries
|
results['targets'] = entries
|
||||||
|
|
||||||
|
# Support the token flag where you can set it to the bot token
|
||||||
|
# or the webhook token (with slash delimiters)
|
||||||
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
|
# Break our entries up into a list; we can ue the Channel
|
||||||
|
# list delimiter above since it doesn't contain any characters
|
||||||
|
# we don't otherwise accept anyway in our token
|
||||||
|
entries = [x for x in filter(
|
||||||
|
bool, CHANNEL_LIST_DELIM.split(
|
||||||
|
NotifySlack.unquote(results['qsd']['token'])))]
|
||||||
|
|
||||||
|
# check to see if we're dealing with a bot/user token
|
||||||
|
if entries and entries[0].startswith('xo'):
|
||||||
|
# We're dealing with a bot
|
||||||
|
results['access_token'] = entries[0]
|
||||||
|
results['token_a'] = None
|
||||||
|
results['token_b'] = None
|
||||||
|
results['token_c'] = None
|
||||||
|
|
||||||
|
else: # Webhook
|
||||||
|
results['access_token'] = None
|
||||||
|
results['token_a'] = entries.pop(0) if entries else None
|
||||||
|
results['token_b'] = entries.pop(0) if entries else None
|
||||||
|
results['token_c'] = entries.pop(0) if entries else None
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support rooms this way too
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
|
117
test/test_api.py
117
test/test_api.py
|
@ -1270,59 +1270,90 @@ def test_apprise_details_plugin_verification():
|
||||||
assert len(arg['delim']) > 0
|
assert len(arg['delim']) > 0
|
||||||
|
|
||||||
else: # alias_of is in the object
|
else: # alias_of is in the object
|
||||||
# must be a string
|
|
||||||
assert isinstance(arg['alias_of'], six.string_types)
|
|
||||||
# Track our alias_of object
|
|
||||||
map_to_aliases.add(arg['alias_of'])
|
|
||||||
|
|
||||||
# Ensure we're not already in the tokens section
|
# Ensure we're not already in the tokens section
|
||||||
# The alias_of object has no value here
|
# The alias_of object has no value here
|
||||||
assert section != 'tokens'
|
assert section != 'tokens'
|
||||||
|
|
||||||
# We can't be an alias_of ourselves
|
# must be a string
|
||||||
if key == arg['alias_of']:
|
assert isinstance(
|
||||||
# This is acceptable as long as we exist in the tokens
|
arg['alias_of'], (six.string_types, list, tuple, set))
|
||||||
# table because that is truely what we map back to
|
|
||||||
assert key in entry['details']['tokens']
|
|
||||||
|
|
||||||
else:
|
aliases = [arg['alias_of']] \
|
||||||
# Throw the problem into an assert tag for debugging
|
if isinstance(arg['alias_of'], six.string_types) \
|
||||||
# purposes... the mapping is not acceptable
|
else arg['alias_of']
|
||||||
assert key != arg['alias_of']
|
|
||||||
|
|
||||||
# alias_of always references back to tokens
|
for alias_of in aliases:
|
||||||
assert \
|
# Track our alias_of object
|
||||||
arg['alias_of'] in entry['details']['tokens'] or \
|
map_to_aliases.add(alias_of)
|
||||||
arg['alias_of'] in entry['details']['args']
|
|
||||||
|
|
||||||
# Find a list directive in our tokens
|
# We can't be an alias_of ourselves
|
||||||
t_match = entry['details']['tokens']\
|
if key == alias_of:
|
||||||
.get(arg['alias_of'], {})\
|
# This is acceptable as long as we exist in the
|
||||||
.get('type', '').startswith('list')
|
# tokens table because that is truely what we map
|
||||||
|
# back to
|
||||||
|
assert key in entry['details']['tokens']
|
||||||
|
|
||||||
a_match = entry['details']['args']\
|
else:
|
||||||
.get(arg['alias_of'], {})\
|
# Throw the problem into an assert tag for
|
||||||
.get('type', '').startswith('list')
|
# debugging purposes... the mapping is not
|
||||||
|
# acceptable
|
||||||
|
assert key != alias_of
|
||||||
|
|
||||||
if not (t_match or a_match):
|
# alias_of always references back to tokens
|
||||||
# Ensure the only token we have is the alias_of
|
assert \
|
||||||
assert len(entry['details'][section][key]) == 1
|
alias_of in entry['details']['tokens'] or \
|
||||||
|
alias_of in entry['details']['args']
|
||||||
|
|
||||||
else:
|
# Find a list directive in our tokens
|
||||||
# We're a list, we allow up to 2 variables
|
t_match = entry['details']['tokens']\
|
||||||
# Obviously we have the alias_of entry; that's why
|
.get(alias_of, {})\
|
||||||
# were at this part of the code. But we can
|
.get('type', '').startswith('list')
|
||||||
# additionally provide a 'delim' over-ride.
|
|
||||||
assert len(entry['details'][section][key]) <= 2
|
a_match = entry['details']['args']\
|
||||||
if len(entry['details'][section][key]) == 2:
|
.get(alias_of, {})\
|
||||||
# Verify that it is in fact the 'delim' tag
|
.get('type', '').startswith('list')
|
||||||
assert 'delim' in entry['details'][section][key]
|
|
||||||
# If we do have a delim value set, it must be of
|
if not (t_match or a_match):
|
||||||
# a list/set/tuple type
|
# Ensure the only token we have is the alias_of
|
||||||
assert isinstance(
|
# hence record should look like as example):
|
||||||
entry['details'][section][key]['delim'],
|
# {
|
||||||
(tuple, set, list),
|
# 'token': {
|
||||||
)
|
# 'alias_of': 'apitoken',
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Or if it can represent more then one entry; in
|
||||||
|
# this case, one must define a name (to define
|
||||||
|
# grouping).
|
||||||
|
# {
|
||||||
|
# 'token': {
|
||||||
|
# 'name': 'Tokens',
|
||||||
|
# 'alias_of': ('apitoken', 'webtoken'),
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
if isinstance(arg['alias_of'], six.string_types):
|
||||||
|
assert len(entry['details'][section][key]) == 1
|
||||||
|
else: # is tuple,list, or set
|
||||||
|
assert len(entry['details'][section][key]) == 2
|
||||||
|
# Must have a name defined to define grouping
|
||||||
|
assert 'name' in entry['details'][section][key]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# We're a list, we allow up to 2 variables
|
||||||
|
# Obviously we have the alias_of entry; that's why
|
||||||
|
# were at this part of the code. But we can
|
||||||
|
# additionally provide a 'delim' over-ride.
|
||||||
|
assert len(entry['details'][section][key]) <= 2
|
||||||
|
if len(entry['details'][section][key]) == 2:
|
||||||
|
# Verify that it is in fact the 'delim' tag
|
||||||
|
assert 'delim' in \
|
||||||
|
entry['details'][section][key]
|
||||||
|
# If we do have a delim value set, it must be
|
||||||
|
# of a list/set/tuple type
|
||||||
|
assert isinstance(
|
||||||
|
entry['details'][section][key]['delim'],
|
||||||
|
(tuple, set, list),
|
||||||
|
)
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
# inspect our object
|
# inspect our object
|
||||||
|
|
|
@ -433,9 +433,10 @@ def test_apprise_cli_nux_env(tmpdir):
|
||||||
|
|
||||||
with mock.patch('apprise.cli.DEFAULT_SEARCH_PATHS', []):
|
with mock.patch('apprise.cli.DEFAULT_SEARCH_PATHS', []):
|
||||||
with environ(APPRISE_URLS=" "):
|
with environ(APPRISE_URLS=" "):
|
||||||
# An empty string is not valid and therefore not loaded so the below
|
# An empty string is not valid and therefore not loaded so the
|
||||||
# fails. We override the DEFAULT_SEARCH_PATHS because we don't
|
# below fails. We override the DEFAULT_SEARCH_PATHS because we
|
||||||
# want to detect ones loaded on the machine running the unit tests
|
# don't want to detect ones loaded on the machine running the unit
|
||||||
|
# tests
|
||||||
result = runner.invoke(cli.main, [
|
result = runner.invoke(cli.main, [
|
||||||
'-b', 'test environment',
|
'-b', 'test environment',
|
||||||
])
|
])
|
||||||
|
|
|
@ -4070,19 +4070,13 @@ TEST_URLS = (
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
# don't include an image by default
|
# don't include an image by default
|
||||||
'include_image': False,
|
'include_image': False,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok'
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/+id/@id/', {
|
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/+id/@id/', {
|
||||||
# + encoded id,
|
# + encoded id,
|
||||||
# @ userid
|
# @ userid
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/'
|
||||||
'?to=#nuxref', {
|
'?to=#nuxref', {
|
||||||
|
@ -4090,17 +4084,35 @@ TEST_URLS = (
|
||||||
|
|
||||||
# Our expected url(privacy=True) startswith() response:
|
# Our expected url(privacy=True) startswith() response:
|
||||||
'privacy_url': 'slack://username@T...2/A...D/T...Q/',
|
'privacy_url': 'slack://username@T...2/A...D/T...Q/',
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
}),
|
||||||
'message': '',
|
# You can't send to email using webhook
|
||||||
},
|
('slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/user@gmail.com', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': 'ok',
|
||||||
|
# we'll have a notify response failure in this case
|
||||||
|
'notify_response': False,
|
||||||
|
}),
|
||||||
|
# Specify Token on argument string (with username)
|
||||||
|
('slack://bot@_/#nuxref?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnadfdajkjkfl/', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': 'ok',
|
||||||
|
}),
|
||||||
|
# Specify Token and channels on argument string (no username)
|
||||||
|
('slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/&to=#chan', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': 'ok',
|
||||||
|
}),
|
||||||
|
# Test webhook that doesn't have a proper response
|
||||||
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#nuxref', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': 'fail',
|
||||||
|
# we'll have a notify response failure in this case
|
||||||
|
'notify_response': False,
|
||||||
}),
|
}),
|
||||||
# Test using a bot-token (also test footer set to no flag)
|
# Test using a bot-token (also test footer set to no flag)
|
||||||
('slack://username@xoxb-1234-1234-abc124/#nuxref?footer=no', {
|
('slack://username@xoxb-1234-1234-abc124/#nuxref?footer=no', {
|
||||||
|
@ -4114,7 +4126,34 @@ TEST_URLS = (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
# Test using a bot-token as argument
|
||||||
|
('slack://?token=xoxb-1234-1234-abc124&to=#nuxref&footer=no&user=test', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': {
|
||||||
|
'ok': True,
|
||||||
|
'message': '',
|
||||||
|
# support attachments
|
||||||
|
'file': {
|
||||||
|
'url_private': 'http://localhost/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'slack://test@x...4/nuxref/',
|
||||||
|
}),
|
||||||
|
# We contain 1 or more invalid channels, so we'll fail on our notify call
|
||||||
|
('slack://?token=xoxb-1234-1234-abc124&to=#nuxref,#$,#-&footer=no', {
|
||||||
|
'instance': plugins.NotifySlack,
|
||||||
|
'requests_response_text': {
|
||||||
|
'ok': True,
|
||||||
|
'message': '',
|
||||||
|
# support attachments
|
||||||
|
'file': {
|
||||||
|
'url_private': 'http://localhost/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# We fail because of the empty channel #$ and #-
|
||||||
|
'notify_response': False,
|
||||||
|
}),
|
||||||
('slack://username@xoxb-1234-1234-abc124/#nuxref', {
|
('slack://username@xoxb-1234-1234-abc124/#nuxref', {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': {
|
||||||
|
@ -4129,28 +4168,19 @@ TEST_URLS = (
|
||||||
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ', {
|
('slack://username@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ', {
|
||||||
# Missing a channel, falls back to webhook channel bindings
|
# Missing a channel, falls back to webhook channel bindings
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
# Native URL Support, take the slack URL and still build from it
|
# Native URL Support, take the slack URL and still build from it
|
||||||
('https://hooks.slack.com/services/{}/{}/{}'.format(
|
('https://hooks.slack.com/services/{}/{}/{}'.format(
|
||||||
'A' * 9, 'B' * 9, 'c' * 24), {
|
'A' * 9, 'B' * 9, 'c' * 24), {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
# Native URL Support with arguments
|
# Native URL Support with arguments
|
||||||
('https://hooks.slack.com/services/{}/{}/{}?format=text'.format(
|
('https://hooks.slack.com/services/{}/{}/{}?format=text'.format(
|
||||||
'A' * 9, 'B' * 9, 'c' * 24), {
|
'A' * 9, 'B' * 9, 'c' * 24), {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': True,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://username@-INVALID-/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
|
('slack://username@-INVALID-/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
|
||||||
# invalid 1st Token
|
# invalid 1st Token
|
||||||
|
@ -4169,30 +4199,21 @@ TEST_URLS = (
|
||||||
# force a failure
|
# force a failure
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': requests.codes.internal_server_error,
|
'requests_response_code': requests.codes.internal_server_error,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': False,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://respect@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#a', {
|
('slack://respect@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#a', {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
# throw a bizzare code forcing us to fail to look it up
|
# throw a bizzare code forcing us to fail to look it up
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': 999,
|
'requests_response_code': 999,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': False,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
('slack://notify@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#b', {
|
('slack://notify@T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#b', {
|
||||||
'instance': plugins.NotifySlack,
|
'instance': plugins.NotifySlack,
|
||||||
# Throws a series of connection and transfer exceptions when this flag
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
# is set and tests that we gracfully handle them
|
# is set and tests that we gracfully handle them
|
||||||
'test_requests_exceptions': True,
|
'test_requests_exceptions': True,
|
||||||
'requests_response_text': {
|
'requests_response_text': 'ok',
|
||||||
'ok': False,
|
|
||||||
'message': '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
@ -5194,6 +5215,8 @@ def test_rest_plugins(mock_post, mock_get):
|
||||||
# Handle our default text response
|
# Handle our default text response
|
||||||
mock_get.return_value.content = requests_response_text
|
mock_get.return_value.content = requests_response_text
|
||||||
mock_post.return_value.content = requests_response_text
|
mock_post.return_value.content = requests_response_text
|
||||||
|
mock_get.return_value.text = requests_response_text
|
||||||
|
mock_post.return_value.text = requests_response_text
|
||||||
|
|
||||||
# Ensure there is no side effect set
|
# Ensure there is no side effect set
|
||||||
mock_post.side_effect = None
|
mock_post.side_effect = None
|
||||||
|
|
|
@ -85,12 +85,39 @@ def test_slack_oauth_access_token(mock_post):
|
||||||
assert obj.send(body="test") is True
|
assert obj.send(body="test") is True
|
||||||
|
|
||||||
# Test Valid Attachment
|
# Test Valid Attachment
|
||||||
|
mock_post.reset_mock()
|
||||||
|
|
||||||
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||||
attach = AppriseAttachment(path)
|
attach = AppriseAttachment(path)
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
attach=attach) is True
|
attach=attach) is True
|
||||||
|
|
||||||
|
assert mock_post.call_count == 2
|
||||||
|
assert mock_post.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/chat.postMessage'
|
||||||
|
assert mock_post.call_args_list[1][0][0] == \
|
||||||
|
'https://slack.com/api/files.upload'
|
||||||
|
|
||||||
|
# Test a valid attachment that throws an Connection Error
|
||||||
|
mock_post.return_value = None
|
||||||
|
mock_post.side_effect = (request, requests.ConnectionError(
|
||||||
|
0, 'requests.ConnectionError() not handled'))
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is False
|
||||||
|
|
||||||
|
# Test a valid attachment that throws an OSError
|
||||||
|
mock_post.return_value = None
|
||||||
|
mock_post.side_effect = (request, OSError(0, 'OSError'))
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is False
|
||||||
|
|
||||||
|
# Reset our mock object back to how it was
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_post.side_effect = None
|
||||||
|
|
||||||
# Test invalid attachment
|
# Test invalid attachment
|
||||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
|
@ -135,6 +162,8 @@ def test_slack_oauth_access_token(mock_post):
|
||||||
# We'll fail now because of an internal exception
|
# We'll fail now because of an internal exception
|
||||||
assert obj.send(body="test") is False
|
assert obj.send(body="test") is False
|
||||||
|
|
||||||
|
# Test Email Lookup
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
def test_slack_webhook(mock_post):
|
def test_slack_webhook(mock_post):
|
||||||
|
@ -148,9 +177,8 @@ def test_slack_webhook(mock_post):
|
||||||
# Prepare Mock
|
# Prepare Mock
|
||||||
mock_post.return_value = requests.Request()
|
mock_post.return_value = requests.Request()
|
||||||
mock_post.return_value.status_code = requests.codes.ok
|
mock_post.return_value.status_code = requests.codes.ok
|
||||||
mock_post.return_value.content = dumps({
|
mock_post.return_value.content = 'ok'
|
||||||
'ok': True,
|
mock_post.return_value.text = 'ok'
|
||||||
})
|
|
||||||
|
|
||||||
# Initialize some generic (but valid) tokens
|
# Initialize some generic (but valid) tokens
|
||||||
token_a = 'A' * 9
|
token_a = 'A' * 9
|
||||||
|
@ -182,3 +210,220 @@ def test_slack_webhook(mock_post):
|
||||||
# This call includes an image with it's payload:
|
# This call includes an image with it's payload:
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
body='body', title='title', notify_type=NotifyType.INFO) is True
|
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('requests.post')
|
||||||
|
@mock.patch('requests.get')
|
||||||
|
def test_slack_send_by_email(mock_get, mock_post):
|
||||||
|
"""
|
||||||
|
API: NotifySlack() Send by Email Tests
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Disable Throttling to speed testing
|
||||||
|
plugins.NotifyBase.request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Generate a (valid) bot token
|
||||||
|
token = 'xoxb-1234-1234-abc124'
|
||||||
|
|
||||||
|
request = mock.Mock()
|
||||||
|
request.content = dumps({
|
||||||
|
'ok': True,
|
||||||
|
'message': '',
|
||||||
|
'user': {
|
||||||
|
'id': 'ABCD1234'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
request.status_code = requests.codes.ok
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.return_value = request
|
||||||
|
|
||||||
|
# Variation Initializations
|
||||||
|
obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com')
|
||||||
|
assert isinstance(obj, plugins.NotifySlack) is True
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# No calls made yet
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
|
||||||
|
# Send our notification
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||||
|
|
||||||
|
# 2 calls were made, one to perform an email lookup, the second
|
||||||
|
# was the notification itself
|
||||||
|
assert mock_get.call_count == 1
|
||||||
|
assert mock_post.call_count == 1
|
||||||
|
assert mock_get.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/users.lookupByEmail'
|
||||||
|
assert mock_post.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/chat.postMessage'
|
||||||
|
|
||||||
|
# Reset our mock object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
mock_get.reset_mock()
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.return_value = request
|
||||||
|
|
||||||
|
# Send our notification again (cached copy of user id associated with
|
||||||
|
# email is used)
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is True
|
||||||
|
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
assert mock_post.call_count == 1
|
||||||
|
assert mock_post.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/chat.postMessage'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Now test a case where we can't look up the valid email
|
||||||
|
#
|
||||||
|
request.content = dumps({
|
||||||
|
'ok': False,
|
||||||
|
'message': '',
|
||||||
|
})
|
||||||
|
|
||||||
|
# Reset our mock object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
mock_get.reset_mock()
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.return_value = request
|
||||||
|
|
||||||
|
# Variation Initializations
|
||||||
|
obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com')
|
||||||
|
assert isinstance(obj, plugins.NotifySlack) is True
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# No calls made yet
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
|
||||||
|
# Send our notification; it will fail because we failed to look up
|
||||||
|
# the user id
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# We would have failed to look up the email, therefore we wouldn't have
|
||||||
|
# even bothered to attempt to send the notification
|
||||||
|
assert mock_get.call_count == 1
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/users.lookupByEmail'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Now test a case where we have a poorly formatted JSON response
|
||||||
|
#
|
||||||
|
request.content = '}'
|
||||||
|
|
||||||
|
# Reset our mock object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
mock_get.reset_mock()
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.return_value = request
|
||||||
|
|
||||||
|
# Variation Initializations
|
||||||
|
obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com')
|
||||||
|
assert isinstance(obj, plugins.NotifySlack) is True
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# No calls made yet
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
|
||||||
|
# Send our notification; it will fail because we failed to look up
|
||||||
|
# the user id
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# We would have failed to look up the email, therefore we wouldn't have
|
||||||
|
# even bothered to attempt to send the notification
|
||||||
|
assert mock_get.call_count == 1
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/users.lookupByEmail'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Now test a case where we have a poorly formatted JSON response
|
||||||
|
#
|
||||||
|
request.content = '}'
|
||||||
|
|
||||||
|
# Reset our mock object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
mock_get.reset_mock()
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.return_value = request
|
||||||
|
|
||||||
|
# Variation Initializations
|
||||||
|
obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com')
|
||||||
|
assert isinstance(obj, plugins.NotifySlack) is True
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# No calls made yet
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
|
||||||
|
# Send our notification; it will fail because we failed to look up
|
||||||
|
# the user id
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# We would have failed to look up the email, therefore we wouldn't have
|
||||||
|
# even bothered to attempt to send the notification
|
||||||
|
assert mock_get.call_count == 1
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/users.lookupByEmail'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Now test a case where we throw an exception trying to perform the lookup
|
||||||
|
#
|
||||||
|
|
||||||
|
request.content = dumps({
|
||||||
|
'ok': True,
|
||||||
|
'message': '',
|
||||||
|
'user': {
|
||||||
|
'id': 'ABCD1234'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
# Create an unauthorized response
|
||||||
|
request.status_code = requests.codes.ok
|
||||||
|
|
||||||
|
# Reset our mock object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
mock_get.reset_mock()
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = request
|
||||||
|
mock_get.side_effect = requests.ConnectionError(
|
||||||
|
0, 'requests.ConnectionError() not handled')
|
||||||
|
|
||||||
|
# Variation Initializations
|
||||||
|
obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com')
|
||||||
|
assert isinstance(obj, plugins.NotifySlack) is True
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# No calls made yet
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_count == 0
|
||||||
|
|
||||||
|
# Send our notification; it will fail because we failed to look up
|
||||||
|
# the user id
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# We would have failed to look up the email, therefore we wouldn't have
|
||||||
|
# even bothered to attempt to send the notification
|
||||||
|
assert mock_get.call_count == 1
|
||||||
|
assert mock_post.call_count == 0
|
||||||
|
assert mock_get.call_args_list[0][0][0] == \
|
||||||
|
'https://slack.com/api/users.lookupByEmail'
|
||||||
|
|
Loading…
Reference in New Issue