From c7f015bf7ce6b43bfbaa59c157be63846c299b7e Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 23 Jan 2021 17:35:04 -0500 Subject: [PATCH] improved test coverage --- apprise/plugins/NotifyEmail.py | 4 +- apprise/plugins/NotifyMailgun.py | 2 +- apprise/plugins/NotifyPushBullet.py | 5 +- test/test_api.py | 55 +++-------- test/test_discord_plugin.py | 47 +++++++-- test/test_pushbullet.py | 146 ++++++++++++++++------------ test/test_pushover.py | 25 ++++- test/test_utils.py | 19 ++++ 8 files changed, 179 insertions(+), 124 deletions(-) diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index d3b5cd1c..4320a8e2 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -636,11 +636,11 @@ class NotifyEmail(NotifyBase): except TypeError: # Python v2.x Support (no charset keyword) # Format our cc addresses to support the Name field - cc = [formataddr( + cc = [formataddr( # pragma: no branch (self.names.get(addr, False), addr)) for addr in cc] # Format our bcc addresses to support the Name field - bcc = [formataddr( + bcc = [formataddr( # pragma: no branch (self.names.get(addr, False), addr)) for addr in bcc] self.logger.debug( diff --git a/apprise/plugins/NotifyMailgun.py b/apprise/plugins/NotifyMailgun.py index f5c94891..481c1d25 100644 --- a/apprise/plugins/NotifyMailgun.py +++ b/apprise/plugins/NotifyMailgun.py @@ -485,7 +485,7 @@ class NotifyMailgun(NotifyBase): except TypeError: # Python v2.x Support (no charset keyword) # Format our cc addresses to support the Name field - payload['cc'] = ','.join([formataddr( + payload['cc'] = ','.join([formataddr( # pragma: no branch (self.names.get(addr, False), addr)) for addr in cc]) diff --git a/apprise/plugins/NotifyPushBullet.py b/apprise/plugins/NotifyPushBullet.py index 9bae32f9..53240d2e 100644 --- a/apprise/plugins/NotifyPushBullet.py +++ b/apprise/plugins/NotifyPushBullet.py @@ -367,8 +367,9 @@ class NotifyPushBullet(NotifyBase): except (OSError, IOError) as e: self.logger.warning( - 'An I/O error occurred while reading {}.'.format( - payload.name if payload else 'attachment')) + 'An I/O error occurred while handling {}.'.format( + payload.name if isinstance(payload, AttachBase) + else payload)) self.logger.debug('I/O Exception: %s' % str(e)) return False, response diff --git a/test/test_api.py b/test/test_api.py index f6a9b3bd..1fb8eab7 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -30,8 +30,6 @@ import six import pytest import requests import mock -from os import chmod -from os import getuid from os.path import dirname from os.path import join @@ -846,48 +844,21 @@ def test_apprise_asset(tmpdir): NotifyImageSize.XY_256, must_exist=True) is not None - # If we make the file un-readable however, we won't be able to read it - # This test is just showing that we won't throw an exception - if getuid() == 0: - # Root always over-rides 0x000 permission settings making the below - # tests futile - pytest.skip('The Root user can not run file permission tests.') + # Test case where we can't access the image file + if sys.version_info.major <= 2: + # Python v2.x + with mock.patch('__builtin__.open', side_effect=OSError()): + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None - chmod(dirname(sub.strpath), 0o000) - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None + # Our content is retrivable again + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None + else: + # Python >= v3.x + with mock.patch('builtins.open', side_effect=OSError()): + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None - # Our path doesn't exist anymore using this logic - assert a.image_path( - NotifyType.INFO, - NotifyImageSize.XY_256, - must_exist=True) is None - - # Return our permission so we don't have any problems with our cleanup - chmod(dirname(sub.strpath), 0o700) - - # Our content is retrivable again - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None - - # our file path is accessible again too - assert a.image_path( - NotifyType.INFO, - NotifyImageSize.XY_256, - must_exist=True) is not None - - # We do the same test, but set the permission on the file - chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o000) - - # our path will still exist in this case - assert a.image_path( - NotifyType.INFO, - NotifyImageSize.XY_256, - must_exist=True) is not None - - # but we will not be able to open it - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None - - # Restore our permissions - chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o640) + # Our content is retrivable again + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None # Disable all image references a = AppriseAsset(image_path_mask=False, image_url_mask=False) diff --git a/test/test_discord_plugin.py b/test/test_discord_plugin.py index ffd40bbb..a15ebf8e 100644 --- a/test/test_discord_plugin.py +++ b/test/test_discord_plugin.py @@ -251,9 +251,16 @@ def test_discord_attachments(mock_post): 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 = requests.Request() - mock_post.return_value.status_code = requests.codes.ok + mock_post.return_value = response # Test our markdown obj = Apprise.instantiate( @@ -266,6 +273,15 @@ def test_discord_attachments(mock_post): 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) @@ -273,15 +289,28 @@ def test_discord_attachments(mock_post): 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 + + 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 diff --git a/test/test_pushbullet.py b/test/test_pushbullet.py index 10bfb606..8d3e99bc 100644 --- a/test/test_pushbullet.py +++ b/test/test_pushbullet.py @@ -72,6 +72,23 @@ def test_pushbullet_attachments(mock_post): # 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')) @@ -79,85 +96,86 @@ def test_pushbullet_attachments(mock_post): # 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 - # Throw an exception on the first call to requests.post() - mock_post.return_value = None - mock_post.side_effect = requests.RequestException() + # Test our call count + assert mock_post.call_count == 0 - # 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 our attachment + attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif')) # Prepare a bad response - response.content = dumps({ + 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 = mock.Mock() - bad_response.content = response.content - bad_response.status_code = 400 + 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() - mock_post.return_value = bad_response + for side_effect in (requests.RequestException(), OSError(), bad_response): + mock_post.side_effect = [response, response, side_effect] - # prepare our attachment - attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, 'apprise-test.gif')) + # We'll fail now because of our error handling + assert obj.send(body="test", attach=attach) is False - # We'll fail now because we were unable to send the attachment + # 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 - - # 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 diff --git a/test/test_pushover.py b/test/test_pushover.py index 6de2a311..f915fb1c 100644 --- a/test/test_pushover.py +++ b/test/test_pushover.py @@ -52,11 +52,19 @@ def test_pushover_attachments(mock_post, tmpdir): user_key = 'u' * 30 api_token = 'a' * 30 - # Prepare Mock return object + # 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 @@ -70,6 +78,11 @@ def test_pushover_attachments(mock_post, tmpdir): # 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' + # Test multiple attachments assert attach.add(os.path.join(TEST_VAR_DIR, 'apprise-test.gif')) assert obj.notify(body="test", attach=attach) is True @@ -101,8 +114,12 @@ def test_pushover_attachments(mock_post, tmpdir): # 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 + + # 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 diff --git a/test/test_utils.py b/test/test_utils.py index 37c2ba73..1c9f4093 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -522,6 +522,25 @@ def test_parse_bool(): assert utils.parse_bool('OhYeah', True) is True +def test_is_uuid(): + """ + API: is_uuid() function + """ + # Invalid Entries + assert utils.is_uuid('invalid') is False + assert utils.is_uuid(None) is False + assert utils.is_uuid(5) is False + assert utils.is_uuid(object) is False + + # A slightly invalid uuid4 entry + assert utils.is_uuid('591ed387-fa65-ac97-9712-b9d2a15e42a9') is False + assert utils.is_uuid('591ed387-fa65-Jc97-9712-b9d2a15e42a9') is False + + # Valid UUID4 Entries + assert utils.is_uuid('591ed387-fa65-4c97-9712-b9d2a15e42a9') is True + assert utils.is_uuid('32b0b447-fe84-4df1-8368-81925e729265') is True + + def test_is_hostname(): """ API: is_hostname() function