mirror of https://github.com/caronc/apprise
				
				
				
			
		
			
				
	
	
		
			371 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			371 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
# -*- coding: utf-8 -*-
 | 
						|
#
 | 
						|
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
 | 
						|
# All rights reserved.
 | 
						|
#
 | 
						|
# This code is licensed under the MIT License.
 | 
						|
#
 | 
						|
# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
# of this software and associated documentation files(the "Software"), to deal
 | 
						|
# in the Software without restriction, including without limitation the rights
 | 
						|
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
 | 
						|
# copies of the Software, and to permit persons to whom the Software is
 | 
						|
# furnished to do so, subject to the following conditions :
 | 
						|
#
 | 
						|
# The above copyright notice and this permission notice shall be included in
 | 
						|
# all copies or substantial portions of the Software.
 | 
						|
#
 | 
						|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
 | 
						|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
						|
# THE SOFTWARE.
 | 
						|
 | 
						|
import mock
 | 
						|
import pytest
 | 
						|
import requests
 | 
						|
from apprise import plugins
 | 
						|
from apprise import Apprise
 | 
						|
 | 
						|
# Disable logging for a cleaner testing output
 | 
						|
import logging
 | 
						|
logging.disable(logging.CRITICAL)
 | 
						|
 | 
						|
 | 
						|
TEST_ACCESS_KEY_ID = 'AHIAJGNT76XIMXDBIJYA'
 | 
						|
TEST_ACCESS_KEY_SECRET = 'bu1dHSdO22pfaaVy/wmNsdljF4C07D3bndi9PQJ9'
 | 
						|
TEST_REGION = 'us-east-2'
 | 
						|
 | 
						|
 | 
						|
def test_object_initialization():
 | 
						|
    """
 | 
						|
    API: NotifySNS Plugin() initialization
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    # Initializes the plugin with a valid access, but invalid access key
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # No access_key_id specified
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=None,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets='+1800555999',
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # No secret_access_key specified
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=None,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets='+1800555999',
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # No region_name specified
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=None,
 | 
						|
            targets='+1800555999',
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # No recipients
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets=None,
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # No recipients - garbage recipients object
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets=object(),
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # The phone number is invalid, and without it, there is nothing
 | 
						|
        # to notify
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets='+1809',
 | 
						|
        )
 | 
						|
 | 
						|
    with pytest.raises(TypeError):
 | 
						|
        # The phone number is invalid, and without it, there is nothing
 | 
						|
        # to notify; we
 | 
						|
        plugins.NotifySNS(
 | 
						|
            access_key_id=TEST_ACCESS_KEY_ID,
 | 
						|
            secret_access_key=TEST_ACCESS_KEY_SECRET,
 | 
						|
            region_name=TEST_REGION,
 | 
						|
            targets='#(invalid-topic-because-of-the-brackets)',
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
def test_url_parsing():
 | 
						|
    """
 | 
						|
    API: NotifySNS Plugin() URL Parsing
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    # No recipients
 | 
						|
    results = plugins.NotifySNS.parse_url('sns://%s/%s/%s/' % (
 | 
						|
        TEST_ACCESS_KEY_ID,
 | 
						|
        TEST_ACCESS_KEY_SECRET,
 | 
						|
        TEST_REGION)
 | 
						|
    )
 | 
						|
 | 
						|
    # Confirm that there were no recipients found
 | 
						|
    assert len(results['targets']) == 0
 | 
						|
    assert 'region_name' in results
 | 
						|
    assert TEST_REGION == results['region_name']
 | 
						|
    assert 'access_key_id' in results
 | 
						|
    assert TEST_ACCESS_KEY_ID == results['access_key_id']
 | 
						|
    assert 'secret_access_key' in results
 | 
						|
    assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
 | 
						|
 | 
						|
    # Detect recipients
 | 
						|
    results = plugins.NotifySNS.parse_url('sns://%s/%s/%s/%s/%s/' % (
 | 
						|
        TEST_ACCESS_KEY_ID,
 | 
						|
        TEST_ACCESS_KEY_SECRET,
 | 
						|
        # Uppercase Region won't break anything
 | 
						|
        TEST_REGION.upper(),
 | 
						|
        '+18001234567',
 | 
						|
        'MyTopic')
 | 
						|
    )
 | 
						|
 | 
						|
    # Confirm that our recipients were found
 | 
						|
    assert len(results['targets']) == 2
 | 
						|
    assert '+18001234567' in results['targets']
 | 
						|
    assert 'MyTopic' in results['targets']
 | 
						|
    assert 'region_name' in results
 | 
						|
    assert TEST_REGION == results['region_name']
 | 
						|
    assert 'access_key_id' in results
 | 
						|
    assert TEST_ACCESS_KEY_ID == results['access_key_id']
 | 
						|
    assert 'secret_access_key' in results
 | 
						|
    assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
 | 
						|
 | 
						|
 | 
						|
