def test_discord_attachments(mock_post):
    """
    API: NotifyDiscord() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    webhook_id = 'C' * 24
    webhook_token = 'D' * 64

    # Prepare Mock return object
    response = mock.Mock()
    response.status_code = requests.codes.ok

    # Throw an exception on the second call to requests.post()
    mock_post.side_effect = [response, OSError()]

    # Test our markdown
    obj = Apprise.instantiate('discord://{}/{}/?format=markdown'.format(
        webhook_id, webhook_token))

    # attach our content
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False
Beispiel #2
0
def test_file_expiry(tmpdir):
    """
    API: AttachFile Expiry
    """
    path = join(TEST_VAR_DIR, 'apprise-test.gif')
    image = tmpdir.mkdir("apprise_file").join("test.jpg")
    with open(path, 'rb') as data:
        image.write(data)

    aa = AppriseAttachment.instantiate(str(image), cache=30)

    # Our file is now available
    assert aa.exists()

    # Our second call has the file already downloaded, but now compares
    # it's date against when we consider it to have expire.  We're well
    # under 30 seconds here (our set value), so this will succeed
    assert aa.exists()

    with mock.patch('time.time', return_value=time.time() + 31):
        # This will force a re-download as our cache will have
        # expired
        assert aa.exists()

    with mock.patch('time.time', side_effect=OSError):
        # We will throw an exception
        assert aa.exists()
Beispiel #3
0
def test_discord_attachments(mock_post):
    """
    API: NotifyDiscord() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    webhook_id = 'C' * 24
    webhook_token = 'D' * 64

    # Prepare Mock return object
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok

    # Test our markdown
    obj = Apprise.instantiate('discord://{}/{}/?format=markdown'.format(
        webhook_id, webhook_token))

    # attach our content
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    # Throw an exception on the second call to requests.post()
    mock_post.return_value = None
    response = mock.Mock()
    response.status_code = requests.codes.ok
    mock_post.side_effect = [response, OSError()]

    # update our attachment to be valid
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
    # Test our markdown

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False
Beispiel #4
0
def test_plugin_ses_attachments(mock_post):
    """
    NotifySES() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifySES.request_rate_per_sec = 0

    # Prepare Mock return object
    response = mock.Mock()
    response.content = AWS_SES_GOOD_RESPONSE
    response.status_code = requests.codes.ok
    mock_post.return_value = response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Test our markdown
    obj = Apprise.instantiate('ses://%s/%s/%s/%s/' %
                              ('*****@*****.**', TEST_ACCESS_KEY_ID,
                               TEST_ACCESS_KEY_SECRET, TEST_REGION))

    # Send a good attachment
    assert obj.notify(body="test", attach=attach) is True

    # Reset our mock object
    mock_post.reset_mock()

    # Add another attachment so we drop into the area of the PushBullet code
    # that sends remaining attachments (if more detected)
    attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Send our attachments
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 1

    # Reset our mock object
    mock_post.reset_mock()

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(body="test", attach=attach) is False
Beispiel #5
0
def test_notify_sparkpost_plugin_attachments(mock_post):
    """
    API: NotifySparkPost() Attachments

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0
    plugins.NotifySparkPost.sparkpost_retry_wait_sec = 0.1
    plugins.NotifySparkPost.sparkpost_retry_attempts = 3

    okay_response = requests.Request()
    okay_response.status_code = requests.codes.ok
    okay_response.content = dumps({
        "results": {
            "total_rejected_recipients": 0,
            "total_accepted_recipients": 1,
            "id": "11668787484950529"
        }
    })

    # Assign our mock object our return value
    mock_post.return_value = okay_response

    # API Key
    apikey = 'abc123'

    obj = Apprise.instantiate(
        'sparkpost://[email protected]/{}'.format(apikey))
    assert isinstance(obj, plugins.NotifySparkPost)

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test invalid attachment
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    with mock.patch('base64.b64encode', side_effect=OSError()):
        # We can't send the message if we fail to parse the data
        assert obj.notify(body='body',
                          title='title',
                          notify_type=NotifyType.INFO,
                          attach=attach) is False

    obj = Apprise.instantiate(
        'sparkpost://[email protected]/{}/'
        '[email protected]/[email protected]?batch=yes'.format(apikey))
    assert isinstance(obj, plugins.NotifySparkPost)

    # Force our batch to break into separate messages
    obj.default_batch_size = 1
    # We'll send 2 messages
    mock_post.reset_mock()

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True
    assert mock_post.call_count == 2

    # single batch
    mock_post.reset_mock()
    # We'll send 1 message
    obj.default_batch_size = 2

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True
    assert mock_post.call_count == 1
