Skip to content

Commit

Permalink
[tests] Renaming; more unit tests.
Browse files Browse the repository at this point in the history
Signed-off-by: Xiangyu Bu <[email protected]>
  • Loading branch information
xybu committed Jan 28, 2017
1 parent 333dbcf commit 5388e5f
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
},
"webhook_port": {
"type": "integer",
"minimum": 1,
"maximum": 65536,
"minimum": 0,
"maximum": 65535,
"description": "@lang['config.webhook_port.desc']"
},
"start_delay_sec": {
Expand All @@ -30,6 +30,8 @@
"subtype": "file",
"to_abspath": true,
"create_if_missing": true,
"allow_empty": true,
"permission": "a",
"description": "@lang['config.logfile_path.desc']"
},
"webhook_type": {
Expand All @@ -39,6 +41,7 @@
},
"webhook_host": {
"type": "string",
"allow_empty": true,
"description": "@lang['config.webhook_host.desc']"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
from . import exceptions


class ConfigKeyTypes:
class DictEntryTypes:
INT = 'integer'
STR = 'string'


class ConfigStrTypes:
class StringSubTypes:
FILE = 'file'


def _test_bool_option(schema, optkey):
return optkey in schema and schema[optkey] is True
def _test_bool_option(schema, opt_key):
return opt_key in schema and schema[opt_key] is True


class GuardedConfigurator:
class GuardedDict:

def __init__(self, config_dict, config_schema_dict):
self.config_dict = config_dict
Expand All @@ -34,24 +34,30 @@ def set_int(self, key, value, schema):
self.config_dict[key] = value

def set_str_subtype(self, key, value, schema):
if schema['subtype'] == ConfigStrTypes.FILE:
if schema['subtype'] == StringSubTypes.FILE:
if _test_bool_option(schema, 'to_abspath'):
value = os.path.abspath(value)
if not os.path.exists(value):
if _test_bool_option(schema, 'create_if_missing'):
with open(value, 'w') as f:
f.write('')
with open(value, 'w'):
pass
else:
raise exceptions.PathDoesNotExist(key, value)
elif not os.path.isfile(value):
raise exceptions.PathIsNotFile(key, value)
elif 'permission' in schema:
with open(value, schema['permission']):
pass
else:
raise exceptions.UnsupportedSchemaType(schema['subtype'], schema)
self.config_dict[key] = value

def set_str(self, key, value, schema):
if not isinstance(value, str):
value = str(value)
if _test_bool_option(schema, 'allow_empty') and value == '':
self.config_dict[key] = ''
return
if 'starts_with' in schema and not value.startswith(schema['starts_with']):
raise exceptions.StringNotStartsWith(key, value, schema['starts_with'])
if 'choices' in schema and value not in schema['choices']:
Expand All @@ -60,13 +66,13 @@ def set_str(self, key, value, schema):
return self.set_str_subtype(key, value, schema)
self.config_dict[key] = value

def set(self, key, value):
def __setitem__(self, key, value):
if key not in self.config_schema_dict:
raise exceptions.ConfiguratorKeyError(key)
raise exceptions.DictGuardKeyError(key)
schema = self.config_schema_dict[key]
if schema['type'] == ConfigKeyTypes.INT:
if schema['type'] == DictEntryTypes.INT:
return self.set_int(key, value, schema)
elif schema['type'] == ConfigKeyTypes.STR:
elif schema['type'] == DictEntryTypes.STR:
return self.set_str(key, value, schema)
else:
raise exceptions.UnsupportedSchemaType(schema['type'], schema)
Expand All @@ -83,5 +89,5 @@ def validate(self):
raise TypeError('Schema for key "%s" must be of dict type.' % key)
if 'type' not in spec:
raise KeyError('Required field "type" is missing in key "%s".' % key)
elif spec['type'] not in (ConfigKeyTypes.INT, ConfigKeyTypes.STR):
elif spec['type'] not in (DictEntryTypes.INT, DictEntryTypes.STR):
raise ValueError('Value of field "type" in key "%s" is not supported.' % key)
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
class ConfiguratorError(Exception):
class DictGuardError(Exception):
def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)


class ConfiguratorSchemaError(ConfiguratorError):
class DictGuardSchemaError(DictGuardError):
def __init__(self, schema):
super().__init__()
self.bad_schema = schema


class UnsupportedSchemaType(ConfiguratorSchemaError):
class UnsupportedSchemaType(DictGuardSchemaError):
def __init__(self, schema_type, schema):
super().__init__(schema)
self.bad_schema_type = schema_type


class ConfiguratorKeyError(ConfiguratorError):
class DictGuardKeyError(DictGuardError):
def __init__(self, key):
self.key = key


class ConfiguratorValueError(ConfiguratorError):
class DictGuardValueError(DictGuardError):
def __init__(self, key, value):
super().__init__()
self.key = key
self.value = value