def test_object_parsing():
 | 
						|
    """
 | 
						|
    API: NotifySNS Plugin() Object Parsing
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    # Create our object
 | 
						|
    a = Apprise()
 | 
						|
 | 
						|
    # Now test failing variations of our URL
 | 
						|
    assert a.add('sns://') is False
 | 
						|
    assert a.add('sns://nosecret') is False
 | 
						|
    assert a.add('sns://nosecret/noregion/') is False
 | 
						|
 | 
						|
    # This is valid but without valid recipients, the URL is actually useless
 | 
						|
    assert a.add('sns://norecipient/norecipient/us-west-2') is False
 | 
						|
    assert len(a) == 0
 | 
						|
 | 
						|
    # Parse a good one
 | 
						|
    assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
 | 
						|
    assert len(a) == 1
 | 
						|
 | 
						|
    assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
 | 
						|
    assert len(a) == 2
 | 
						|
 | 
						|
 | 
						|
def test_aws_response_handling():
 | 
						|
    """
 | 
						|
    API: NotifySNS Plugin() AWS Response Handling
 | 
						|
 | 
						|
    """
 | 
						|
    # Not a string
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(None)
 | 
						|
    assert response['type'] is None
 | 
						|
    assert response['request_id'] is None
 | 
						|
 | 
						|
    # Invalid XML
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(
 | 
						|
        '<Bad Response xmlns="http://sns.amazonaws.com/doc/2010-03-31/">')
 | 
						|
    assert response['type'] is None
 | 
						|
    assert response['request_id'] is None
 | 
						|
 | 
						|
    # Single Element in XML
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(
 | 
						|
        '<SingleElement></SingleElement>')
 | 
						|
    assert response['type'] == 'SingleElement'
 | 
						|
    assert response['request_id'] is None
 | 
						|
 | 
						|
    # Empty String
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict('')
 | 
						|
    assert response['type'] is None
 | 
						|
    assert response['request_id'] is None
 | 
						|
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(
 | 
						|
        """
 | 
						|
        <PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
 | 
						|
            <PublishResult>
 | 
						|
                <MessageId>5e16935a-d1fb-5a31-a716-c7805e5c1d2e</MessageId>
 | 
						|
            </PublishResult>
 | 
						|
            <ResponseMetadata>
 | 
						|
                <RequestId>dc258024-d0e6-56bb-af1b-d4fe5f4181a4</RequestId>
 | 
						|
            </ResponseMetadata>
 | 
						|
        </PublishResponse>
 | 
						|
        """)
 | 
						|
    assert response['type'] == 'PublishResponse'
 | 
						|
    assert response['request_id'] == 'dc258024-d0e6-56bb-af1b-d4fe5f4181a4'
 | 
						|
    assert response['message_id'] == '5e16935a-d1fb-5a31-a716-c7805e5c1d2e'
 | 
						|
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(
 | 
						|
        """
 | 
						|
         <CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
 | 
						|
           <CreateTopicResult>
 | 
						|
             <TopicArn>arn:aws:sns:us-east-1:000000000000:abcd</TopicArn>
 | 
						|
                </CreateTopicResult>
 | 
						|
            <ResponseMetadata>
 | 
						|
                <RequestId>604bef0f-369c-50c5-a7a4-bbd474c83d6a</RequestId>
 | 
						|
            </ResponseMetadata>
 | 
						|
        </CreateTopicResponse>
 | 
						|
        """)
 | 
						|
    assert response['type'] == 'CreateTopicResponse'
 | 
						|
    assert response['request_id'] == '604bef0f-369c-50c5-a7a4-bbd474c83d6a'
 | 
						|
    assert response['topic_arn'] == 'arn:aws:sns:us-east-1:000000000000:abcd'
 | 
						|
 | 
						|
    response = plugins.NotifySNS.aws_response_to_dict(
 | 
						|
        """
 | 
						|
        <ErrorResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
 | 
						|
            <Error>
 | 
						|
                <Type>Sender</Type>
 | 
						|
                <Code>InvalidParameter</Code>
 | 
						|
                <Message>Invalid parameter: TopicArn or TargetArn Reason:
 | 
						|
                no value for required parameter</Message>
 | 
						|
            </Error>
 | 
						|
            <RequestId>b5614883-babe-56ca-93b2-1c592ba6191e</RequestId>
 | 
						|
        </ErrorResponse>
 | 
						|
        """)
 | 
						|
    assert response['type'] == 'ErrorResponse'
 | 
						|
    assert response['request_id'] == 'b5614883-babe-56ca-93b2-1c592ba6191e'
 | 
						|
    assert response['error_type'] == 'Sender'
 | 
						|
    assert response['error_code'] == 'InvalidParameter'
 | 
						|
    assert response['error_message'].startswith('Invalid parameter:')
 | 
						|
    assert response['error_message'].endswith('required parameter')
 | 
						|
 | 
						|
 | 
						|
@mock.patch('requests.post')
 | 
						|
def test_aws_topic_handling(mock_post):
 | 
						|
    """
 | 
						|
    API: NotifySNS Plugin() AWS Topic Handling
 | 
						|
 | 
						|
    """
 | 
						|
    # Disable Throttling to speed testing
 | 
						|
    plugins.NotifySNS.request_rate_per_sec = 0
 | 
						|
 | 
						|
    arn_response = \
 | 
						|
        """
 | 
						|
         <CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
 | 
						|
           <CreateTopicResult>
 | 
						|
             <TopicArn>arn:aws:sns:us-east-1:000000000000:abcd</TopicArn>
 | 
						|
                </CreateTopicResult>
 | 
						|
            <ResponseMetadata>
 | 
						|
                <RequestId>604bef0f-369c-50c5-a7a4-bbd474c83d6a</RequestId>
 | 
						|
            </ResponseMetadata>
 | 
						|
        </CreateTopicResponse>
 | 
						|
        """
 | 
						|
 | 
						|
    def post(url, data, **kwargs):
 | 
						|
        """
 | 
						|
        Since Publishing a token requires 2 posts, we need to return our
 | 
						|
        response depending on what step we're on
 | 
						|
        """
 | 
						|
 | 
						|
        # A request
 | 
						|
        robj = mock.Mock()
 | 
						|
        robj.content = ''
 | 
						|
        robj.status_code = requests.codes.ok
 | 
						|
 | 
						|
        if data.find('=CreateTopic') >= 0:
 | 
						|
            # Topic Post Failure
 | 
						|
            robj.status_code = requests.codes.bad_request
 | 
						|
 | 
						|
        return robj
 | 
						|
 | 
						|
    # Assign ourselves a new function
 | 
						|
    mock_post.side_effect = post
 | 
						|
 | 
						|
    # Create our object
 | 
						|
    a = Apprise()
 | 
						|
 | 
						|
    a.add([
 | 
						|
        # Single Topic
 | 
						|
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/us-west-2/TopicA',
 | 
						|
        # Multi-Topic
 | 
						|
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/us-east-1/TopicA/TopicB/'
 | 
						|
        # Topic-Mix
 | 
						|
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkce/us-west-2/' \
 | 
						|
        '12223334444/TopicA'])
 | 
						|
 | 
						|
    # CreateTopic fails
 | 
						|
    assert a.notify(title='', body='test') is False
 | 
						|
 | 
						|
    def post(url, data, **kwargs):
 | 
						|
        """
 | 
						|
        Since Publishing a token requires 2 posts, we need to return our
 | 
						|
        response depending on what step we're on
 | 
						|
        """
 | 
						|
 | 
						|
        # A request
 | 
						|
        robj = mock.Mock()
 | 
						|
        robj.content = ''
 | 
						|
        robj.status_code = requests.codes.ok
 | 
						|
 | 
						|
        if data.find('=CreateTopic') >= 0:
 | 
						|
            robj.content = arn_response
 | 
						|
 | 
						|
        # Manipulate Topic Publishing only (not phone)
 | 
						|
        elif data.find('=Publish') >= 0 and data.find('TopicArn=') >= 0:
 | 
						|
            # Topic Post Failure
 | 
						|
            robj.status_code = requests.codes.bad_request
 | 
						|
 | 
						|
        return robj
 | 
						|
 | 
						|
    # Assign ourselves a new function
 | 
						|
    mock_post.side_effect = post
 | 
						|
 | 
						|
    # Publish fails
 | 
						|
    assert a.notify(title='', body='test') is False
 | 
						|
 | 
						|
    # Disable our side effect
 | 
						|
    mock_post.side_effect = None
 | 
						|
 | 
						|
    # Handle case where TopicArn is missing:
 | 
						|
    robj = mock.Mock()
 | 
						|
    robj.content = "<CreateTopicResponse></CreateTopicResponse>"
 | 
						|
    robj.status_code = requests.codes.ok
 | 
						|
 | 
						|
    # Assign ourselves a new function
 | 
						|
    mock_post.return_value = robj
 | 
						|
    assert a.notify(title='', body='test') is False
 | 
						|
 | 
						|
    # Handle case where we fails get a bad response
 | 
						|
    robj = mock.Mock()
 | 
						|
    robj.content = ''
 | 
						|
    robj.status_code = requests.codes.bad_request
 | 
						|
    mock_post.return_value = robj
 | 
						|
    assert a.notify(title='', body='test') is False
 | 
						|
 | 
						|
    # Handle case where we get a valid response and TopicARN
 | 
						|
    robj = mock.Mock()
 | 
						|
    robj.content = arn_response
 | 
						|
    robj.status_code = requests.codes.ok
 | 
						|
    mock_post.return_value = robj
 | 
						|
    # We would have failed to make Post
 | 
						|
    assert a.notify(title='', body='test') is True
 |