Beispiel #6
0
def test_plugin_pushover_attachments(mock_post, tmpdir):
    """
    NotifyPushover() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    user_key = 'u' * 30
    api_token = 'a' * 30

    # Prepare a good response
    response = mock.Mock()
    response.content = dumps(
        {"status": 1, "request": "647d2300-702c-4b38-8b2f-d56326ae460b"})
    response.status_code = requests.codes.ok

    # Prepare a bad response
    bad_response = mock.Mock()
    response.content = dumps(
        {"status": 1, "request": "647d2300-702c-4b38-8b2f-d56326ae460b"})
    bad_response.status_code = requests.codes.internal_server_error

    # Assign our good response
    mock_post.return_value = response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Instantiate our object
    obj = Apprise.instantiate(
        'pover://{}@{}/'.format(user_key, api_token))
    assert isinstance(obj, plugins.NotifyPushover)

    # Test our attachment
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 1
    assert mock_post.call_args_list[0][0][0] == \
        'https://api.pushover.net/1/messages.json'

    # Reset our mock object for multiple tests
    mock_post.reset_mock()

    # Test multiple attachments
    assert attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://api.pushover.net/1/messages.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.pushover.net/1/messages.json'

    # Reset our mock object for multiple tests
    mock_post.reset_mock()

    image = tmpdir.mkdir("pover_image").join("test.jpg")
    image.write('a' * plugins.NotifyPushover.attach_max_size_bytes)

    attach = AppriseAttachment.instantiate(str(image))
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 1
    assert mock_post.call_args_list[0][0][0] == \
        'https://api.pushover.net/1/messages.json'

    # Reset our mock object for multiple tests
    mock_post.reset_mock()

    # Add 1 more byte to the file (putting it over the limit)
    image.write('a' * (plugins.NotifyPushover.attach_max_size_bytes + 1))

    attach = AppriseAttachment.instantiate(str(image))
    assert obj.notify(body="test", attach=attach) is False

    # Test our call count
    assert mock_post.call_count == 0

    # Test case when file is missing
    attach = AppriseAttachment.instantiate(
        'file://{}?cache=False'.format(str(image)))
    os.unlink(str(image))
    assert obj.notify(
        body='body', title='title', attach=attach) is False

    # Test our call count
    assert mock_post.call_count == 0

    # Test unsuported files:
    image = tmpdir.mkdir("pover_unsupported").join("test.doc")
    image.write('a' * 256)
    attach = AppriseAttachment.instantiate(str(image))

    # Content is silently ignored
    assert obj.notify(body="test", attach=attach) is True

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Throw an exception on the first call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [side_effect, side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

        # Same case without an attachment
        assert obj.send(body="test") is False
Beispiel #7
0
def test_apprise():
    """
    API: Apprise() object

    """
    # Caling load matix a second time which is an internal function causes it
    # to skip over content already loaded into our matrix and thefore accesses
    # other if/else parts of the code that aren't otherwise called
    __load_matrix()

    a = Apprise()

    # no items
    assert len(a) == 0

    # Apprise object can also be directly tested with 'if' keyword
    # No entries results in a False response
    assert not a

    # Create an Asset object
    asset = AppriseAsset(theme='default')

    # We can load the device using our asset
    a = Apprise(asset=asset)

    # We can load our servers up front as well
    servers = [
        'faast://abcdefghijklmnop-abcdefg',
        'kodi://kodi.server.local',
    ]

    a = Apprise(servers=servers)

    # 2 servers loaded
    assert len(a) == 2

    # Apprise object can also be directly tested with 'if' keyword
    # At least one entry results in a True response
    assert a

    # We can retrieve our URLs this way:
    assert len(a.urls()) == 2

    # We can add another server
    assert a.add('mmosts://mattermost.server.local/'
                 '3ccdd113474722377935511fc85d3dd4') is True
    assert len(a) == 3

    # We can pop an object off of our stack by it's indexed value:
    obj = a.pop(0)
    assert isinstance(obj, NotifyBase) is True
    assert len(a) == 2

    # We can retrieve elements from our list too by reference:
    assert isinstance(a[0].url(), six.string_types) is True

    # We can iterate over our list too:
    count = 0
    for o in a:
        assert isinstance(o.url(), six.string_types) is True
        count += 1
    # verify that we did indeed iterate over each element
    assert len(a) == count

    # We can empty our set
    a.clear()
    assert len(a) == 0

    # An invalid schema
    assert a.add('this is not a parseable url at all') is False
    assert len(a) == 0

    # An unsupported schema
    assert a.add('invalid://we.just.do.not.support.this.plugin.type') is False
    assert len(a) == 0

    # A poorly formatted URL
    assert a.add('json://user:@@@:bad?no.good') is False
    assert len(a) == 0

    # Add a server with our asset we created earlier
    assert a.add(
        'mmosts://mattermost.server.local/'
        '3ccdd113474722377935511fc85d3dd4',
        asset=asset) is True

    # Clear our server listings again
    a.clear()

    # No servers to notify
    assert a.notify(title="my title", body="my body") is False

    class BadNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(BadNotification, self).__init__(**kwargs)

            # We fail whenever we're initialized
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification,
                  self).__init__(notify_format=NotifyFormat.HTML, **kwargs)

        def url(self):
            # Support URL
            return ''

        def send(self, **kwargs):
            # Pretend everything is okay
            return True

    # Store our bad notification in our schema map
    SCHEMA_MAP['bad'] = BadNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Just to explain what is happening here, we would have parsed the
    # url properly but failed when we went to go and create an instance
    # of it.
    assert a.add('bad://localhost') is False
    assert len(a) == 0

    assert a.add('good://localhost') is True
    assert len(a) == 1

    # Bad Notification Type is still allowed as it is presumed the user
    # know's what their doing
    assert a.notify(title="my title", body="my body",
                    notify_type='bad') is True

    # No Title/Body combo's
    assert a.notify(title=None, body=None) is False
    assert a.notify(title='', body=None) is False
    assert a.notify(title=None, body='') is False

    # As long as one is present, we're good
    assert a.notify(title=None, body='present') is True
    assert a.notify(title='present', body=None) is True
    assert a.notify(title="present", body="present") is True

    # Send Attachment with success
    attach = join(TEST_VAR_DIR, 'apprise-test.gif')
    assert a.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Send the attachment as an AppriseAttachment object
    assert a.notify(body='body',
                    title='test',
                    notify_type=NotifyType.INFO,
                    attach=AppriseAttachment(attach)) is True

    # test a invalid attachment
    assert a.notify(body='body',
                    title='test',
                    notify_type=NotifyType.INFO,
                    attach='invalid://') is False

    # Repeat the same tests above...
    # however do it by directly accessing the object; this grants the similar
    # results:
    assert a[0].notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Send the attachment as an AppriseAttachment object
    assert a[0].notify(body='body',
                       title='test',
                       notify_type=NotifyType.INFO,
                       attach=AppriseAttachment(attach)) is True

    # test a invalid attachment
    assert a[0].notify(body='body',
                       title='test',
                       notify_type=NotifyType.INFO,
                       attach='invalid://') is False

    # Clear our server listings again
    a.clear()

    class ThrowNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    class RuntimeNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise RuntimeError()

        def url(self):
            # Support URL
            return ''

    class FailNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            return False

        def url(self):
            # Support URL
            return ''

    # Store our bad notification in our schema map
    SCHEMA_MAP['throw'] = ThrowNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['fail'] = FailNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['runtime'] = RuntimeNotification

    assert a.add('runtime://localhost') is True
    assert a.add('throw://localhost') is True
    assert a.add('fail://localhost') is True
    assert len(a) == 3

    # Test when our notify both throws an exception and or just
    # simply returns False
    assert a.notify(title="present", body="present") is False

    # Create a Notification that throws an unexected exception
    class ThrowInstantiateNotification(NotifyBase):
        def __init__(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    SCHEMA_MAP['throw'] = ThrowInstantiateNotification

    # Reset our object
    a.clear()
    assert len(a) == 0

    # Instantiate a bad object
    plugin = a.instantiate(object, tag="bad_object")
    assert plugin is None

    # Instantiate a good object
    plugin = a.instantiate('good://localhost', tag="good")
    assert isinstance(plugin, NotifyBase)

    # Test simple tagging inside of the object
    assert "good" in plugin
    assert "bad" not in plugin

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert ["bad", "good"] in plugin
    assert set(["bad", "good"]) in plugin
    assert ("bad", "good") in plugin

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert len(a) == 1

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert len(a) == 4

    # Reset our object again
    a.clear()
    with pytest.raises(TypeError):
        a.instantiate('throw://localhost', suppress_exceptions=False)

    assert len(a) == 0

    assert a.instantiate('throw://localhost', suppress_exceptions=True) is None
    assert len(a) == 0

    #
    # We rince and repeat the same tests as above, however we do them
    # using the dict version
    #

    # Reset our object
    a.clear()
    assert len(a) == 0

    # Instantiate a good object
    plugin = a.instantiate({'schema': 'good', 'host': 'localhost'}, tag="good")
    assert isinstance(plugin, NotifyBase)

    # Test simple tagging inside of the object
    assert "good" in plugin
    assert "bad" not in plugin

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert ["bad", "good"] in plugin
    assert set(["bad", "good"]) in plugin
    assert ("bad", "good") in plugin

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert len(a) == 1

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert len(a) == 4

    # Reset our object again
    a.clear()
    with pytest.raises(TypeError):
        a.instantiate({
            'schema': 'throw',
            'host': 'localhost'
        },
                      suppress_exceptions=False)

    assert len(a) == 0

    assert a.instantiate({
        'schema': 'throw',
        'host': 'localhost'
    },
                         suppress_exceptions=True) is None
    assert len(a) == 0
def test_notify_telegram_plugin(mock_post, mock_get, tmpdir):
    """
    API: NotifyTelegram() Tests

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Bot Token
    bot_token = '123456789:abcdefg_hijklmnop'
    invalid_bot_token = 'abcd:123'

    # Chat ID
    chat_ids = 'l2g, lead2gold'

    # Prepare Mock
    mock_get.return_value = requests.Request()
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok
    mock_get.return_value.status_code = requests.codes.ok
    mock_get.return_value.content = '{}'
    mock_post.return_value.content = '{}'

    # Exception should be thrown about the fact no bot token was specified
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=None, targets=chat_ids)

    # Invalid JSON while trying to detect bot owner
    mock_get.return_value.content = '{'
    mock_post.return_value.content = '}'
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Invalid JSON while trying to detect bot owner + 400 error
    mock_get.return_value.status_code = requests.codes.internal_server_error
    mock_post.return_value.status_code = requests.codes.internal_server_error
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Return status back to how they were
    mock_post.return_value.status_code = requests.codes.ok
    mock_get.return_value.status_code = requests.codes.ok

    # Exception should be thrown about the fact an invalid bot token was
    # specifed
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=invalid_bot_token, targets=chat_ids)

    obj = plugins.NotifyTelegram(bot_token=bot_token,
                                 targets=chat_ids,
                                 include_image=True)
    assert isinstance(obj, plugins.NotifyTelegram) is True
    assert len(obj.targets) == 2

    # Test Image Sending Exceptions
    mock_post.side_effect = IOError()
    assert not obj.send_media(obj.targets[0], NotifyType.INFO)

    # Test our other objects
    mock_post.side_effect = requests.HTTPError
    assert not obj.send_media(obj.targets[0], NotifyType.INFO)

    # Restore their entries
    mock_get.side_effect = None
    mock_post.side_effect = None
    mock_get.return_value.content = '{}'
    mock_post.return_value.content = '{}'

    # test url call
    assert isinstance(obj.url(), six.string_types) is True

    # test privacy version of url
    assert isinstance(obj.url(privacy=True), six.string_types) is True
    assert obj.url(privacy=True).startswith('tgram://1...p/') is True

    # Test that we can load the string we generate back:
    obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url()))
    assert isinstance(obj, plugins.NotifyTelegram) is True

    # Prepare Mock to fail
    response = mock.Mock()
    response.status_code = requests.codes.internal_server_error

    # a error response
    response.content = dumps({
        'description': 'test',
    })
    mock_get.return_value = response
    mock_post.return_value = response

    # No image asset
    nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, targets=chat_ids)
    nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)

    # Test that our default settings over-ride base settings since they are
    # not the same as the one specified in the base; this check merely
    # ensures our plugin inheritance is working properly
    assert obj.body_maxlen == plugins.NotifyTelegram.body_maxlen

    # We don't override the title maxlen so we should be set to the same
    # as our parent class in this case
    assert obj.title_maxlen == plugins.NotifyBase.title_maxlen

    # This tests erroneous messages involving multiple chat ids
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # This tests erroneous messages involving a single chat id
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets='l2g')
    nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, targets='l2g')
    nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)

    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # Bot Token Detection
    # Just to make it clear to people reading this code and trying to learn
    # what is going on.  Apprise tries to detect the bot owner if you don't
    # specify a user to message.  The idea is to just default to messaging
    # the bot owner himself (it makes it easier for people).  So we're testing
    # the creating of a Telegram Notification without providing a chat ID.
    # We're testing the error handling of this bot detection section of the
    # code
    mock_post.return_value.content = dumps({
        "ok":
        True,
        "result": [
            {
                "update_id": 645421321,
                "message": {
                    "message_id":
                    1,
                    "from": {
                        "id": 532389719,
                        "is_bot": False,
                        "first_name": "Chris",
                        "language_code": "en-US"
                    },
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
        ],
    })
    mock_post.return_value.status_code = requests.codes.ok

    # Test sending attachments
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets='12345')
    assert len(obj.targets) == 1
    assert obj.targets[0] == '12345'

    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    assert len(obj.targets) == 1
    assert obj.targets[0] == '532389719'

    # Do the test again, but without the expected (parsed response)
    mock_post.return_value.content = dumps({
        "ok": True,
        "result": [],
    })

    # Exception should be thrown about the fact no bot token was specified
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Detect the bot with a bad response
    mock_post.return_value.content = dumps({})
    obj.detect_bot_owner()

    # Test our bot detection with a internal server error
    mock_post.return_value.status_code = requests.codes.internal_server_error

    # Exception should be thrown over internal server error caused
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Test our bot detection with an unmappable html error
    mock_post.return_value.status_code = 999
    # Exception should be thrown over invali internal error no
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Do it again but this time provide a failure message
    mock_post.return_value.content = dumps({'description': 'Failure Message'})

    # Exception should be thrown about the fact no bot token was specified
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=bot_token, targets=None)

    # Do it again but this time provide a failure message and perform a
    # notification without a bot detection by providing at least 1 chat id
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=['@abcd'])
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # iterate over our exceptions and test them
    mock_post.side_effect = requests.HTTPError

    # No chat_ids specified
    with pytest.raises(TypeError):
        obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
