Esempio n. 1
0
def test_apprise_details():
    """
    API: Apprise() Details

    """

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

    # Details object
    details = a.details()

    # Dictionary response
    assert isinstance(details, dict)

    # Apprise version
    assert 'version' in details
    assert details.get('version') == __version__

    # Defined schemas identify each plugin
    assert 'schemas' in details
    assert isinstance(details.get('schemas'), list)

    # We have an entry per defined plugin
    assert 'asset' in details
    assert isinstance(details.get('asset'), dict)
    assert 'app_id' in details['asset']
    assert 'app_desc' in details['asset']
    assert 'default_extension' in details['asset']
    assert 'theme' in details['asset']
    assert 'image_path_mask' in details['asset']
    assert 'image_url_mask' in details['asset']
    assert 'image_url_logo' in details['asset']

    # All plugins must have a name defined; the below generates
    # a list of entrys that do not have a string defined.
    assert (not len([
        x['service_name'] for x in details['schemas']
        if not isinstance(x['service_name'], six.string_types)
    ]))
Esempio n. 2
0
def test_apprise_details_plugin_verification():
    """
    API: Apprise() Details Plugin Verification

    """

    # Reset our matrix
    __reset_matrix()
    __load_matrix()

    a = Apprise()

    # Details object
    details = a.details()

    # Dictionary response
    assert isinstance(details, dict)

    # Details object with language defined:
    details = a.details(lang='en')

    # Dictionary response
    assert isinstance(details, dict)

    # Details object with unsupported language:
    details = a.details(lang='xx')

    # Dictionary response
    assert isinstance(details, dict)

    # Apprise version
    assert 'version' in details
    assert details.get('version') == __version__

    # Defined schemas identify each plugin
    assert 'schemas' in details
    assert isinstance(details.get('schemas'), list)

    # We have an entry per defined plugin
    assert 'asset' in details
    assert isinstance(details.get('asset'), dict)
    assert 'app_id' in details['asset']
    assert 'app_desc' in details['asset']
    assert 'default_extension' in details['asset']
    assert 'theme' in details['asset']
    assert 'image_path_mask' in details['asset']
    assert 'image_url_mask' in details['asset']
    assert 'image_url_logo' in details['asset']

    # Valid Type Regular Expression Checker
    # Case Sensitive and MUST match the following:
    is_valid_type_re = re.compile(r'((choice|list):)?(string|bool|int|float)')

    # match tokens found in templates so we can cross reference them back
    # to see if they have a matching argument
    template_token_re = re.compile(r'{([^}]+)}[^{]*?(?=$|{)')

    # Define acceptable map_to arguments that can be tied in with the
    # kwargs function definitions.
    valid_kwargs = set([
        # URL prepared kwargs
        'user',
        'password',
        'port',
        'host',
        'schema',
        'fullpath',
        # URLBase and NotifyBase args:
        'verify',
        'format',
        'overflow',
    ])

    # Valid Schema Entries:
    valid_schema_keys = (
        'name',
        'private',
        'required',
        'type',
        'values',
        'min',
        'max',
        'regex',
        'default',
        'list',
        'delim',
        'prefix',
        'map_to',
        'alias_of',
    )
    for entry in details['schemas']:

        # Track the map_to entries (if specified); We need to make sure that
        # these properly map back
        map_to_entries = set()

        # Track the alias_of entries
        map_to_aliases = set()

        # A Service Name MUST be defined
        assert 'service_name' in entry
        assert isinstance(entry['service_name'], six.string_types)

        # Acquire our protocols
        protocols = parse_list(entry['protocols'], entry['secure_protocols'])

        # At least one schema/protocol MUST be defined
        assert len(protocols) > 0

        # our details
        assert 'details' in entry
        assert isinstance(entry['details'], dict)

        # All schema details should include args
        for section in ['kwargs', 'args', 'tokens']:
            assert section in entry['details']
            assert isinstance(entry['details'][section], dict)

            for key, arg in entry['details'][section].items():
                # Validate keys (case-sensitive)
                assert len(
                    [k for k in arg.keys() if k not in valid_schema_keys]) == 0

                # Test our argument
                assert isinstance(arg, dict)

                if 'alias_of' not in arg:
                    # Minimum requirement of an argument
                    assert 'name' in arg
                    assert isinstance(arg['name'], six.string_types)

                    assert 'type' in arg
                    assert isinstance(arg['type'], six.string_types)
                    assert is_valid_type_re.match(arg['type']) is not None

                    if 'min' in arg:
                        assert arg['type'].endswith('float') \
                            or arg['type'].endswith('int')
                        assert isinstance(arg['min'], (int, float))

                        if 'max' in arg:
                            # If a min and max was specified, at least check
                            # to confirm the min is less then the max
                            assert arg['min'] < arg['max']

                    if 'max' in arg:
                        assert arg['type'].endswith('float') \
                            or arg['type'].endswith('int')
                        assert isinstance(arg['max'], (int, float))

                    if 'private' in arg:
                        assert isinstance(arg['private'], bool)

                    if 'required' in arg:
                        assert isinstance(arg['required'], bool)

                    if 'prefix' in arg:
                        assert isinstance(arg['prefix'], six.string_types)
                        if section == 'kwargs':
                            # The only acceptable prefix types for kwargs
                            assert arg['prefix'] in ('+', '-')

                    else:
                        # kwargs requires that the 'prefix' is defined
                        assert section != 'kwargs'

                    if 'map_to' in arg:
                        # must be a string
                        assert isinstance(arg['map_to'], six.string_types)
                        # Track our map_to object
                        map_to_entries.add(arg['map_to'])

                    else:
                        map_to_entries.add(key)

                    # Some verification
                    if arg['type'].startswith('choice'):

                        # choice:bool is redundant and should be swapped to
                        # just bool
                        assert not arg['type'].endswith('bool')

                        # Choices require that a values list is provided
                        assert 'values' in arg
                        assert isinstance(arg['values'], (list, tuple))
                        assert len(arg['values']) > 0

                        # Test default
                        if 'default' in arg:
                            # if a default is provided on a choice object,
                            # it better be in the list of values
                            assert arg['default'] in arg['values']

                    if arg['type'].startswith('bool'):
                        # Boolean choices are less restrictive but require a
                        # default value
                        assert 'default' in arg
                        assert isinstance(arg['default'], bool)

                    if 'regex' in arg:
                        # Regex must ALWAYS be in the format (regex, option)
                        assert isinstance(arg['regex'], (tuple, list))
                        assert len(arg['regex']) == 2
                        assert isinstance(arg['regex'][0], six.string_types)
                        assert arg['regex'][1] is None or isinstance(
                            arg['regex'][1], six.string_types)

                        # Compile the regular expression to verify that it is
                        # valid
                        try:
                            re.compile(arg['regex'][0])
                        except:
                            assert '{} is an invalid regex'\
                                .format(arg['regex'][0])

                        # Regex should never start and/or end with ^/$; leave
                        # that up to the user making use of the regex instead
                        assert re.match(r'^[()\s]*\^', arg['regex'][0]) is None
                        assert re.match(r'[()\s$]*\$', arg['regex'][0]) is None

                    if arg['type'].startswith('list'):
                        # Delimiters MUST be defined
                        assert 'delim' in arg
                        assert isinstance(arg['delim'], (list, tuple))
                        assert len(arg['delim']) > 0

                else:  # alias_of is in the object
                    # must be a string
                    assert isinstance(arg['alias_of'], six.string_types)
                    # Track our alias_of object
                    map_to_aliases.add(arg['alias_of'])
                    # We should never map to ourselves
                    assert arg['alias_of'] != key
                    # 2 entries (name, and alias_of only!)
                    assert len(entry['details'][section][key]) == 1

        # inspect our object
        spec = inspect.getargspec(SCHEMA_MAP[protocols[0]].__init__)

        function_args = \
            (set(parse_list(spec.keywords)) - set(['kwargs'])) \
            | (set(spec.args) - set(['self'])) | valid_kwargs

        # Iterate over our map_to_entries and make sure that everything
        # maps to a function argument
        for arg in map_to_entries:
            if arg not in function_args:
                # This print statement just makes the error easier to
                # troubleshoot
                print(
                    '{}:// template/arg/func reference missing error.'.format(
                        protocols[0]))
            assert arg in function_args

        # Iterate over all of the function arguments and make sure that
        # it maps back to a key
        function_args -= valid_kwargs
        for arg in function_args:
            if arg not in map_to_entries:
                # This print statement just makes the error easier to
                # troubleshoot
                print(
                    '{}:// template/func/arg reference missing error.'.format(
                        protocols[0]))
            assert arg in map_to_entries

        # Iterate over our map_to_aliases and make sure they were defined in
        # either the as a token or arg
        for arg in map_to_aliases:
            assert arg in set(entry['details']['args'].keys()) \
                | set(entry['details']['tokens'].keys())

        # Template verification
        assert 'templates' in entry['details']
        assert isinstance(entry['details']['templates'], (set, tuple, list))

        # Iterate over our templates and parse our arguments
        for template in entry['details']['templates']:
            # Ensure we've properly opened and closed all of our tokens
            assert template.count('{') == template.count('}')

            expected_tokens = template.count('}')
            args = template_token_re.findall(template)
            assert expected_tokens == len(args)

            # Build a cross reference set of our current defined objects
            defined_tokens = set()
            for key, arg in entry['details']['tokens'].items():
                defined_tokens.add(key)
                if 'alias_of' in arg:
                    defined_tokens.add(arg['alias_of'])

            # We want to make sure all of our defined tokens have been
            # accounted for in at least one defined template
            for arg in args:
                assert arg in set(entry['details']['args'].keys()) \
                    | set(entry['details']['tokens'].keys())

                # The reverse of the above; make sure that each entry defined
                # in the template_tokens is accounted for in at least one of
                # the defined templates
                assert arg in defined_tokens
