Example #1
0
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
Example #2
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
Example #3
0
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
Example #4
0
# 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 {
Example #5
0
    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 &lt;i&gt;' \
        'Apprise Test Title&lt;/i&gt;</b>\r\n&lt;' \
        'a href="http://localhost"&gt;&lt;i&gt;Apprise Body Title' \
        '&lt;/i&gt;&lt;/a&gt; had &lt;a href="http://127.0.0.1"' \
        '&gt;a change&lt;/a&gt;'

    # 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&nbsp;us\"\'</p>"
    assert obj.notify(body=body,
                      title='special characters',
                      notify_type=NotifyType.INFO) is True
    assert mock_post.call_count == 1
    payload = loads(mock_post.call_args_list[0][1]['data'])

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # Test Telegram Group
    obj = Apprise.instantiate(
        'tgram://123456789:ABCdefghijkl123456789opqyz/-123456789525')
    assert isinstance(obj, plugins.NotifyTelegram)
    assert len(obj.targets) == 1
    assert '-123456789525' in obj.targets
Example #8
0
    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
Example #9
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)

    # 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)
Example #10
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
Example #11
0
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
Example #12
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
Example #13
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)
Example #14
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)

    # 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)
Example #15
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)
Example #17
0
    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
Example #18
0
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')
Example #19
0
# 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):