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
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()
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
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
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
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
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)
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 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
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
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'
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)
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
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
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
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
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
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
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
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