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', ])
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