Exemple #1
0
def test_parse_list():
    """utils: parse_list() testing """

    # A simple single array entry (As str)
    results = utils.parse_list(
        '.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg,.mpeg,.vob,.iso')

    assert results == sorted([
        '.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
        '.xvid', '.wmv', '.mp4',
    ])

    class StrangeObject(object):
        def __str__(self):
            return '.avi'

    # Now 2 lists with lots of duplicates and other delimiters
    results = utils.parse_list(
        '.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg .mpeg,.vob,,; ;',
        ('.mkv,.avi,.divx,.xvid,.mov    ', '    .wmv,.mp4;.mpg,.mpeg,'),
        '.vob,.iso', ['.vob', ['.vob', '.mkv', StrangeObject(), ], ],
        StrangeObject())

    assert results == sorted([
        '.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
        '.xvid', '.wmv', '.mp4',
    ])

    # Garbage in is removed
    assert utils.parse_list(object(), 42, None) == []

    # Now a list with extras we want to add as strings
    # empty entries are removed
    results = utils.parse_list([
        '.divx', '.iso', '.mkv', '.mov', '', '  ', '.avi', '.mpeg', '.vob',
        '.xvid', '.mp4'], '.mov,.wmv,.mp4,.mpg')

    assert results == sorted([
        '.divx', '.wmv', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.vob',
        '.xvid', '.mpeg', '.mp4',
    ])
Exemple #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