diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index b23f11af96..00b9a2d6bb 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -590,6 +590,9 @@ def _send_server_notification(self, body, title='', attachments = None if attach and self.attachment_support: attachments = self._send_attachments(attach) + if not attachments: + # take an early exit + return False while len(rooms) > 0: @@ -687,6 +690,10 @@ def _send_attachments(self, attach): payloads = [] for attachment in attach: + if not attachment: + # invalid attachment (bad file) + return False + if not re.match(r'^image/', attachment.mimetype, re.I): # unsuppored at this time continue @@ -1198,6 +1205,13 @@ def _fetch(self, path, payload=None, params=None, attachment=None, # Return; we're done return (False, response) + except (OSError, IOError) as e: + self.logger.warning( + 'An I/O error occurred while reading {}.'.format( + attachment.name if attachment else 'unknown file')) + self.logger.debug('I/O Exception: %s' % str(e)) + return (False, {}) + return (True, response) # If we get here, we ran out of retries diff --git a/test/test_plugin_matrix.py b/test/test_plugin_matrix.py index 14a6702a13..624ed99e65 100644 --- a/test/test_plugin_matrix.py +++ b/test/test_plugin_matrix.py @@ -32,9 +32,10 @@ from unittest import mock +import os import requests import pytest -from apprise import AppriseAsset +from apprise import Apprise, AppriseAsset, AppriseAttachment, NotifyType from json import dumps from apprise.plugins.NotifyMatrix import NotifyMatrix @@ -52,6 +53,9 @@ 'home_server': 'localhost', }) +# Attachment Directory +TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var') + # Our Testing URLs apprise_url_tests = ( ################################## @@ -868,3 +872,205 @@ def mock_function_handing(url, data, **kwargs): assert obj.access_token is None assert obj.notify('test', 'test') is True + + +@mock.patch('requests.get') +@mock.patch('requests.post') +def test_plugin_matrix_attachments_api_v3(mock_post, mock_get): + """ + NotifyMatrix() Attachment Checks (v3) + + """ + + # Prepare a good response + response = mock.Mock() + response.status_code = requests.codes.ok + response.content = MATRIX_GOOD_RESPONSE.encode('utf-8') + + # 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 + mock_get.return_value = response + + # Instantiate our object + obj = Apprise.instantiate('matrix://user:pass@localhost/#general?v=3') + + # 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 + + attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif')) + + # Test our call count + assert mock_post.call_count == 5 + assert mock_post.call_args_list[0][0][0] == \ + 'http://localhost/_matrix/client/v3/login' + assert mock_post.call_args_list[1][0][0] == \ + 'http://localhost/_matrix/media/v3/upload' + assert mock_post.call_args_list[2][0][0] == \ + 'http://localhost/_matrix/client/v3/join/%23general%3Alocalhost' + assert mock_post.call_args_list[3][0][0] == \ + 'http://localhost/_matrix/client/v3/rooms/%21abc123%3Alocalhost/' \ + 'send/m.room.message' + assert mock_post.call_args_list[4][0][0] == \ + 'http://localhost/_matrix/client/v3/rooms/%21abc123%3Alocalhost/' \ + 'send/m.room.message' + + # Attach an unsupported file type + attach = AppriseAttachment( + os.path.join(TEST_VAR_DIR, 'apprise-archive.zip')) + assert obj.notify( + body='body', title='title', notify_type=NotifyType.INFO, + attach=attach) is False + + # 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 + + +@mock.patch('requests.get') +@mock.patch('requests.post') +def test_plugin_matrix_attachments_api_v2(mock_post, mock_get): + """ + NotifyMatrix() Attachment Checks (v2) + + """ + + # Prepare a good response + response = mock.Mock() + response.status_code = requests.codes.ok + response.content = MATRIX_GOOD_RESPONSE.encode('utf-8') + + # 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 + mock_get.return_value = response + + # Instantiate our object + obj = Apprise.instantiate('matrix://user:pass@localhost/#general?v=3') + + # 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 + + attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif')) + + # Attach an unsupported file + mock_post.return_value = response + mock_get.return_value = response + mock_post.side_effect = None + mock_get.side_effect = None + + # Force a object removal (thus a logout call) + del obj + + # Reset our object + mock_post.reset_mock() + mock_get.reset_mock() + + # Instantiate our object + obj = Apprise.instantiate('matrixs://user:pass@localhost/#general?v=2') + + assert obj.notify( + body='body', title='title', notify_type=NotifyType.INFO, + attach=attach) is True + + # Test our call count + assert mock_post.call_count == 5 + assert mock_post.call_args_list[0][0][0] == \ + 'https://localhost/_matrix/client/r0/login' + assert mock_post.call_args_list[1][0][0] == \ + 'https://localhost/_matrix/media/r0/upload' + assert mock_post.call_args_list[2][0][0] == \ + 'https://localhost/_matrix/client/r0/join/%23general%3Alocalhost' + assert mock_post.call_args_list[3][0][0] == \ + 'https://localhost/_matrix/client/r0/rooms/%21abc123%3Alocalhost/' \ + 'send/m.room.message' + assert mock_post.call_args_list[4][0][0] == \ + 'https://localhost/_matrix/client/r0/rooms/%21abc123%3Alocalhost/' \ + 'send/m.room.message' + + # Attach an unsupported file type + attach = AppriseAttachment( + os.path.join(TEST_VAR_DIR, 'apprise-archive.zip')) + assert obj.notify( + body='body', title='title', notify_type=NotifyType.INFO, + attach=attach) is False + + # 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 + mock_get.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] + mock_get.side_effect = [side_effect] + + 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] + mock_get.side_effect = [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] + mock_get.side_effect = [response, bad_response] + + # We'll fail now because of an internal exception + assert obj.send(body="test", attach=attach) is False