def test_invalid_apprise_config(tmpdir): """ Parse invalid configuration includes """ class BadConfig(ConfigBase): # always allow incusion allow_cross_includes = ContentIncludeMode.ALWAYS def __init__(self, **kwargs): super(BadConfig, self).__init__(**kwargs) # We intentionally fail whenever we're initialized raise TypeError() @staticmethod def parse_url(url, *args, **kwargs): # always parseable return ConfigBase.parse_url(url, verify_host=False) # Store our bad configuration in our schema map CONFIG_SCHEMA_MAP['bad'] = BadConfig # temporary file to work with t = tmpdir.mkdir("apprise-bad-obj").join("invalid") buf = """ # Include an invalid schema include invalid:// # An unparsable valid schema include https:// # A valid configuration that will throw an exception include bad:// # Include ourselves (So our recursive includes fails as well) include {} """.format(str(t)) t.write(buf) # Create ourselves a config object with caching disbled ac = AppriseConfig(recursion=2, insecure_includes=True, cache=False) # Nothing loaded yet assert len(ac) == 0 # Add our config assert ac.add(configs=str(t), asset=AppriseAsset()) is True # One configuration file assert len(ac) == 1 # All of the servers were invalid and would not load assert len(ac.servers()) == 0
def test_apprise_config_tagging(tmpdir): """ API: AppriseConfig tagging """ # temporary file to work with t = tmpdir.mkdir("tagging").join("apprise") buf = "gnome://" t.write(buf) # Create ourselves a config object ac = AppriseConfig() # Add an item associated with tag a assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a') is True # Add an item associated with tag b assert ac.add(configs=str(t), asset=AppriseAsset(), tag='b') is True # Add an item associated with tag a or b assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a,b') is True # Now filter: a: assert len(ac.servers(tag='a')) == 2 # Now filter: a or b: assert len(ac.servers(tag='a,b')) == 3 # Now filter: a and b assert len(ac.servers(tag=[('a', 'b')])) == 1 # all matches everything assert len(ac.servers(tag='all')) == 3 # Test cases using the `always` keyword # Create ourselves a config object ac = AppriseConfig() # Add an item associated with tag a assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a,always') is True # Add an item associated with tag b assert ac.add(configs=str(t), asset=AppriseAsset(), tag='b') is True # Add an item associated with tag a or b assert ac.add(configs=str(t), asset=AppriseAsset(), tag='c,d') is True # Now filter: a: assert len(ac.servers(tag='a')) == 1 # Now filter: a or b: assert len(ac.servers(tag='a,b')) == 2 # Now filter: e # we'll match the `always' assert len(ac.servers(tag='e')) == 1 assert len(ac.servers(tag='e', match_always=False)) == 0 # all matches everything assert len(ac.servers(tag='all')) == 3 # Now filter: d # we'll match the `always' tag assert len(ac.servers(tag='d')) == 2 assert len(ac.servers(tag='d', match_always=False)) == 1
def test_notify_matrix_plugin_fetch(mock_post, mock_get): """ API: NotifyMatrix() Server Fetch/API Tests """ # Disable Throttling to speed testing plugins.NotifyBase.request_rate_per_sec = 0 response_obj = { 'room_id': '!abc123:localhost', 'room_alias': '#abc123:localhost', 'joined_rooms': ['!abc123:localhost', '!def456:localhost'], # Login details 'access_token': 'abcd1234', 'user_id': '@apprise:localhost', 'home_server': 'localhost', } def fetch_failed(url, *args, **kwargs): # Default configuration request = mock.Mock() request.status_code = requests.codes.ok request.content = dumps(response_obj) if url.find('/rooms/') > -1: # over-ride on room query request.status_code = 403 request.content = dumps({ u'errcode': u'M_UNKNOWN', u'error': u'Internal server error', }) return request mock_get.side_effect = fetch_failed mock_post.side_effect = fetch_failed obj = plugins.NotifyMatrix(user='******', password='******', include_image=True) assert isinstance(obj, plugins.NotifyMatrix) is True # We would hve failed to send our image notification assert obj.send(user='******', password='******', body="test") is False # Do the same query with no images to fetch asset = AppriseAsset(image_path_mask=False, image_url_mask=False) obj = plugins.NotifyMatrix(user='******', password='******', asset=asset) assert isinstance(obj, plugins.NotifyMatrix) is True # We would hve failed to send our notification assert obj.send(user='******', password='******', body="test") is False # Disable Throttling to speed testing plugins.NotifyBase.request_rate_per_sec = 0 response_obj = { # Registration 'access_token': 'abcd1234', 'user_id': '@apprise:localhost', 'home_server': 'localhost', # For room joining 'room_id': '!abc123:localhost', } # Default configuration mock_get.side_effect = None mock_post.side_effect = None request = mock.Mock() request.status_code = requests.codes.ok request.content = dumps(response_obj) mock_post.return_value = request mock_get.return_value = request obj = plugins.NotifyMatrix(include_image=True) assert isinstance(obj, plugins.NotifyMatrix) is True assert obj.access_token is None assert obj._register() is True assert obj.access_token is not None # Cause retries request.status_code = 429 request.content = dumps({ 'retry_after_ms': 1, }) code, response = obj._fetch('/retry/apprise/unit/test') assert code is False request.content = dumps({'error': { 'retry_after_ms': 1, }}) code, response = obj._fetch('/retry/apprise/unit/test') assert code is False request.content = dumps({'error': {}}) code, response = obj._fetch('/retry/apprise/unit/test') assert code is False
# standard library import random import string from string import Template # 3rd party import gevent from apprise import Apprise from apprise import AppriseAsset # betanin import betanin.config.betanin as conf_betanin from betanin.status import Status _apprise_asset = AppriseAsset() _apprise_asset.app_id = "betanin" _apprise_asset.app_desc = "betanin" APPRISE = Apprise(asset=_apprise_asset) STATUS_LONG = { Status.COMPLETED: "has completed", Status.FAILED: "has failed", Status.NEEDS_INPUT: "needs input", } def _random_string(size=6, chars=string.ascii_lowercase + string.digits): return "".join(random.choice(chars) for x in range(size)) def _make_templates(config): return {
def notify(self, servers, body, title, notify_type=NotifyType.INFO, body_format=NotifyFormat.MARKDOWN): """ processes list of servers specified """ # Apprise Asset Object asset = AppriseAsset(theme=self.default_theme) asset.app_id = 'NZB-Notify' asset.app_desc = 'NZB Notification' asset.app_url = 'https://github.com/caronc/nzb-notify' # Source Theme from GitHub Page asset.image_url_mask = 'https://raw.githubusercontent.com' \ '/caronc/nzb-notify/master/Notify' \ '/apprise-theme/{THEME}/apprise-{TYPE}-{XY}.png' asset.image_path_mask = join( dirname(__file__), 'Notify', 'apprise-theme', '{THEME}', 'apprise-{TYPE}-{XY}.png') # Include Image Flag _url = self.parse_url(self.get('IncludeImage')) # Define some globals to use in this function image_path = None image_url = None if _url: # Toggle our include image flag right away to True include_image = True # Get some more details if not re.match('^(https?|file)$', _url['schema'] ,re.IGNORECASE): self.logger.error( 'An invalid image url protocol (%s://) was specified.' % \ _url['schema'], ) return False if _url['schema'] == 'file': if not isile(_url['fullpath']): self.logger.error( 'The specified file %s was not found.' % \ _url['fullpath'], ) return False image_path = _url['fullpath'] else: # We're dealing with a web request image_url = _url['url'] else: # Dealing with the old way of doing things; just toggling a true/false # flag include_image = self.parse_bool(self.get('IncludeImage'), False) if isinstance(servers, basestring): # servers can be a list of URLs, or it can be # a string which will be parsed into this list # we wanted. servers = self.parse_list(self.get('Servers', '')) # Create our apprise object a = Apprise(asset=asset) for server in servers: # Add our URL if not a.add(server): # Validation Failure self.logger.error( 'Could not initialize %s instance.' % server, ) continue # Notify our servers a.notify(body=body, title=title, notify_type=notify_type, body_format=body_format) # Always return true return True
def test_plugin_telegram_formating_py2(mock_post): """ NotifyTelegram() Python v2 Formatting Tests """ # Disable Throttling to speed testing plugins.NotifyTelegram.request_rate_per_sec = 0 # Prepare Mock mock_post.return_value = requests.Request() mock_post.return_value.status_code = requests.codes.ok mock_post.return_value.content = '{}' # Simple success response mock_post.return_value.content = dumps({ "ok": True, "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", }], } }, ], }) mock_post.return_value.status_code = requests.codes.ok results = plugins.NotifyTelegram.parse_url( 'tgram://123456789:abcdefg_hijklmnop/') instance = plugins.NotifyTelegram(**results) assert isinstance(instance, plugins.NotifyTelegram) response = instance.send(title='title', body='body') assert response is True # 1 call to look up bot owner, and second for notification assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' # Reset our values mock_post.reset_mock() # Now test our HTML Conversion as TEXT) aobj = Apprise() aobj.add('tgram://123456789:abcdefg_hijklmnop/') assert len(aobj) == 1 title = '馃毃 Change detected for <i>Apprise Test Title</i>' body = '<a href="http://localhost"><i>Apprise Body Title</i></a>' \ ' had <a href="http://127.0.0.1">a change</a>' assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a TEXT mode assert payload['text'].encode('utf-8') == \ '<b>\xf0\x9f\x9a\xa8 Change detected for <i>' \ 'Apprise Test Title</i></b>\r\n<' \ 'a href="http://localhost"><i>Apprise Body Title' \ '</i></a> had <a href="http://127.0.0.1"' \ '>a change</a>' # Reset our values mock_post.reset_mock() # Now test our HTML Conversion as TEXT) aobj = Apprise() aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=html') assert len(aobj) == 1 assert aobj.notify(title=title, body=body, body_format=NotifyFormat.HTML) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'].encode('utf-8') == \ '<b>\xf0\x9f\x9a\xa8 Change detected for <i>Apprise Test Title</i>' \ '</b>\r\n<a href="http://localhost"><i>Apprise Body Title</i></a> ' \ 'had <a href="http://127.0.0.1">a change</a>' # Reset our values mock_post.reset_mock() # Now test our MARKDOWN Handling title = '# 馃毃 Change detected for _Apprise Test Title_' body = '_[Apprise Body Title](http://localhost)_' \ ' had [a change](http://127.0.0.1)' aobj = Apprise() aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=markdown') assert len(aobj) == 1 assert aobj.notify(title=title, body=body, body_format=NotifyFormat.MARKDOWN) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'].encode('utf-8') == \ '# \xf0\x9f\x9a\xa8 Change detected for _Apprise Test Title_\r\n_' \ '[Apprise Body Title](http://localhost)_ had ' \ '[a change](http://127.0.0.1)' # Reset our values mock_post.reset_mock() # Upstream to use HTML but input specified as Markdown aobj = Apprise() aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=html') assert len(aobj) == 1 # HTML forced by the command line, but MARKDOWN specified as # upstream mode assert aobj.notify(title=title, body=body, body_format=NotifyFormat.MARKDOWN) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'].encode('utf-8') == \ '<b>\r\n<b>\xf0\x9f\x9a\xa8 Change detected for ' \ '<i>Apprise Test Title</i></b>\r\n</b>\r\n<i>' \ '<a href="http://localhost">Apprise Body Title</a>' \ '</i> had <a href="http://127.0.0.1">a change</a>\r\n' # Reset our values mock_post.reset_mock() # Now test hebrew types (outside of default utf-8) # 讻讜转专转 谞驻诇讗讛 translates to 'A wonderful title' # 讝讜 讛讜讚注讛 translates to 'This is a notification' title = '讻讜转专转 谞驻诇讗讛' \ .decode('utf-8').encode('ISO-8859-8') body = '[_[讝讜 讛讜讚注讛](http://localhost)_' \ .decode('utf-8').encode('ISO-8859-8') asset = AppriseAsset(encoding='utf-8') # Now test default entries aobj = Apprise(asset=asset) aobj.add('tgram://123456789:abcdefg_hijklmnop/') assert len(aobj) == 1 # Our notification will fail because we'll have an encoding error assert not aobj.notify(title=title, body=body) # Nothing was even attempted to be notified assert mock_post.call_count == 0 # Let's use the expected input asset = AppriseAsset(encoding='ISO-8859-8') # Now test default entries aobj = Apprise(asset=asset) aobj.add('tgram://123456789:abcdefg_hijklmnop/') assert len(aobj) == 1 # Our notification will work now assert aobj.notify(title=title, body=body) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'].encode('utf-8') == \ '<b>\xd7\x9b\xd7\x95\xd7\xaa\xd7\xa8\xd7\xaa '\ '\xd7\xa0\xd7\xa4\xd7\x9c\xd7\x90\xd7\x94</b>\r\n[_[\xd7\x96\xd7\x95 '\ '\xd7\x94\xd7\x95\xd7\x93\xd7\xa2\xd7\x94](http://localhost)_' # Now we'll test an edge case where a title was defined, but after # processing it, it was determiend there really wasn't anything there # at all at the end of the day. # Reset our values mock_post.reset_mock() # Upstream to use HTML but input specified as Markdown aobj = Apprise() aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown') assert len(aobj) == 1 # Now test our MARKDOWN Handling (no title defined... not really anyway) title = '# ' body = '_[Apprise Body Title](http://localhost)_' \ ' had [a change](http://127.0.0.2)' # MARKDOWN forced by the command line, but TEXT specified as # upstream mode assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'] == \ '_[Apprise Body Title](http://localhost)_ had ' \ '[a change](http://127.0.0.2)' # Reset our values mock_post.reset_mock() # Upstream to use HTML but input specified as Markdown aobj = Apprise() aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown') assert len(aobj) == 1 # Set an actual title this time title = '# A Great Title' body = '_[Apprise Body Title](http://localhost)_' \ ' had [a change](http://127.0.0.2)' # MARKDOWN forced by the command line, but TEXT specified as # upstream mode assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT) # Test our calls assert mock_post.call_count == 2 assert mock_post.call_args_list[0][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' assert mock_post.call_args_list[1][0][0] == \ 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' payload = loads(mock_post.call_args_list[1][1]['data']) # Test that everything is escaped properly in a HTML mode assert payload['text'] == \ '# A Great Title\r\n_[Apprise Body Title](http://localhost)_ had ' \ '[a change](http://127.0.0.2)'
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, servers, body, title, notify_type=NotifyType.INFO, body_format=NotifyFormat.MARKDOWN): """ processes list of servers specified """ # Decode our data body = decode(body) title = decode(title) # Apprise Asset Object asset = AppriseAsset(theme=self.default_theme) asset.app_id = 'NZB-Notify' asset.app_desc = 'NZB Notification' asset.app_url = 'https://github.com/caronc/nzb-notify' # Source Theme from GitHub Page asset.image_url_mask = 'https://raw.githubusercontent.com' \ '/caronc/nzb-notify/master/Notify' \ '/apprise-theme/{THEME}/apprise-{TYPE}-{XY}.png' asset.image_path_mask = join( dirname(__file__), 'Notify', 'apprise-theme', '{THEME}', 'apprise-{TYPE}-{XY}.png') # Include Image Flag _url = self.parse_url(self.get('IncludeImage')) # Define some globals to use in this function image_path = None image_url = None if _url: # Toggle our include image flag right away to True include_image = True # Get some more details if not re.match('^(https?|file)$', _url['schema'], re.IGNORECASE): self.logger.error( 'An invalid image url protocol (%s://) was specified.' % _url['schema'], ) return False if _url['schema'] == 'file': if not isfile(_url['fullpath']): self.logger.error( 'The specified file %s was not found.' % _url['fullpath'], ) return False image_path = _url['fullpath'] else: # We're dealing with a web request image_url = _url['url'] else: # Dealing with the old way of doing things; just toggling a # true/false flag include_image = self.parse_bool(self.get('IncludeImage'), False) if isinstance(servers, basestring): # servers can be a list of URLs, or it can be # a string which will be parsed into this list # we wanted. servers = self.parse_list(self.get('Servers', '')) # Create our apprise object a = Apprise(asset=asset) for server in servers: # Add our URL if not a.add(server): # Validation Failure self.logger.error( 'Could not initialize %s instance.' % server, ) continue # Notify our servers a.notify(body=body, title=title, notify_type=notify_type, body_format=body_format) # Always return true return True
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) # 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) # 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 notify(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) # 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() try: a.instantiate('throw://localhost', suppress_exceptions=False) assert (False) except TypeError: assert (True) 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() try: a.instantiate({ 'schema': 'throw', 'host': 'localhost' }, suppress_exceptions=False) assert (False) except TypeError: assert (True) assert (len(a) == 0) assert (a.instantiate({ 'schema': 'throw', 'host': 'localhost' }, suppress_exceptions=True) is None) assert (len(a) == 0)
def test_apprise_config(tmpdir): """ API: AppriseConfig basic testing """ # Create ourselves a config object ac = AppriseConfig() # There are no servers loaded assert len(ac) == 0 # Object can be directly checked as a boolean; response is False # when there are no entries loaded assert not ac # lets try anyway assert len(ac.servers()) == 0 t = tmpdir.mkdir("simple-formatting").join("apprise") t.write(""" # A comment line over top of a URL mailto://usera:[email protected] # A line with mulitiple tag assignments to it taga,tagb=gnome:// # Event if there is accidental leading spaces, this configuation # is accepting of htat and will not exclude them tagc=kde:// # A very poorly structured url sns://:@/ # Just 1 token provided causes exception sns://T1JJ3T3L2/ # XML xml://localhost/?+HeaderEntry=Test&:IgnoredEntry=Ignored """) # Create ourselves a config object ac = AppriseConfig(paths=str(t)) # One configuration file should have been found assert len(ac) == 1 # Object can be directly checked as a boolean; response is True # when there is at least one entry assert ac # We should be able to read our 4 servers from that assert len(ac.servers()) == 4 # Get our URL back assert isinstance(ac[0].url(), six.string_types) # Test cases where our URL is invalid t = tmpdir.mkdir("strange-lines").join("apprise") t.write(""" # basicly this consists of defined tags and no url tag= """) # Create ourselves a config object ac = AppriseConfig(paths=str(t), asset=AppriseAsset()) # One configuration file should have been found assert len(ac) == 1 # No urls were set assert len(ac.servers()) == 0 # Create a ConfigBase object cb = ConfigBase() # Test adding of all entries assert ac.add(configs=cb, asset=AppriseAsset(), tag='test') is True # Test adding of all entries assert ac.add( configs=['file://?', ], asset=AppriseAsset(), tag='test') is False # Test the adding of garbage assert ac.add(configs=object()) is False # Try again but enforce our format ac = AppriseConfig(paths='file://{}?format=text'.format(str(t))) # One configuration file should have been found assert len(ac) == 1 # No urls were set assert len(ac.servers()) == 0 # # Test Internatialization and the handling of unicode characters # istr = """ # Iñtërnâtiônàlization Testing windows://""" if six.PY2: # decode string into unicode istr = istr.decode('utf-8') # Write our content to our file t = tmpdir.mkdir("internationalization").join("apprise") with io.open(str(t), 'wb') as f: f.write(istr.encode('latin-1')) # Create ourselves a config object ac = AppriseConfig(paths=str(t)) # One configuration file should have been found assert len(ac) == 1 # This will fail because our default encoding is utf-8; however the file # we opened was not; it was latin-1 and could not be parsed. assert len(ac.servers()) == 0 # Test iterator count = 0 for entry in ac: count += 1 assert len(ac) == count # We can fix this though; set our encoding to latin-1 ac = AppriseConfig(paths='file://{}?encoding=latin-1'.format(str(t))) # One configuration file should have been found assert len(ac) == 1 # Our URL should be found assert len(ac.servers()) == 1 # Get our URL back assert isinstance(ac[0].url(), six.string_types) # pop an entry from our list assert isinstance(ac.pop(0), ConfigBase) is True # Determine we have no more configuration entries loaded assert len(ac) == 0 # # Test buffer handling (and overflow) t = tmpdir.mkdir("buffer-handling").join("apprise") buf = "gnome://" t.write(buf) # Reset our config object ac.clear() # Create ourselves a config object ac = AppriseConfig(paths=str(t)) # update our length to be the size of our actual file ac[0].max_buffer_size = len(buf) # One configuration file should have been found assert len(ac) == 1 assert len(ac.servers()) == 1 # update our buffer size to be slightly smaller then what we allow ac[0].max_buffer_size = len(buf) - 1 # Content is automatically cached; so even though we adjusted the buffer # above, our results have been cached so we get a 1 response. assert len(ac.servers()) == 1
def test_apprise_add_config(): """ API AppriseConfig.add_config() """ content = """ # A comment line over top of a URL mailto://usera:[email protected] # A line with mulitiple tag assignments to it taga,tagb=gnome:// # Event if there is accidental leading spaces, this configuation # is accepting of htat and will not exclude them tagc=kde:// # A very poorly structured url sns://:@/ # Just 1 token provided causes exception sns://T1JJ3T3L2/ """ # Create ourselves a config object ac = AppriseConfig() assert ac.add_config(content=content) is True # One configuration file should have been found assert len(ac) == 1 assert ac[0].config_format is ConfigFormat.TEXT # Object can be directly checked as a boolean; response is True # when there is at least one entry assert ac # We should be able to read our 3 servers from that assert len(ac.servers()) == 3 # Get our URL back assert isinstance(ac[0].url(), six.string_types) # Test invalid content assert ac.add_config(content=object()) is False assert ac.add_config(content=42) is False assert ac.add_config(content=None) is False # Still only one server loaded assert len(ac) == 1 # Test having a pre-defined asset object and tag created assert ac.add_config( content=content, asset=AppriseAsset(), tag='a') is True # Now there are 2 servers loaded assert len(ac) == 2 # and 6 urls.. (as we've doubled up) assert len(ac.servers()) == 6 content = """ # A YAML File urls: - mailto://usera:[email protected] - gnome://: tag: taga,tagb - json://localhost: +HeaderEntry1: 'a header entry' -HeaderEntryDepricated: 'a deprecated entry' :HeaderEntryIgnored: 'an ignored header entry' - xml://localhost: +HeaderEntry1: 'a header entry' -HeaderEntryDepricated: 'a deprecated entry' :HeaderEntryIgnored: 'an ignored header entry' """ # Create ourselves a config object ac = AppriseConfig() assert ac.add_config(content=content) is True # One configuration file should have been found assert len(ac) == 1 assert ac[0].config_format is ConfigFormat.YAML # Object can be directly checked as a boolean; response is True # when there is at least one entry assert ac # We should be able to read our 4 servers from that assert len(ac.servers()) == 4 # Now an invalid configuration file content = "invalid" # Create ourselves a config object ac = AppriseConfig() assert ac.add_config(content=content) is False # Nothing is loaded assert len(ac.servers()) == 0
def test_apprise(): """ API: Apprise() object """ # Caling load matix a second time which is an internal function causes it # to skip over content already loaded into our matrix and thefore accesses # other if/else parts of the code that aren't otherwise called __load_matrix() a = Apprise() # no items assert len(a) == 0 # Apprise object can also be directly tested with 'if' keyword # No entries results in a False response assert not a # Create an Asset object asset = AppriseAsset(theme='default') # We can load the device using our asset a = Apprise(asset=asset) # We can load our servers up front as well servers = [ 'faast://abcdefghijklmnop-abcdefg', 'kodi://kodi.server.local', ] a = Apprise(servers=servers) # 2 servers loaded assert len(a) == 2 # Apprise object can also be directly tested with 'if' keyword # At least one entry results in a True response assert a # We can retrieve our URLs this way: assert len(a.urls()) == 2 # We can add another server assert a.add('mmosts://mattermost.server.local/' '3ccdd113474722377935511fc85d3dd4') is True assert len(a) == 3 # Try adding nothing but delimiters assert a.add(',, ,, , , , ,') is False # The number of servers added doesn't change 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 '' @staticmethod def parse_url(url, *args, **kwargs): # always parseable return NotifyBase.parse_url(url, verify_host=False) 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 @staticmethod def parse_url(url, *args, **kwargs): # always parseable return NotifyBase.parse_url(url, verify_host=False) # 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 # Test our socket details # rto = Socket Read Timeout # cto = Socket Connect Timeout plugin = a.instantiate('good://localhost?rto=5.1&cto=10') assert isinstance(plugin, NotifyBase) assert plugin.socket_connect_timeout == 10.0 assert plugin.socket_read_timeout == 5.1 plugin = a.instantiate('good://localhost?rto=invalid&cto=invalid') assert isinstance(plugin, NotifyBase) assert plugin.socket_connect_timeout == URLBase.socket_connect_timeout assert plugin.socket_read_timeout == URLBase.socket_read_timeout # 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_apprise_secure_logging(mock_post): """ API: Apprise() secure logging tests """ # Ensure we're not running in a disabled state logging.disable(logging.NOTSET) logger.setLevel(logging.CRITICAL) # Prepare Mock mock_post.return_value = requests.Request() mock_post.return_value.status_code = requests.codes.ok # Default Secure Logging is set to enabled asset = AppriseAsset() assert asset.secure_logging is True # Load our asset a = Apprise(asset=asset) with LogCapture(level=logging.DEBUG) as stream: # add a test server assert a.add("json://*****:*****@localhost") is True # Our servers should carry this flag a[0].asset.secure_logging is True logs = re.split(r'\r*\n', stream.getvalue().rstrip()) assert len(logs) == 1 entry = re.split(r'\s-\s', logs[0]) assert len(entry) == 3 assert entry[1] == 'DEBUG' assert entry[2].startswith( 'Loaded JSON URL: json://user:****@localhost/') # Send notification assert a.notify("test") is True # Test our call count assert mock_post.call_count == 1 # Reset mock_post.reset_mock() # Now we test the reverse configuration and turn off # secure logging. # Default Secure Logging is set to disable asset = AppriseAsset(secure_logging=False) assert asset.secure_logging is False # Load our asset a = Apprise(asset=asset) with LogCapture(level=logging.DEBUG) as stream: # add a test server assert a.add("json://*****:*****@localhost") is True # Our servers should carry this flag a[0].asset.secure_logging is False logs = re.split(r'\r*\n', stream.getvalue().rstrip()) assert len(logs) == 1 entry = re.split(r'\s-\s', logs[0]) assert len(entry) == 3 assert entry[1] == 'DEBUG' # Note that our password is no longer escaped (it is however # url encoded) assert entry[2].startswith( 'Loaded JSON URL: json://user:pass1%24-3%21@localhost/') # Disable Logging logging.disable(logging.CRITICAL)
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) # 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', 'palot://1f418df7577e32b89ac6511f2eb9aa68', ] a = Apprise(servers=servers) # 3 servers loaded assert (len(a) == 3) # We can add another server assert (a.add('mmosts://mattermost.server.local/' '3ccdd113474722377935511fc85d3dd4') is True) assert (len(a) == 4) # 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__() # We fail whenever we're initialized raise TypeError() class GoodNotification(NotifyBase): def __init__(self, **kwargs): super(GoodNotification, self).__init__(notify_format=NotifyFormat.HTML) def notify(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) # Clear our server listings again a.clear() class ThrowNotification(NotifyBase): def notify(self, **kwargs): # Pretend everything is okay raise TypeError() class RuntimeNotification(NotifyBase): def notify(self, **kwargs): # Pretend everything is okay raise RuntimeError() class FailNotification(NotifyBase): def notify(self, **kwargs): # Pretend everything is okay return False # 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() SCHEMA_MAP['throw'] = ThrowInstantiateNotification # Reset our object a.clear() assert (len(a) == 0) # Instantiate a good object plugin = a.instantiate('good://localhost') assert (isinstance(plugin, NotifyBase)) # We an add already substatiated instances into our Apprise object a.add(plugin) assert (len(a) == 1) # Reset our object again a.clear() try: a.instantiate('throw://localhost', suppress_exceptions=False) assert (False) except TypeError: assert (True) assert (len(a) == 0) assert (a.instantiate('throw://localhost', suppress_exceptions=True) is None) assert (len(a) == 0)
def test_apprise_config_with_apprise_obj(tmpdir): """ API: ConfigBase - parse valid config """ # temporary file to work with t = tmpdir.mkdir("apprise-obj").join("apprise") buf = """ good://hostname localhost=good://localhost """ t.write(buf) # Define our good:// url class GoodNotification(NotifyBase): def __init__(self, **kwargs): super(GoodNotification, self).__init__( notify_format=NotifyFormat.HTML, **kwargs) def notify(self, **kwargs): # Pretend everything is okay return True def url(self, **kwargs): # support url() return '' # Store our good notification in our schema map NOTIFY_SCHEMA_MAP['good'] = GoodNotification # Create ourselves a config object ac = AppriseConfig(cache=False) # Nothing loaded yet assert len(ac) == 0 # Add an item associated with tag a assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a') is True # One configuration file assert len(ac) == 1 # 2 services found in it assert len(ac.servers()) == 2 # Pop one of them (at index 0) ac.server_pop(0) # Verify that it no longer listed assert len(ac.servers()) == 1 # Test our ability to add Config objects to our apprise object a = Apprise() # Add our configuration object assert a.add(servers=ac) is True # Detect our 1 entry (originally there were 2 but we deleted one) assert len(a) == 1 # Notify our service assert a.notify(body='apprise configuration power!') is True # Add our configuration object assert a.add( servers=[AppriseConfig(str(t)), AppriseConfig(str(t))]) is True # Detect our 5 loaded entries now; 1 from first config, and another # 2x2 based on adding our list above assert len(a) == 5 # We can't add garbage assert a.add(servers=object()) is False assert a.add(servers=[object(), object()]) is False # Our length is unchanged assert len(a) == 5 # reference index 0 of our list ref = a[0] assert isinstance(ref, NotifyBase) is True # Our length is unchanged assert len(a) == 5 # pop the index ref_popped = a.pop(0) # Verify our response assert isinstance(ref_popped, NotifyBase) is True # Our length drops by 1 assert len(a) == 4 # Content popped is the same as one referenced by index # earlier assert ref == ref_popped # pop an index out of range try: a.pop(len(a)) # We'll thrown an IndexError and not make it this far assert False except IndexError: # As expected assert True # Our length remains unchanged assert len(a) == 4 # Reference content out of range try: a[len(a)] # We'll thrown an IndexError and not make it this far assert False except IndexError: # As expected assert True # reference index at the end of our list ref = a[len(a) - 1] # Verify our response assert isinstance(ref, NotifyBase) is True # Our length stays the same assert len(a) == 4 # We can pop from the back of the list without a problem too ref_popped = a.pop(len(a) - 1) # Verify our response assert isinstance(ref_popped, NotifyBase) is True # Content popped is the same as one referenced by index # earlier assert ref == ref_popped # Our length drops by 1 assert len(a) == 3 # Now we'll test adding another element to the list so that it mixes up # our response object. # Below we add 3 different types, a ConfigBase, NotifyBase, and URL assert a.add( servers=[ ConfigFile(path=(str(t))), 'good://another.host', GoodNotification(**{'host': 'nuxref.com'})]) is True # Our length increases by 4 (2 entries in the config file, + 2 others) assert len(a) == 7 # reference index at the end of our list ref = a[len(a) - 1] # Verify our response assert isinstance(ref, NotifyBase) is True # We can pop from the back of the list without a problem too ref_popped = a.pop(len(a) - 1) # Verify our response assert isinstance(ref_popped, NotifyBase) is True # Content popped is the same as one referenced by index # earlier assert ref == ref_popped # Our length drops by 1 assert len(a) == 6 # pop our list while len(a) > 0: assert isinstance(a.pop(len(a) - 1), NotifyBase) is True
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 run(self, url, meta, mock_post, mock_get): """ Run a specific test """ # Disable Throttling to speed testing plugins.NotifyBase.request_rate_per_sec = 0 # Our expected instance instance = meta.get('instance', None) # Our expected server objects _self = meta.get('self', None) # Our expected Query response (True, False, or exception type) response = meta.get('response', True) # Our expected privacy url # Don't set this if don't need to check it's value privacy_url = meta.get('privacy_url') # Our regular expression url_matches = meta.get('url_matches') # 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) # Whether or not we should include an image with our request; unless # otherwise specified, we assume that images are to be included include_image = meta.get('include_image', True) if include_image: # a default asset asset = AppriseAsset() else: # Disable images asset = AppriseAsset(image_path_mask=False, image_url_mask=False) asset.image_url_logo = None test_requests_exceptions = meta.get('test_requests_exceptions', False) # Mock our request object robj = mock.Mock() robj.content = u'' mock_get.return_value = robj mock_post.return_value = robj if test_requests_exceptions is False: # Handle our default response 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_get.return_value.text = requests_response_text mock_post.return_value.text = requests_response_text # Ensure there is no side effect set mock_post.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: # We can now instantiate our object: obj = Apprise.instantiate(url, asset=asset, suppress_exceptions=False) except Exception as e: # Handle our exception if instance is None: print('%s %s' % (url, str(e))) raise e if not isinstance(e, instance): print('%s %s' % (url, str(e))) raise e # We're okay if we get here return if obj is None: if instance is not None: # We're done (assuming this is what we were # expecting) print("{} didn't instantiate itself " "(we expected it to be a {})".format(url, instance)) assert False # We're done because we got the results we expected return if instance is None: # Expected None but didn't get it print('%s instantiated %s (but expected None)' % (url, str(obj))) assert False if not isinstance(obj, instance): print('%s instantiated %s (but expected %s)' % (url, type(instance), str(obj))) assert False if isinstance(obj, plugins.NotifyBase): # Ensure we are not performing any type of thorttling obj.request_rate_per_sec = 0 # We loaded okay; now lets make sure we can reverse # this url assert isinstance(obj.url(), six.string_types) is True # Test url() with privacy=True assert isinstance(obj.url(privacy=True), six.string_types) is True # Some Simple Invalid Instance Testing assert instance.parse_url(None) is None assert instance.parse_url(object) is None assert instance.parse_url(42) is None if privacy_url: # Assess that our privacy url is as expected if not obj.url(privacy=True).startswith(privacy_url): raise AssertionError( "Privacy URL: '{}' != expected '{}'".format( obj.url(privacy=True)[:len(privacy_url)], privacy_url)) if url_matches: # Assess that our URL matches a set regex assert re.search(url_matches, obj.url()) # Instantiate the exact same object again using the URL # from the one that was already created properly obj_cmp = Apprise.instantiate(obj.url()) # Our object should be the same instance as what we had # originally expected above. if not isinstance(obj_cmp, plugins.NotifyBase): # Assert messages are hard to trace back with the # way these tests work. Just printing before # throwing our assertion failure makes things # easier to debug later on print('TEST FAIL: {} regenerated as {}'.format(url, obj.url())) assert False # Tidy our object del obj_cmp if _self: # Iterate over our expected entries inside of our # object for key, val in self.items(): # Test that our object has the desired key assert hasattr(key, obj) is True assert getattr(key, obj) == val try: self.__notify(url, obj, meta, asset) except AssertionError: # Don't mess with these entries print('%s AssertionError' % url) raise # Tidy our object and allow any possible defined deconstructors to # be executed. del obj
def test_apprise_asset(tmpdir): """ API: AppriseAsset() object """ a = AppriseAsset(theme=None) # Default theme assert (a.theme == 'default') a = AppriseAsset( theme='dark', image_path_mask='/{THEME}/{TYPE}-{XY}{EXTENSION}', image_url_mask='http://localhost/{THEME}/{TYPE}-{XY}{EXTENSION}', ) a.default_html_color = '#abcabc' a.html_notify_map[NotifyType.INFO] = '#aaaaaa' assert (a.color('invalid', tuple) == (171, 202, 188)) assert (a.color(NotifyType.INFO, tuple) == (170, 170, 170)) assert (a.color('invalid', int) == 11258556) assert (a.color(NotifyType.INFO, int) == 11184810) assert (a.color('invalid', None) == '#abcabc') assert (a.color(NotifyType.INFO, None) == '#aaaaaa') # None is the default assert (a.color(NotifyType.INFO) == '#aaaaaa') # Invalid Type try: a.color(NotifyType.INFO, dict) # We should not get here (exception should be thrown) assert (False) except ValueError: # The exception we expect since dict is not supported assert (True) except Exception: # Any other exception is not good assert (False) assert (a.image_url( NotifyType.INFO, NotifyImageSize.XY_256) == 'http://localhost/dark/info-256x256.png') assert (a.image_path(NotifyType.INFO, NotifyImageSize.XY_256, must_exist=False) == '/dark/info-256x256.png') # This path doesn't exist so image_raw will fail (since we just # randompyl picked it for testing) assert (a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None) assert (a.image_path( NotifyType.INFO, NotifyImageSize.XY_256, must_exist=True) is None) # Create a new object (with our default settings) a = AppriseAsset() # Our default configuration can access our file assert (a.image_path(NotifyType.INFO, NotifyImageSize.XY_256, must_exist=True) is not None) assert (a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None) # Create a temporary directory sub = tmpdir.mkdir("great.theme") # Write a file sub.join("{0}-{1}.png".format( NotifyType.INFO, NotifyImageSize.XY_256, )).write("the content doesn't matter for testing.") # Create an asset that will reference our file we just created a = AppriseAsset( theme='great.theme', image_path_mask='%s/{THEME}/{TYPE}-{XY}.png' % dirname(sub.strpath), ) # We'll be able to read file we just created assert (a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None) # We can retrieve the filename at this point even with must_exist set # to True assert (a.image_path(NotifyType.INFO, 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.') chmod(dirname(sub.strpath), 0o000) 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) # Disable all image references a = AppriseAsset(image_path_mask=False, image_url_mask=False) # We always return none in these calls now assert (a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None) assert (a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) is None) assert (a.image_path( NotifyType.INFO, NotifyImageSize.XY_256, must_exist=False) is None) assert (a.image_path( NotifyType.INFO, NotifyImageSize.XY_256, must_exist=True) is None) # Test our default extension out a = AppriseAsset( image_path_mask='/{THEME}/{TYPE}-{XY}{EXTENSION}', image_url_mask='http://localhost/{THEME}/{TYPE}-{XY}{EXTENSION}', default_extension='.jpeg', ) assert (a.image_path(NotifyType.INFO, NotifyImageSize.XY_256, must_exist=False) == '/default/info-256x256.jpeg') assert (a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) == 'http://localhost/' 'default/info-256x256.jpeg') # extension support assert (a.image_path(NotifyType.INFO, NotifyImageSize.XY_128, must_exist=False, extension='.ico') == '/default/info-128x128.ico') assert (a.image_url(NotifyType.INFO, NotifyImageSize.XY_256, extension='.test') == 'http://localhost/' 'default/info-256x256.test')
# standard library import random import string from string import Template # 3rd party import gevent from apprise import Apprise from apprise import AppriseAsset # betanin import betanin.config.betanin as conf_betanin from betanin.status import Status _apprise_asset = AppriseAsset() _apprise_asset.app_id = "betanin" _apprise_asset.app_desc = "betanin" _apprise_asset.async_mode = False APPRISE = Apprise(asset=_apprise_asset) STATUS_LONG = { Status.COMPLETED: "has completed", Status.FAILED: "has failed", Status.NEEDS_INPUT: "needs input", } def _random_string(size=6, chars=string.ascii_lowercase + string.digits): return "".join(random.choice(chars) for x in range(size)) def _make_templates(config):