class IntValueRequired(ConfiguratorValueError):
class IntValueRequired(DictGuardValueError):
pass


class IntValueBelowMinimum(ConfiguratorValueError):
class IntValueBelowMinimum(DictGuardValueError):
def __init__(self, key, value, minimum):
super().__init__(key, value)
self.minimum = minimum


class IntValueAboveMaximum(ConfiguratorValueError):
class IntValueAboveMaximum(DictGuardValueError):
def __init__(self, key, value, maximum):
super().__init__(key, value)
self.maximum = maximum


class StringNotStartsWith(ConfiguratorValueError):
class StringNotStartsWith(DictGuardValueError):
def __init__(self, key, value, expected_starts_with):
super().__init__(key, value)
self.expected_starts_with = expected_starts_with


class StringInvalidChoice(ConfiguratorValueError):
class StringInvalidChoice(DictGuardValueError):
def __init__(self, key, value, choices_allowed):
super().__init__(key, value)
self.choices_allowed = choices_allowed


class PathDoesNotExist(ConfiguratorValueError):
class PathDoesNotExist(DictGuardValueError):
pass


class PathIsNotFile(ConfiguratorValueError):
class PathIsNotFile(DictGuardValueError):
pass
95 changes: 50 additions & 45 deletions onedrived/od_pref.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
from . import mkdir, get_resource, od_auth, od_i18n
from .od_models import pretty_api, drive_config
from .od_api_session import OneDriveAPISession, get_keyring_key
from .od_models.configurator import GuardedConfigurator, exceptions as configurator_exceptions
from .od_models.dict_guard import GuardedDict, exceptions as guard_errors
from .od_context import load_context, save_context


context = load_context()
translator = od_i18n.Translator(('od_pref', ), locale_str=str(locale.getlocale()[0]))
config_schema = json.loads(get_resource('data/configurator_schema.json', pkg_name='onedrived'))
config_guard = GuardedConfigurator(config_dict=context.config, config_schema_dict=config_schema)
config_schema = json.loads(get_resource('data/config_schema.json', pkg_name='onedrived'))
config_guard = GuardedDict(config_dict=context.config, config_schema_dict=config_schema)


def error(s):
Expand Down Expand Up @@ -244,6 +244,41 @@ def list_drives():
return


def read_drive_config_interactively(drive_exists, curr_drive_config):
local_root = None
ignore_file = None
if drive_exists:
local_root_default = curr_drive_config.localroot_path
ignore_file_default = curr_drive_config.ignorefile_path
else:
local_root_default = context.user_home + '/OneDrive'
ignore_file_default = context.config_dir + '/' + context.DEFAULT_IGNORE_FILENAME
while local_root is None:
local_root = click.prompt('Enter the directory path to sync with this Drive',
type=str, default=local_root_default)
local_root = os.path.abspath(local_root)
if not os.path.exists(local_root):
if click.confirm('Directory "%s" does not exist. Create it?' % local_root):
try:
mkdir(local_root, context.user_uid)
except OSError as e:
error('OSError: %s' % e)
local_root = None
elif not os.path.isdir(local_root):
error('Path "%s" should be a directory.' % local_root)
local_root = None
elif not click.confirm('Syncing with directory "%s"?' % local_root):
local_root = None
while ignore_file is None:
ignore_file = click.prompt('Enter the path to ignore file for this Drive',
type=str, default=ignore_file_default)
ignore_file = os.path.abspath(ignore_file)
if not os.path.isfile(ignore_file):
error('Path "%s" is not a file.' % ignore_file)
ignore_file = None
return local_root, ignore_file


@click.command(name='set', short_help='Add a remote Drive to sync with local directory or modify an existing one. '
'If either --drive-id or --email is missing, use interactive mode.')
@click.option('--drive-id', '-d', type=str, required=False, default=None,
Expand Down Expand Up @@ -302,37 +337,7 @@ def set_drive(drive_id=None, email=None, local_root=None, ignore_file=None):
'Going to add/edit Drive "%s" of account "%s"...' % (drive_id, account_profile.account_email), fg='cyan'))