Beispiel #9
0
def test_plugin_discord_attachments(mock_post):
    """
    NotifyDiscord() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    webhook_id = 'C' * 24
    webhook_token = 'D' * 64

    # Prepare a good response
    response = mock.Mock()
    response.status_code = requests.codes.ok

    # Prepare a bad response
    bad_response = mock.Mock()
    bad_response.status_code = requests.codes.internal_server_error

    # Prepare Mock return object
    mock_post.return_value = response

    # Test our markdown
    obj = Apprise.instantiate('discord://{}/{}/?format=markdown'.format(
        webhook_id, webhook_token))

    # attach our content
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://discord.com/api/webhooks/{}/{}'.format(
            webhook_id, webhook_token)
    assert mock_post.call_args_list[1][0][0] == \
        'https://discord.com/api/webhooks/{}/{}'.format(
            webhook_id, webhook_token)

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    # update our attachment to be valid
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    mock_post.return_value = None
    # Throw an exception on the first call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the second call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [response, side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # handle a bad response
    bad_response = mock.Mock()
    bad_response.status_code = requests.codes.internal_server_error
    mock_post.side_effect = [response, bad_response]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False
def test_plugin_telegram_general(mock_post):
    """
    NotifyTelegram() General Tests

    """

    # Disable Throttling to speed testing
    plugins.NotifyTelegram.request_rate_per_sec = 0

    # Bot Token
    bot_token = '123456789:abcdefg_hijklmnop'
    invalid_bot_token = 'abcd:123'

    # Chat ID
    chat_ids = 'l2g, lead2gold'

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok
    mock_post.return_value.content = '{}'

    # Exception should be thrown about the fact no bot token was specified
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=None, targets=chat_ids)

    # Invalid JSON while trying to detect bot owner
    mock_post.return_value.content = '}'
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    obj.notify(title='hello', body='world')

    # Invalid JSON while trying to detect bot owner + 400 error
    mock_post.return_value.status_code = requests.codes.internal_server_error
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    obj.notify(title='hello', body='world')

    # Return status back to how they were
    mock_post.return_value.status_code = requests.codes.ok

    # Exception should be thrown about the fact an invalid bot token was
    # specifed
    with pytest.raises(TypeError):
        plugins.NotifyTelegram(bot_token=invalid_bot_token, targets=chat_ids)

    obj = plugins.NotifyTelegram(bot_token=bot_token,
                                 targets=chat_ids,
                                 include_image=True)
    assert isinstance(obj, plugins.NotifyTelegram) is True
    assert len(obj.targets) == 2

    # Test Image Sending Exceptions
    mock_post.side_effect = IOError()
    assert not obj.send_media(obj.targets[0], NotifyType.INFO)

    # Test our other objects
    mock_post.side_effect = requests.HTTPError
    assert not obj.send_media(obj.targets[0], NotifyType.INFO)

    # Restore their entries
    mock_post.side_effect = None
    mock_post.return_value.content = '{}'

    # test url call
    assert isinstance(obj.url(), six.string_types) is True

    # test privacy version of url
    assert isinstance(obj.url(privacy=True), six.string_types) is True
    assert obj.url(privacy=True).startswith('tgram://1...p/') is True

    # Test that we can load the string we generate back:
    obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url()))
    assert isinstance(obj, plugins.NotifyTelegram) is True

    # Prepare Mock to fail
    response = mock.Mock()
    response.status_code = requests.codes.internal_server_error

    # a error response
    response.content = dumps({
        'description': 'test',
    })
    mock_post.return_value = response

    # No image asset
    nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, targets=chat_ids)
    nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)

    # Test that our default settings over-ride base settings since they are
    # not the same as the one specified in the base; this check merely
    # ensures our plugin inheritance is working properly
    assert obj.body_maxlen == plugins.NotifyTelegram.body_maxlen

    # This tests erroneous messages involving multiple chat ids
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # This tests erroneous messages involving a single chat id
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets='l2g')
    nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, targets='l2g')
    nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)

    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is False
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # Bot Token Detection
    # Just to make it clear to people reading this code and trying to learn
    # what is going on.  Apprise tries to detect the bot owner if you don't
    # specify a user to message.  The idea is to just default to messaging
    # the bot owner himself (it makes it easier for people).  So we're testing
    # the creating of a Telegram Notification without providing a chat ID.
    # We're testing the error handling of this bot detection section of the
    # code
    mock_post.return_value.content = dumps({
        "ok":
        True,
        "result": [
            {
                "update_id": 645421319,
                # Entry without `message` in it
            },
            {
                # Entry without `from` in `message`
                "update_id": 645421320,
                "message": {
                    "message_id":
                    2,
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
            {
                "update_id": 645421321,
                "message": {
                    "message_id":
                    2,
                    "from": {
                        "id": 532389719,
                        "is_bot": False,
                        "first_name": "Chris",
                        "language_code": "en-US"
                    },
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
        ],
    })
    mock_post.return_value.status_code = requests.codes.ok

    obj = plugins.NotifyTelegram(bot_token=bot_token, targets='12345')
    assert len(obj.targets) == 1
    assert obj.targets[0] == '12345'

    # Test the escaping of characters since Telegram escapes stuff for us to
    # which we need to consider
    mock_post.reset_mock()
    body = "<p>\'\"This can't\t\r\nfail&nbsp;us\"\'</p>"
    assert obj.notify(body=body,
                      title='special characters',
                      notify_type=NotifyType.INFO) is True
    assert mock_post.call_count == 1
    payload = loads(mock_post.call_args_list[0][1]['data'])

    # Test our payload
    assert payload['text'] == \
        '<b>special characters</b>\r\n\'"This can\'t\t\r\nfail us"\'\r\n'

    # Test sending attachments
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    # No user detected; this happens after our firsst notification
    assert len(obj.targets) == 0

    assert obj.notify(title='hello', body='world') is True
    assert len(obj.targets) == 1
    assert obj.targets[0] == '532389719'

    # Do the test again, but without the expected (parsed response)
    mock_post.return_value.content = dumps({
        "ok": True,
        "result": [],
    })

    # No user will be detected now
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    # No user detected; this happens after our firsst notification
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # Do the test again, but with ok not set to True
    mock_post.return_value.content = dumps({
        "ok":
        False,
        "result": [
            {
                "update_id": 645421321,
                "message": {
                    "message_id":
                    2,
                    "from": {
                        "id": 532389719,
                        "is_bot": False,
                        "first_name": "Chris",
                        "language_code": "en-US"
                    },
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
        ],
    })

    # No user will be detected now
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    # No user detected; this happens after our firsst notification
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # An edge case where no results were provided; this will probably never
    # happen, but it helps with test coverage completeness
    mock_post.return_value.content = dumps({
        "ok": True,
    })

    # No user will be detected now
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    # No user detected; this happens after our firsst notification
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0
    # Detect the bot with a bad response
    mock_post.return_value.content = dumps({})
    obj.detect_bot_owner()

    # Test our bot detection with a internal server error
    mock_post.return_value.status_code = requests.codes.internal_server_error

    # internal server error prevents notification from being sent
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # Test our bot detection with an unmappable html error
    mock_post.return_value.status_code = 999
    plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # Do it again but this time provide a failure message
    mock_post.return_value.content = dumps({'description': 'Failure Message'})
    plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # Do it again but this time provide a failure message and perform a
    # notification without a bot detection by providing at least 1 chat id
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=['@abcd'])
    assert nimg_obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO) is False

    # iterate over our exceptions and test them
    mock_post.side_effect = requests.HTTPError

    # No chat_ids specified
    obj = plugins.NotifyTelegram(bot_token=bot_token, targets=None)
    assert len(obj.targets) == 0
    assert obj.notify(title='hello', body='world') is False
    assert len(obj.targets) == 0

    # Test Telegram Group
    obj = Apprise.instantiate(
        'tgram://123456789:ABCdefghijkl123456789opqyz/-123456789525')
    assert isinstance(obj, plugins.NotifyTelegram)
    assert len(obj.targets) == 1
    assert '-123456789525' in obj.targets
Beispiel #11
0
    def __notify(self, url, obj, meta, asset, mock_del, mock_put, mock_head,
                 mock_post, mock_get):
        """
        Perform notification testing against object specified
        """
        #
        # Prepare our options
        #

        # Allow notification type override, otherwise default to INFO
        notify_type = meta.get('notify_type', NotifyType.INFO)

        # Whether or not we're testing exceptions or not
        test_requests_exceptions = meta.get('test_requests_exceptions', False)

        # Our expected Query response (True, False, or exception type)
        response = meta.get('response', True)

        # Our expected Notify response (True or False)
        notify_response = meta.get('notify_response', response)

        # Our expected Notify Attachment response (True or False)
        attach_response = meta.get('attach_response', notify_response)

        # Test attachments
        # Don't set this if don't need to check it's value
        check_attachments = meta.get('check_attachments', True)

        # Allow us to force the server response code to be something other then
        # the defaults
        requests_response_code = meta.get(
            'requests_response_code',
            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 isinstance(requests_response_text, six.string_types):
            # Convert to string
            requests_response_text = dumps(requests_response_text)

        # A request
        robj = mock.Mock()
        robj.content = u''
        mock_get.return_value = robj
        mock_post.return_value = robj
        mock_head.return_value = robj
        mock_del.return_value = robj
        mock_put.return_value = robj

        if test_requests_exceptions is False:
            # Handle our default response
            mock_put.return_value.status_code = requests_response_code
            mock_head.return_value.status_code = requests_response_code
            mock_del.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

            # Handle our default text response
            mock_get.return_value.content = requests_response_text
            mock_post.return_value.content = requests_response_text
            mock_del.return_value.content = requests_response_text
            mock_put.return_value.content = requests_response_text
            mock_head.return_value.content = requests_response_text

            mock_get.return_value.text = requests_response_text
            mock_post.return_value.text = requests_response_text
            mock_put.return_value.text = requests_response_text
            mock_del.return_value.text = requests_response_text
            mock_head.return_value.text = requests_response_text

            # Ensure there is no side effect set
            mock_post.side_effect = None
            mock_del.side_effect = None
            mock_put.side_effect = None
            mock_head.side_effect = None
            mock_get.side_effect = None

        else:
            # Handle exception testing; first we turn the boolean flag
            # into a list of exceptions
            test_requests_exceptions = self.req_exceptions

        try:
            if test_requests_exceptions is False:
                # Disable throttling
                obj.request_rate_per_sec = 0

                # check that we're as expected
                assert obj.notify(body=self.body,
                                  title=self.title,
                                  notify_type=notify_type) == notify_response

                # check that this doesn't change using different overflow
                # methods
                assert obj.notify(
                    body=self.body,
                    title=self.title,
                    notify_type=notify_type,
                    overflow=OverflowMode.UPSTREAM) == notify_response
                assert obj.notify(
                    body=self.body,
                    title=self.title,
                    notify_type=notify_type,
                    overflow=OverflowMode.TRUNCATE) == notify_response
                assert obj.notify(
                    body=self.body,
                    title=self.title,
                    notify_type=notify_type,
                    overflow=OverflowMode.SPLIT) == notify_response

                #
                # Handle varations of the Asset Object missing fields
                #

                # First make a backup
                app_id = asset.app_id
                app_desc = asset.app_desc

                # now clear records
                asset.app_id = None
                asset.app_desc = None

                # Notify should still work
                assert obj.notify(body=self.body,
                                  title=self.title,
                                  notify_type=notify_type) == notify_response

                # App ID only
                asset.app_id = app_id
                asset.app_desc = None

                # Notify should still work
                assert obj.notify(body=self.body,
                                  title=self.title,
                                  notify_type=notify_type) == notify_response

                # App Desc only
                asset.app_id = None
                asset.app_desc = app_desc

                # Notify should still work
                assert obj.notify(body=self.body,
                                  title=self.title,
                                  notify_type=notify_type) == notify_response

                # Restore
                asset.app_id = app_id
                asset.app_desc = app_desc

                if check_attachments:
                    # Test single attachment support; even if the service
                    # doesn't support attachments, it should still
                    # gracefully ignore the data
                    attach = os.path.join(self.__test_var_dir,
                                          'apprise-test.gif')
                    assert obj.notify(body=self.body,
                                      title=self.title,
                                      notify_type=notify_type,
                                      attach=attach) == attach_response

                    # Same results should apply to a list of attachments
                    attach = AppriseAttachment((
                        os.path.join(self.__test_var_dir, 'apprise-test.gif'),
                        os.path.join(self.__test_var_dir, 'apprise-test.png'),
                        os.path.join(self.__test_var_dir, 'apprise-test.jpeg'),
                    ))
                    assert obj.notify(body=self.body,
                                      title=self.title,
                                      notify_type=notify_type,
                                      attach=attach) == attach_response

            else:
                # Disable throttling
                obj.request_rate_per_sec = 0

                for _exception in self.req_exceptions:
                    mock_post.side_effect = _exception
                    mock_head.side_effect = _exception
                    mock_del.side_effect = _exception
                    mock_put.side_effect = _exception
                    mock_get.side_effect = _exception

                    try:
                        assert obj.notify(body=self.body,
                                          title=self.title,
                                          notify_type=NotifyType.INFO) is False

                    except AssertionError:
                        # Don't mess with these entries
                        raise

                    except Exception:
                        # We can't handle this exception type
                        raise

        except AssertionError:
            # Don't mess with these entries
            raise

        except Exception as e:
            # Check that we were expecting this exception to happen
            try:
                if not isinstance(e, response):
                    raise e

            except TypeError:
                print('%s Unhandled response %s' % (url, type(e)))
                raise e

        #
        # Do the test again but without a title defined
        #
        try:
            if test_requests_exceptions is False:
                # check that we're as expected
                assert obj.notify(body='body', notify_type=notify_type) \
                    == notify_response

            else:
                for _exception in self.req_exceptions:
                    mock_post.side_effect = _exception
                    mock_del.side_effect = _exception
                    mock_put.side_effect = _exception
                    mock_head.side_effect = _exception
                    mock_get.side_effect = _exception

                    try:
                        assert obj.notify(body=self.body,
                                          notify_type=NotifyType.INFO) is False

                    except AssertionError:
                        # Don't mess with these entries
                        raise

                    except Exception:
                        # We can't handle this exception type
                        raise

        except AssertionError:
            # Don't mess with these entries
            raise

        except Exception as e:
            # Check that we were expecting this exception to happen
            if not isinstance(e, response):
                raise e

        return True
def test_plugin_custom_form_attachments(mock_post):
    """
    NotifyForm() Attachments

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    okay_response = requests.Request()
    okay_response.status_code = requests.codes.ok
    okay_response.content = ""

    # Assign our mock object our return value
    mock_post.return_value = okay_response

    obj = Apprise.instantiate(
        'form://[email protected]/?method=post')
    assert isinstance(obj, plugins.NotifyForm)

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test invalid attachment
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    mock_post.return_value = None
    mock_post.side_effect = OSError()
    # We can't send the message if we can't read the attachment
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    # Get a appropriate "builtin" module name for pythons 2/3.
    if sys.version_info.major >= 3:
        builtin_open_function = 'builtins.open'

    else:
        builtin_open_function = '__builtin__.open'

    # Test Valid Attachment (load 3)
    path = (
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
    )
    attach = AppriseAttachment(path)

    # Return our good configuration
    mock_post.side_effect = None
    mock_post.return_value = okay_response
    with mock.patch(builtin_open_function, side_effect=OSError()):
        # We can't send the message we can't open the attachment for reading
        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

    # Fail on the 2nd attempt (but not the first)
    with mock.patch(builtin_open_function,
                    side_effect=[None, OSError(), None]):
        # We can't send the message we can't open the attachment for reading
        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

    # Test file exception handling when performing post
    mock_post.return_value = None
    mock_post.side_effect = OSError()
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False
Beispiel #13
0
def test_plugin_twitter_tweet_attachments(mock_get, mock_post):
    """
    NotifyTwitter() Tweet Attachment Checks

    """
    ckey = 'ckey'
    csecret = 'csecret'
    akey = 'akey'
    asecret = 'asecret'
    screen_name = 'apprise'

    good_tweet_response_obj = {
        'screen_name': screen_name,
        'id': 9876,
    }

    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Prepare a good DM response
    good_tweet_response = mock.Mock()
    good_tweet_response.content = dumps(good_tweet_response_obj)
    good_tweet_response.status_code = requests.codes.ok

    # Prepare bad response
    bad_response = mock.Mock()
    bad_response.content = dumps({})
    bad_response.status_code = requests.codes.internal_server_error

    # Prepare a good media response
    good_media_response = mock.Mock()
    good_media_response.content = dumps({
        "media_id": 710511363345354753,
        "media_id_string": "710511363345354753",
        "media_key": "3_710511363345354753",
        "size": 11065,
        "expires_after_secs": 86400,
        "image": {
            "image_type": "image/jpeg",
            "w": 800,
            "h": 320
        }
    })
    good_media_response.status_code = requests.codes.ok

    # Prepare a bad media response
    bad_media_response = mock.Mock()
    bad_media_response.content = dumps({
        "errors": [{
            "code":
            93,
            "message":
            "This application is not allowed to access or "
            "delete your direct messages.",
        }]
    })
    bad_media_response.status_code = requests.codes.internal_server_error

    mock_post.side_effect = [good_media_response, good_tweet_response]
    mock_get.return_value = good_tweet_response

    twitter_url = 'twitter://{}/{}/{}/{}?mode=tweet'.format(
        ckey, csecret, akey, asecret)

    # attach our content
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # Send our notification
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test our call count
    assert mock_get.call_count == 0
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    # Update our good response to have more details in it
    good_tweet_response_obj = {
        'screen_name': screen_name,
        'id': 9876,
        # needed for additional logging
        'id_str': '12345',
        'user': {
            'screen_name': screen_name,
        }
    }

    good_tweet_response.content = dumps(good_tweet_response_obj)

    mock_get.reset_mock()
    mock_post.reset_mock()

    mock_post.side_effect = [good_media_response, good_tweet_response]
    mock_get.return_value = good_tweet_response

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # Send our notification (again); this time there willb e more tweet logging
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test our call count
    assert mock_get.call_count == 0
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    mock_post.side_effect = [good_media_response, bad_media_response]

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # Our notification will fail now since our tweet will error out
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    # Test our call count
    assert mock_get.call_count == 0
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    bad_media_response.content = ''

    mock_post.side_effect = [good_media_response, bad_media_response]

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # Our notification will fail now since our tweet will error out
    # This is the same test as above, except our error response isn't parseable
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    # Test our call count
    assert mock_get.call_count == 0
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    # Test case where upload fails
    mock_get.return_value = good_tweet_response
    mock_post.side_effect = [good_media_response, bad_response]

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # Send our notification; it will fail because of the media response
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    assert mock_get.call_count == 0
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    mock_post.side_effect = [good_media_response, good_tweet_response]
    mock_get.return_value = good_tweet_response

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    # No get request as cached response is used
    assert mock_get.call_count == 0

    # No post request as attachment is no good anyway
    assert mock_post.call_count == 0

    mock_get.reset_mock()
    mock_post.reset_mock()

    mock_post.side_effect = [
        good_media_response, good_media_response, good_media_response,
        good_media_response, good_tweet_response, good_tweet_response,
        good_tweet_response, good_tweet_response
    ]
    mock_get.return_value = good_tweet_response

    # instantiate our object (without a batch mode)
    obj = Apprise.instantiate(twitter_url + "&batch=no")

    # 4 images are produced
    attach = [
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.jpeg'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.png'),
        # This one is not supported, so it's ignored gracefully
        os.path.join(TEST_VAR_DIR, 'apprise-test.mp4'),
    ]

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    assert mock_get.call_count == 0
    # No get request as cached response is used
    assert mock_post.call_count == 8
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[2][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[3][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[4][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'
    assert mock_post.call_args_list[5][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'
    assert mock_post.call_args_list[6][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'
    assert mock_post.call_args_list[7][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    mock_post.side_effect = [
        good_media_response, good_media_response, good_media_response,
        good_media_response, good_tweet_response, good_tweet_response,
        good_tweet_response, good_tweet_response
    ]
    mock_get.return_value = good_tweet_response

    # instantiate our object
    obj = Apprise.instantiate(twitter_url)

    # 4 images are produced
    attach = [
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.jpeg'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.png'),
        # This one is not supported, so it's ignored gracefully
        os.path.join(TEST_VAR_DIR, 'apprise-test.mp4'),
    ]

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    assert mock_get.call_count == 0
    # No get request as cached response is used
    assert mock_post.call_count == 7
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[2][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[3][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[4][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'
    assert mock_post.call_args_list[5][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'
    # The 2 images are grouped together (batch mode)
    assert mock_post.call_args_list[6][0][0] == \
        'https://api.twitter.com/1.1/statuses/update.json'

    mock_get.reset_mock()
    mock_post.reset_mock()

    # We have an OSError thrown in the middle of our preparation
    mock_post.side_effect = [good_media_response, OSError()]
    mock_get.return_value = good_tweet_response

    # 2 images are produced
    attach = [
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.png'),
        # This one is not supported, so it's ignored gracefully
        os.path.join(TEST_VAR_DIR, 'apprise-test.mp4'),
    ]

    # We'll fail to send this time
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    assert mock_get.call_count == 0
    # No get request as cached response is used
    assert mock_post.call_count == 2
    assert mock_post.call_args_list[0][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
    assert mock_post.call_args_list[1][0][0] == \
        'https://upload.twitter.com/1.1/media/upload.json'
Beispiel #14
0
def test_attach_file():
    """
    API: AttachFile()

    """
    # Simple gif test
    path = join(TEST_VAR_DIR, 'apprise-test.gif')
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    assert response.path == path
    assert response.name == 'apprise-test.gif'
    assert response.mimetype == 'image/gif'
    # Download is successful and has already been called by now; below pulls
    # results from cache
    assert response.download()
    assert response.url().startswith('file://{}'.format(path))
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', response.url()) is None
    assert re.search(r'[?&]name=', response.url()) is None

    # File handling (even if image is set to maxium allowable)
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    with mock.patch('os.path.getsize', return_value=AttachBase.max_file_size):
        # It will still work
        assert response.path == path

    # File handling when size is to large
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    with mock.patch('os.path.getsize',
                    return_value=AttachBase.max_file_size + 1):
        # We can't work in this case
        assert response.path is None

    # File handling when image is not available
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    with mock.patch('os.path.isfile', return_value=False):
        # This triggers a full check and will fail the isfile() check
        assert response.path is None

    # The call to AttachBase.path automatically triggers a call to download()
    # but this same is done with a call to AttachBase.name as well.  Above
    # test cases reference 'path' right after instantiation; here we reference
    # 'name'
    response = AppriseAttachment.instantiate(path)
    assert response.name == 'apprise-test.gif'
    assert response.path == path
    assert response.mimetype == 'image/gif'
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', response.url()) is None
    assert re.search(r'[?&]name=', response.url()) is None

    # continuation to cheking 'name' instead of 'path' first where our call
    # to download() fails
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    with mock.patch('os.path.isfile', return_value=False):
        # This triggers a full check and will fail the isfile() check
        assert response.name is None

    # The call to AttachBase.path automatically triggers a call to download()
    # but this same is done with a call to AttachBase.mimetype as well.  Above
    # test cases reference 'path' right after instantiation; here we reference
    # 'mimetype'
    response = AppriseAttachment.instantiate(path)
    assert response.mimetype == 'image/gif'
    assert response.name == 'apprise-test.gif'
    assert response.path == path
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', response.url()) is None
    assert re.search(r'[?&]name=', response.url()) is None

    # continuation to cheking 'name' instead of 'path' first where our call
    # to download() fails
    response = AppriseAttachment.instantiate(path)
    assert isinstance(response, AttachFile)
    with mock.patch('os.path.isfile', return_value=False):
        # download() fails so we don't have a mimetpe
        assert response.mimetype is None
        assert response.name is None
        assert response.path is None
        # This triggers a full check and will fail the isfile() check

    # Force a mime-type and new name
    response = AppriseAttachment.instantiate(
        'file://{}?mime={}&name={}'.format(path, 'image/jpeg', 'test.jpeg'))
    assert isinstance(response, AttachFile)
    assert response.path == path
    assert response.name == 'test.jpeg'
    assert response.mimetype == 'image/jpeg'
    # We will match on mime type now  (%2F = /)
    assert re.search(r'[?&]mime=image%2Fjpeg', response.url(), re.I)
    assert re.search(r'[?&]name=test\.jpeg', response.url(), re.I)
Beispiel #15
0
def test_pushover_attachments(mock_post, tmpdir):
    """
    API: NotifyPushover() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    user_key = 'u' * 30
    api_token = 'a' * 30

    # Prepare Mock return object
    response = mock.Mock()
    response.content = dumps({
        "status": 1,
        "request": "647d2300-702c-4b38-8b2f-d56326ae460b"
    })
    response.status_code = requests.codes.ok
    mock_post.return_value = response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Instantiate our object
    obj = Apprise.instantiate('pover://{}@{}/'.format(user_key, api_token))
    assert isinstance(obj, plugins.NotifyPushover)

    # Test our attachment
    assert obj.notify(body="test", attach=attach) is True

    # Test multiple attachments
    assert attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
    assert obj.notify(body="test", attach=attach) is True

    image = tmpdir.mkdir("pover_image").join("test.jpg")
    image.write('a' * plugins.NotifyPushover.attach_max_size_bytes)

    attach = AppriseAttachment.instantiate(str(image))
    assert obj.notify(body="test", attach=attach) is True

    # Add 1 more byte to the file (putting it over the limit)
    image.write('a' * (plugins.NotifyPushover.attach_max_size_bytes + 1))

    attach = AppriseAttachment.instantiate(str(image))
    assert obj.notify(body="test", attach=attach) is False

    # Test case when file is missing
    attach = AppriseAttachment.instantiate('file://{}?cache=False'.format(
        str(image)))
    os.unlink(str(image))
    assert obj.notify(body='body', title='title', attach=attach) is False

    # Test unsuported files:
    image = tmpdir.mkdir("pover_unsupported").join("test.doc")
    image.write('a' * 256)
    attach = AppriseAttachment.instantiate(str(image))

    # Content is silently ignored
    assert obj.notify(body="test", attach=attach) is True

    # Throw an exception on the second call to requests.post()
    mock_post.side_effect = OSError()
    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))
    assert obj.notify(body="test", attach=attach) is False
def test_plugin_pushbullet_attachments(mock_post):
    """
    NotifyPushBullet() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyPushBullet.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    access_token = 't' * 32

    # Prepare Mock return object
    response = mock.Mock()
    response.content = dumps({
        "file_name":
        "cat.jpg",
        "file_type":
        "image/jpeg",
        "file_url":
        "https://dl.pushb.com/abc/cat.jpg",
        "upload_url":
        "https://upload.pushbullet.com/abcd123",
    })
    response.status_code = requests.codes.ok
    mock_post.return_value = response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Test our markdown
    obj = Apprise.instantiate(
        'pbul://{}/?format=markdown'.format(access_token))

    # Send a good attachment
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 4
    # Image Prep
    assert mock_post.call_args_list[0][0][0] == \
        'https://api.pushbullet.com/v2/upload-request'
    assert mock_post.call_args_list[1][0][0] == \
        'https://upload.pushbullet.com/abcd123'
    # Message
    assert mock_post.call_args_list[2][0][0] == \
        'https://api.pushbullet.com/v2/pushes'
    # Image Send
    assert mock_post.call_args_list[3][0][0] == \
        'https://api.pushbullet.com/v2/pushes'

    # Reset our mock object
    mock_post.reset_mock()

    # Add another attachment so we drop into the area of the PushBullet code
    # that sends remaining attachments (if more detected)
    attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Send our attachments
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 7
    # Image Prep
    assert mock_post.call_args_list[0][0][0] == \
        'https://api.pushbullet.com/v2/upload-request'
    assert mock_post.call_args_list[1][0][0] == \
        'https://upload.pushbullet.com/abcd123'
    assert mock_post.call_args_list[2][0][0] == \
        'https://api.pushbullet.com/v2/upload-request'
    assert mock_post.call_args_list[3][0][0] == \
        'https://upload.pushbullet.com/abcd123'
    # Message
    assert mock_post.call_args_list[4][0][0] == \
        'https://api.pushbullet.com/v2/pushes'
    # Image Send
    assert mock_post.call_args_list[5][0][0] == \
        'https://api.pushbullet.com/v2/pushes'
    assert mock_post.call_args_list[6][0][0] == \
        'https://api.pushbullet.com/v2/pushes'

    # Reset our mock object
    mock_post.reset_mock()

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(body="test", attach=attach) is False

    # Test our call count
    assert mock_post.call_count == 0

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Prepare a bad response
    bad_response = mock.Mock()
    bad_response.content = dumps({
        "file_name":
        "cat.jpg",
        "file_type":
        "image/jpeg",
        "file_url":
        "https://dl.pushb.com/abc/cat.jpg",
        "upload_url":
        "https://upload.pushbullet.com/abcd123",
    })
    bad_response.status_code = requests.codes.internal_server_error

    # Prepare a bad response
    bad_json_response = mock.Mock()
    bad_json_response.content = '}'
    bad_json_response.status_code = requests.codes.ok

    # Throw an exception on the first call to requests.post()
    mock_post.return_value = None
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = side_effect

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the second call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [response, side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the third call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [response, response, side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the forth call to requests.post()
    for side_effect in (requests.RequestException(), OSError(), bad_response):
        mock_post.side_effect = [response, response, response, side_effect]

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False

    # Test case where we don't get a valid response back
    mock_post.side_effect = bad_json_response

    # We'll fail because of an invalid json object
    assert obj.send(body="test", attach=attach) is False
Beispiel #17
0
def test_notify_mailgun_plugin_attachments(mock_post):
    """
    API: NotifyMailgun() Attachments

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    okay_response = requests.Request()
    okay_response.status_code = requests.codes.ok
    okay_response.content = ""

    # Assign our mock object our return value
    mock_post.return_value = okay_response

    # API Key
    apikey = 'abc123'

    obj = Apprise.instantiate(
        'mailgun://[email protected]/{}'.format(apikey))
    assert isinstance(obj, plugins.NotifyMailgun)

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test invalid attachment
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    mock_post.return_value = None
    mock_post.side_effect = OSError()
    # We can't send the message if we can't read the attachment
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    # Get a appropriate "builtin" module name for pythons 2/3.
    if sys.version_info.major >= 3:
        builtin_open_function = 'builtins.open'

    else:
        builtin_open_function = '__builtin__.open'

    # Test Valid Attachment (load 3)
    path = (
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
    )
    attach = AppriseAttachment(path)

    # Return our good configuration
    mock_post.side_effect = None
    mock_post.return_value = okay_response
    with mock.patch(builtin_open_function, side_effect=OSError()):
        # We can't send the message we can't open the attachment for reading
        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

    # Do it again, but fail on the third file
    with mock.patch(
            builtin_open_function,
            side_effect=(mock.Mock(), mock.Mock(), OSError())):

        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

    with mock.patch(builtin_open_function) as mock_open:
        mock_fp = mock.Mock()
        mock_fp.seek.side_effect = OSError()
        mock_open.return_value = mock_fp

        # We can't send the message we can't seek through it
        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

        mock_post.reset_mock()
        # Fail on the third file; this tests the for-loop inside the seek()
        # section of the code that calls close() on previously opened files
        mock_fp.seek.side_effect = (None, None, OSError())
        mock_open.return_value = mock_fp
        # We can't send the message we can't seek through it
        assert obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach) is False

    # test the handling of our batch modes
    obj = Apprise.instantiate(
        'mailgun://[email protected]/{}/'
        '[email protected]/[email protected]?batch=yes'.format(apikey))
    assert isinstance(obj, plugins.NotifyMailgun)

    # Force our batch to break into separate messages
    obj.default_batch_size = 1
    # We'll send 2 messages
    mock_post.reset_mock()

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True
    assert mock_post.call_count == 2

    # single batch
    mock_post.reset_mock()
    # We'll send 1 message
    obj.default_batch_size = 2

    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True
    assert mock_post.call_count == 1
Beispiel #18
0
def test_attach_http(mock_get):
    """
    API: AttachHTTP() object

    """

    # Define our good:// url
    class GoodNotification(NotifyBase):
        def __init__(self, *args, **kwargs):
            super(GoodNotification, self).__init__(*args, **kwargs)

        def notify(self, *args, **kwargs):
            # Pretend everything is okay
            return True

        def url(self):
            # Support url() function
            return ''

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Temporary path
    path = join(TEST_VAR_DIR, 'apprise-test.gif')

    class DummyResponse(object):
        """
        A dummy response used to manage our object
        """
        status_code = requests.codes.ok
        headers = {
            'Content-Length': getsize(path),
            'Content-Type': 'image/gif',
        }

        # Pointer to file
        ptr = None

        # used to return random keep-alive chunks
        _keepalive_chunk_ref = 0

        def close(self):
            return

        def iter_content(self, chunk_size=1024):
            """Lazy function (generator) to read a file piece by piece.
            Default chunk size: 1k."""

            while True:
                self._keepalive_chunk_ref += 1
                if 16 % self._keepalive_chunk_ref == 0:
                    # Yield a keep-alive block
                    yield ''

                data = self.ptr.read(chunk_size)
                if not data:
                    break
                yield data

        def raise_for_status(self):
            return

        def __enter__(self):
            self.ptr = open(path, 'rb')
            return self

        def __exit__(self, *args, **kwargs):
            self.ptr.close()

    # Prepare Mock
    dummy_response = DummyResponse()
    mock_get.return_value = dummy_response

    # Test custom url get parameters
    results = AttachHTTP.parse_url(
        'http://*****:*****@localhost/apprise.gif?dl=1&cache=300')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    assert isinstance(attachment.url(), six.string_types) is True

    # Test that our extended variables are passed along
    assert mock_get.call_count == 0
    assert attachment
    assert mock_get.call_count == 1
    assert 'params' in mock_get.call_args_list[0][1]
    assert 'dl' in mock_get.call_args_list[0][1]['params']

    # Verify that arguments that are reserved for apprise are not
    # passed along
    assert 'cache' not in mock_get.call_args_list[0][1]['params']

    results = AttachHTTP.parse_url(
        'http://*****:*****@localhost/apprise.gif?+key=value&cache=True')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None
    # No Content-Disposition; so we use filename from path
    assert attachment.name == 'apprise.gif'
    assert attachment.mimetype == 'image/gif'

    results = AttachHTTP.parse_url(
        'http://*****:*****@localhost/ignore-filename.gif')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None
    assert attachment.mimetype == 'image/gif'
    # Because we could determine our mime type, we could build an extension
    # for our unknown filename
    assert attachment.name == 'myimage.gif'
    assert attachment
    assert len(attachment) == getsize(path)

    # Similar to test above except we make our max message size just 1 byte
    # smaller then our gif file. This will cause us to fail to read the
    # attachment
    AttachHTTP.max_file_size = getsize(path) - 1
    results = AttachHTTP.parse_url('http://localhost/toobig.jpg')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    # we can not download this attachment
    assert not attachment
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None
    assert attachment.mimetype is None
    assert attachment.name is None
    assert len(attachment) == 0

    # Disable our file size limitations
    AttachHTTP.max_file_size = 0
    results = AttachHTTP.parse_url('http://user@localhost')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None
    assert attachment.mimetype == 'image/gif'
    # Because we could determine our mime type, we could build an extension
    # for our unknown filename
    assert attachment.name == 'myimage.gif'
    assert attachment
    assert len(attachment) == getsize(path)

    # Set our header up with an invalid Content-Length; we can still process
    # this data. It just means we track it lower when reading back content
    dummy_response.headers = {'Content-Length': 'invalid'}
    results = AttachHTTP.parse_url('http://localhost/invalid-length.gif')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None
    assert attachment.mimetype == 'image/gif'
    # Because we could determine our mime type, we could build an extension
    # for our unknown filename
    assert attachment.name == 'invalid-length.gif'
    assert attachment

    # Give ourselves nothing to work with
    dummy_response.headers = {}
    results = AttachHTTP.parse_url('http://user@localhost')
    assert isinstance(results, dict)
    attachment = AttachHTTP(**results)
    # we can not download this attachment
    assert attachment
    assert isinstance(attachment.url(), six.string_types) is True
    # No mime-type and/or filename over-ride was specified, so therefore it
    # won't show up in the generated URL
    assert re.search(r'[?&]mime=', attachment.url()) is None
    assert re.search(r'[?&]name=', attachment.url()) is None

    # Handle edge-case where detected_name is None for whatever reason
    attachment.detected_name = None
    assert attachment.mimetype == attachment.unknown_mimetype
    assert attachment.name.startswith(AttachHTTP.unknown_filename)
    assert len(attachment) == getsize(path)

    # Exception handling
    mock_get.return_value = None
    for _exception in REQUEST_EXCEPTIONS:
        aa = AppriseAttachment.instantiate(
            'http://localhost/exception.gif?cache=30')
        assert isinstance(aa, AttachHTTP)

        mock_get.side_effect = _exception
        assert not aa

    # Restore value
    AttachHTTP.max_file_size = max_file_size
Beispiel #19
0
def test_slack_oauth_access_token(mock_post):
    """
    API: NotifySlack() OAuth Access Token Tests

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Generate an invalid bot token
    token = 'xo-invalid'

    request = mock.Mock()
    request.content = dumps({
        'ok': True,
        'message': '',

        # Attachment support
        'file': {
            'url_private': 'http://localhost',
        }
    })
    request.status_code = requests.codes.ok

    # We'll fail to validate the access_token
    with pytest.raises(TypeError):
        plugins.NotifySlack(access_token=token)

    # Generate a (valid) bot token
    token = 'xoxb-1234-1234-abc124'

    # Prepare Mock
    mock_post.return_value = request

    # Variation Initializations
    obj = plugins.NotifySlack(access_token=token, targets='#apprise')
    assert isinstance(obj, plugins.NotifySlack) is True
    assert isinstance(obj.url(), six.string_types) is True

    # apprise room was found
    assert obj.send(body="test") is True

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test invalid attachment
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    # Test case where expected return attachment payload is invalid
    request.content = dumps({
        'ok': True,
        'message': '',

        # Attachment support
        'file': None
    })
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    # We'll fail because of the bad 'file' response
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is False

    # Slack requests pay close attention to the response to determine
    # if things go well... this is not a good JSON response:
    request.content = '{'

    # As a result, we'll fail to send our notification
    assert obj.send(body="test", attach=attach) is False

    request.content = dumps({
        'ok': False,
        'message': 'We failed',
    })

    # A response from Slack (even with a 200 response) still
    # results in a failure:
    assert obj.send(body="test", attach=attach) is False

    # Handle exceptions reading our attachment from disk (should it happen)
    mock_post.side_effect = OSError("Attachment Error")
    mock_post.return_value = None

    # We'll fail now because of an internal exception
    assert obj.send(body="test") is False
def test_pushbullet_attachments(mock_post):
    """
    API: NotifyPushBullet() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    access_token = 't' * 32

    # Prepare Mock return object
    response = mock.Mock()
    response.content = dumps({
        "file_name":
        "cat.jpg",
        "file_type":
        "image/jpeg",
        "file_url":
        "https://dl.pushb.com/abc/cat.jpg",
        "upload_url":
        "https://upload.pushbullet.com/abcd123",
    })
    response.status_code = requests.codes.ok

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Test our markdown
    obj = Apprise.instantiate(
        'pbul://{}/?format=markdown'.format(access_token))

    # Throw an exception on the first call to requests.post()
    mock_post.side_effect = requests.RequestException()

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the second call to requests.post()
    mock_post.side_effect = [response, OSError()]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the third call to requests.post()
    mock_post.side_effect = [response, response, requests.RequestException()]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the forth call to requests.post()
    mock_post.side_effect = [
        response, response, response,
        requests.RequestException()
    ]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Test case where we don't get a valid response back
    response.content = '}'
    mock_post.side_effect = response

    # We'll fail because of an invalid json object
    assert obj.send(body="test", attach=attach) is False
Beispiel #21
0
def test_notify_pushsafer_plugin(mock_post):
    """
    API: NotifyPushSafer() Tests

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Private Key
    privatekey = 'abc123'

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok
    mock_post.return_value.content = dumps({
        'status': 1,
        'success': "okay",
    })

    # Exception should be thrown about the fact no private key was specified
    with pytest.raises(TypeError):
        plugins.NotifyPushSafer(privatekey=None)

    # Multiple Attachment Support
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment()
    for _ in range(0, 4):
        attach.add(path)

    obj = plugins.NotifyPushSafer(privatekey=privatekey)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test error reading attachment from disk
    with mock.patch('io.open', side_effect=OSError):
        obj.notify(
            body='body', title='title', notify_type=NotifyType.INFO,
            attach=attach)

    # Test unsupported mime type
    attach = AppriseAttachment(path)

    attach[0]._mimetype = 'application/octet-stream'

    # We gracefully just don't send the attachment in these cases;
    # The notify itself will still be successful
    mock_post.reset_mock()
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # the 'p', 'p2', and 'p3' are the data variables used when including an
    # image.
    assert 'data' in mock_post.call_args[1]
    assert 'p' not in mock_post.call_args[1]['data']
    assert 'p2' not in mock_post.call_args[1]['data']
    assert 'p3' not in mock_post.call_args[1]['data']

    # Invalid file path
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False
Beispiel #22
0
def test_smtplib_send_okay(mock_smtplib):
    """
    API: Test a successfully sent email

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Defaults to HTML
    obj = Apprise.instantiate('mailto://*****:*****@gmail.com',
                              suppress_exceptions=False)
    assert isinstance(obj, plugins.NotifyEmail)

    # Support an email simulation where we can correctly quit
    mock_smtplib.starttls.return_value = True
    mock_smtplib.login.return_value = True
    mock_smtplib.sendmail.return_value = True
    mock_smtplib.quit.return_value = True

    assert obj.notify(body='body', title='test',
                      notify_type=NotifyType.INFO) is True

    # Set Text
    obj = Apprise.instantiate('mailto://*****:*****@gmail.com?format=text',
                              suppress_exceptions=False)
    assert isinstance(obj, plugins.NotifyEmail)

    assert obj.notify(body='body', title='test',
                      notify_type=NotifyType.INFO) is True

    # Create an apprise object to work with as well
    a = Apprise()
    assert a.add('mailto://*****:*****@gmail.com?format=text')

    # Send Attachment with success
    attach = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    assert obj.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # same results happen from our Apprise object
    assert a.notify(body='body', title='test', attach=attach) is True

    # test using an Apprise Attachment object
    assert obj.notify(body='body',
                      title='test',
                      notify_type=NotifyType.INFO,
                      attach=AppriseAttachment(attach)) is True

    # same results happen from our Apprise object
    assert a.notify(
        body='body', title='test', attach=AppriseAttachment(attach)) is True

    max_file_size = AttachBase.max_file_size
    # Now do a case where the file can't be sent

    AttachBase.max_file_size = 1
    assert obj.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is False

    # same results happen from our Apprise object
    assert a.notify(body='body', title='test', attach=attach) is False

    # Restore value
    AttachBase.max_file_size = max_file_size
def test_notify_xml_plugin_attachments(mock_post):
    """
    API: NotifyXML() Attachments

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    okay_response = requests.Request()
    okay_response.status_code = requests.codes.ok
    okay_response.content = ""

    # Assign our mock object our return value
    mock_post.return_value = okay_response

    obj = Apprise.instantiate('xml://localhost.localdomain/')
    assert isinstance(obj, plugins.NotifyXML)

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    attach = AppriseAttachment(path)
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Test invalid attachment
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=path) is False

    # Get a appropriate "builtin" module name for pythons 2/3.
    if sys.version_info.major >= 3:
        builtin_open_function = 'builtins.open'

    else:
        builtin_open_function = '__builtin__.open'

    # Test Valid Attachment (load 3)
    path = (
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
        os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
    )
    attach = AppriseAttachment(path)

    # Return our good configuration
    mock_post.side_effect = None
    mock_post.return_value = okay_response
    with mock.patch(builtin_open_function, side_effect=OSError()):
        # We can't send the message we can't open the attachment for reading
        assert obj.notify(body='body',
                          title='title',
                          notify_type=NotifyType.INFO,
                          attach=attach) is False

    # test the handling of our batch modes
    obj = Apprise.instantiate('xml://[email protected]/')
    assert isinstance(obj, plugins.NotifyXML)

    # Now send an attachment normally without issues
    mock_post.reset_mock()
    assert obj.notify(
        body='body', title='title', notify_type=NotifyType.INFO,
        attach=attach) is True
    assert mock_post.call_count == 1
Beispiel #24
0
def test_pushbullet_attachments(mock_post):
    """
    API: NotifyPushBullet() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    access_token = 't' * 32

    # Prepare Mock return object
    response = mock.Mock()
    response.content = dumps({
        "file_name": "cat.jpg",
        "file_type": "image/jpeg",
        "file_url": "https://dl.pushb.com/abc/cat.jpg",
        "upload_url": "https://upload.pushbullet.com/abcd123",
    })
    response.status_code = requests.codes.ok
    mock_post.return_value = response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Test our markdown
    obj = Apprise.instantiate(
        'pbul://{}/?format=markdown'.format(access_token))

    # Send a good attachment
    assert obj.notify(body="test", attach=attach) is True

    # Add another attachment so we drop into the area of the PushBullet code
    # that sends remaining attachments (if more detected)
    attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Send our attachments
    assert obj.notify(body="test", attach=attach) is True

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(body="test", attach=attach) is False

    # Throw an exception on the first call to requests.post()
    mock_post.return_value = None
    mock_post.side_effect = requests.RequestException()

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the second call to requests.post()
    mock_post.side_effect = [response, OSError()]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the third call to requests.post()
    mock_post.side_effect = [
        response, response, requests.RequestException()]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the forth call to requests.post()
    mock_post.side_effect = [
        response, response, response, requests.RequestException()]

    # We'll fail now because of an internal exception
    assert obj.send(body="test", attach=attach) is False

    # Test case where we don't get a valid response back
    response.content = '}'
    mock_post.side_effect = response

    # We'll fail because of an invalid json object
    assert obj.send(body="test", attach=attach) is False

    #
    # Test bad responses
    #

    # Prepare a bad response
    response.content = dumps({
        "file_name": "cat.jpg",
        "file_type": "image/jpeg",
        "file_url": "https://dl.pushb.com/abc/cat.jpg",
        "upload_url": "https://upload.pushbullet.com/abcd123",
    })
    bad_response = mock.Mock()
    bad_response.content = response.content
    bad_response.status_code = 400

    # Throw an exception on the third call to requests.post()
    mock_post.return_value = bad_response

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # We'll fail now because we were unable to send the attachment
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the second call
    mock_post.side_effect = [response, bad_response, response]
    assert obj.send(body="test", attach=attach) is False

    # Throw an OSError
    mock_post.side_effect = [response, OSError()]
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the third call
    mock_post.side_effect = [response, response, bad_response]
    assert obj.send(body="test", attach=attach) is False

    # Throw an exception on the fourth call
    mock_post.side_effect = [response, response, response, bad_response]
    assert obj.send(body="test", attach=attach) is False

    # A good message
    mock_post.side_effect = [response, response, response, response]
    assert obj.send(body="test", attach=attach) is True
Beispiel #25
0
def test_plugin_ntfy_attachments(mock_post):
    """
    NotifyNtfy() Attachment Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyNtfy.request_rate_per_sec = 0

    # Prepare Mock return object
    response = mock.Mock()
    response.content = GOOD_RESPONSE_TEXT
    response.status_code = requests.codes.ok
    mock_post.return_value = response

    # Test how the notifications work without attachments as they use the
    # JSON type posting instead

    # Reset our mock object
    mock_post.reset_mock()

    # Prepare our object
    obj = Apprise.instantiate('ntfy://*****:*****@localhost:8080/topic')

    # Send a good attachment
    assert obj.notify(title="hello", body="world")
    assert mock_post.call_count == 1

    assert mock_post.call_args_list[0][0][0] == \
        'http://*****:*****@localhost:8084/topic')

    # Send a good attachment
    assert obj.notify(body="test", attach=attach) is True

    # Test our call count; includes both image and message
    assert mock_post.call_count == 1

    assert mock_post.call_args_list[0][0][0] == \
        'http://localhost:8084/topic'

    assert mock_post.call_args_list[0][1]['params']['message'] == 'test'
    assert 'title' not in mock_post.call_args_list[0][1]['params']
    assert mock_post.call_args_list[0][1]['params']['filename'] == \
        'apprise-test.gif'

    # Reset our mock object
    mock_post.reset_mock()

    # Add another attachment so we drop into the area of the PushBullet code
    # that sends remaining attachments (if more detected)
    attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.png'))

    # Send our attachments
    assert obj.notify(body="test", title="wonderful", attach=attach) is True

    # Test our call count
    assert mock_post.call_count == 2
    # Image + Message sent
    assert mock_post.call_args_list[0][0][0] == \
        'http://localhost:8084/topic'
    assert mock_post.call_args_list[0][1]['params']['message'] == \
        'test'
    assert mock_post.call_args_list[0][1]['params']['title'] == \
        'wonderful'
    assert mock_post.call_args_list[0][1]['params']['filename'] == \
        'apprise-test.gif'

    # Image no 2 (no message)
    assert mock_post.call_args_list[1][0][0] == \
        'http://localhost:8084/topic'
    assert 'message' not in mock_post.call_args_list[1][1]['params']
    assert 'title' not in mock_post.call_args_list[1][1]['params']
    assert mock_post.call_args_list[1][1]['params']['filename'] == \
        'apprise-test.png'

    # Reset our mock object
    mock_post.reset_mock()

    # An invalid attachment will cause a failure
    path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
    attach = AppriseAttachment(path)
    assert obj.notify(body="test", attach=attach) is False

    # Test our call count
    assert mock_post.call_count == 0

    # prepare our attachment
    attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif'))

    # Throw an exception on the first call to requests.post()
    mock_post.return_value = None
    for side_effect in (requests.RequestException(), OSError()):
        mock_post.side_effect = side_effect

        # We'll fail now because of our error handling
        assert obj.send(body="test", attach=attach) is False