Esempio n. 3
0
def test_apprise_details():
    """
    API: Apprise() Details

    """
    # Reset our matrix
    __reset_matrix()

    # This is a made up class that is just used to verify
    class TestDetailNotification(NotifyBase):
        """
        This class is used to test various configurations supported
        """

        # Minimum requirements for a plugin to produce details
        service_name = 'Detail Testing'

        # The default simple (insecure) protocol (used by NotifyMail)
        protocol = 'details'

        # Set test_bool flag
        always_true = True
        always_false = False

        # Define object templates
        templates = (
            '{schema}://{host}',
            '{schema}://{host}:{port}',
            '{schema}://{user}@{host}:{port}',
            '{schema}://{user}:{pass}@{host}:{port}',
        )

        # Define our tokens; these are the minimum tokens required required to
        # be passed into this function (as arguments). The syntax appends any
        # previously defined in the base package and builds onto them
        template_tokens = dict(
            NotifyBase.template_tokens,
            **{
                'notype': {
                    # Nothing defined is still valid
                },
                'regex_test01': {
                    'name': _('RegexTest'),
                    'type': 'string',
                    'regex': r'[A-Z0-9]',
                },
                'regex_test02': {
                    'name': _('RegexTest'),
                    # Support regex options too
                    'regex': (r'[A-Z0-9]', 'i'),
                },
                'regex_test03': {
                    'name': _('RegexTest'),
                    # Support regex option without a second option
                    'regex': (r'[A-Z0-9]'),
                },
                'regex_test04': {
                    # this entry would just end up getting removed
                    'regex': None,
                },
                # List without delimiters (causes defaults to kick in)
                'mylistA': {
                    'name': 'fruit',
                    'type': 'list:string',
                },
                # A list with a delimiter list
                'mylistB': {
                    'name': 'softdrinks',
                    'type': 'list:string',
                    'delim': ['|', '-'],
                },
            })

        template_args = dict(
            NotifyBase.template_args,
            **{
                # Test _exist_if logic
                'test_exists_if_01': {
                    'name': 'Always False',
                    'type': 'bool',
                    # Provide a default
                    'default': False,
                    # Base the existance of this key/value entry on the lookup
                    # of this class value at runtime. Hence:
                    #     if not NotifyObject.always_false
                    #         del this_entry
                    #
                    '_exists_if': 'always_false',
                },
                # Test _exist_if logic
                'test_exists_if_02': {
                    'name': 'Always True',
                    'type': 'bool',
                    # Provide a default
                    'default': False,
                    # Base the existance of this key/value entry on the lookup
                    # of this class value at runtime. Hence:
                    #     if not NotifyObject.always_true
                    #         del this_entry
                    #
                    '_exists_if': 'always_true',
                },
            })

        def url(self):
            # Support URL
            return ''

        def notify(self, **kwargs):
            # Pretend everything is okay (so we don't break other tests)
            return True

    # Store our good detail notification in our schema map
    SCHEMA_MAP['details'] = TestDetailNotification

    # Create our Apprise instance
    a = Apprise()

    # Dictionary response
    assert isinstance(a.details(), dict)

    # Reset our matrix
    __reset_matrix()
    __load_matrix()