if interactive:
local_root = None
ignore_file = None
if drive_exists:
local_root_default = curr_drive_config.localroot_path
ignore_file_default = curr_drive_config.ignorefile_path
else:
local_root_default = context.user_home + '/OneDrive'
ignore_file_default = context.config_dir + '/' + context.DEFAULT_IGNORE_FILENAME
while local_root is None:
local_root = click.prompt('Enter the directory path to sync with this Drive',
type=str, default=local_root_default)
local_root = os.path.abspath(local_root)
if not os.path.exists(local_root):
if click.confirm('Directory "%s" does not exist. Create it?' % local_root):
try:
mkdir(local_root, context.user_uid)
except OSError as e:
error('OSError: %s' % e)
local_root = None
elif not os.path.isdir(local_root):
error('Path "%s" should be a directory.' % local_root)
local_root = None
elif not click.confirm('Syncing with directory "%s"?' % local_root):
local_root = None
while ignore_file is None:
ignore_file = click.prompt('Enter the path to ignore file for this Drive',
type=str, default=ignore_file_default)
ignore_file = os.path.abspath(ignore_file)
if not os.path.isfile(ignore_file):
error('Path "%s" is not a file.' % ignore_file)
ignore_file = None
local_root, ignore_file = read_drive_config_interactively(drive_exists, curr_drive_config)
else:
# Non-interactive mode. The drive may or may not exist in config, and the cmdline args may or may not be
# specified. If drive exists in config, use existing values for missing args. If drive does not exist,
Expand All @@ -349,12 +354,12 @@ def set_drive(drive_id=None, email=None, local_root=None, ignore_file=None):
if ignore_file is None and drive_exists:
ignore_file = curr_drive_config.ignorefile_path
if ignore_file is None or not os.path.isfile(ignore_file):
click.echo(click.style('Warning: ignore file path does not point to a file. Use default.', fg='yellow'))
click.secho('Warning: ignore file path does not point to a file. Use default.', fg='yellow')
ignore_file = context.config_dir + '/' + context.DEFAULT_IGNORE_FILENAME
if (drive_exists and
local_root == curr_drive_config.localroot_path and
ignore_file == curr_drive_config.ignorefile_path):
click.echo(click.style('No parameter was changed. Skipped operation.', fg='yellow'))
click.secho('No parameter was changed. Skipped operation.', fg='yellow')
return
except ValueError as e:
error(str(e))
Expand Down Expand Up @@ -420,24 +425,24 @@ def print_config():
@click.argument('value')
def set_config(key, value):
try:
config_guard.set(key, value)
config_guard[key] = value
click.echo('config.%s = %s' % (key, str(context.config[key])))
except configurator_exceptions.ConfiguratorKeyError as e:
except guard_errors.DictGuardKeyError as e:
error(translator['configurator.error_invalid_key'].format(key=e.key))
except configurator_exceptions.IntValueRequired as e:
except guard_errors.IntValueRequired as e:
error(translator['configurator.error_int_value_required'].format(key=e.key, value=e.value))
except configurator_exceptions.IntValueBelowMinimum as e:
except guard_errors.IntValueBelowMinimum as e:
error(translator['configurator.error_int_below_minimum'].format(key=e.key, value=e.value, minimum=e.minimum))
except configurator_exceptions.IntValueAboveMaximum as e:
except guard_errors.IntValueAboveMaximum as e:
error(translator['configurator.error_int_below_minimum'].format(key=e.key, value=e.value, maximum=e.maximum))
except configurator_exceptions.StringInvalidChoice as e:
except guard_errors.StringInvalidChoice as e:
error(translator['configurator.error_str_invalid_choice'].format(
key=e.key, value=e.value, choices=', '.join(e.choices_allowed)))
except configurator_exceptions.StringNotStartsWith as e:
except guard_errors.StringNotStartsWith as e:
error(translator['configurator.error_str_not_startswith'].format(
key=e.key, value=e.value, starts_with=e.expected_starts_with))
except (configurator_exceptions.PathDoesNotExist, configurator_exceptions.PathIsNotFile) as e:
if isinstance(e, configurator_exceptions.PathIsNotFile):
except (guard_errors.PathDoesNotExist, guard_errors.PathIsNotFile) as e:
if isinstance(e, guard_errors.PathIsNotFile):
str_key = 'configurator.error_path_not_file'
else:
str_key = 'configurator.error_path_not_exist'
Expand Down
36 changes: 36 additions & 0 deletions tests/data/sample_config_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"webhook_port": {
"type": "integer",
"minimum": 1,
"maximum": 65536,
"@default_value": 8080,
"description": "@lang['config.webhook_port.desc']"
},
"logfile_path": {
"type": "string",
"subtype": "file",
"to_abspath": true,
"create_if_missing": true,
"allow_empty": true,
"permissions": "w",
"@default_value": "/var/log/onedrived.log",
"description": "@lang['config.logfile_path.desc']"
},
"webhook_type": {
"type": "string",
"choices": ["direct", "ngrok"],
"@default_value": "direct",
"description": "@lang['config.webhook_type.desc']"
},
"webhook_host": {
"type": "string",
"@default_value": "haha",
"description": "@lang['config.webhook_host.desc']"
},
"https_url": {
"type": "string",
"allow_empty": true,
"@default_value": "https://foo/bar",
"starts_with": "https://"
}
}
Loading

0 comments on commit 5388e5f

Please sign in to comment.