467 lines
17 KiB
Python
467 lines
17 KiB
Python
from __future__ import unicode_literals
|
|
|
|
import logging
|
|
import mock
|
|
import socket
|
|
|
|
from mopidy import exceptions
|
|
from mopidy.utils import config
|
|
|
|
from tests import unittest
|
|
|
|
|
|
class ValidateChoiceTest(unittest.TestCase):
|
|
def test_no_choices_passes(self):
|
|
config.validate_choice('foo', None)
|
|
|
|
def test_valid_value_passes(self):
|
|
config.validate_choice('foo', ['foo', 'bar', 'baz'])
|
|
config.validate_choice(1, [1, 2, 3])
|
|
|
|
def test_empty_choices_fails(self):
|
|
self.assertRaises(ValueError, config.validate_choice, 'foo', [])
|
|
|
|
def test_invalid_value_fails(self):
|
|
words = ['foo', 'bar', 'baz']
|
|
self.assertRaises(ValueError, config.validate_choice, 'foobar', words)
|
|
self.assertRaises(ValueError, config.validate_choice, 5, [1, 2, 3])
|
|
|
|
|
|
class ValidateMinimumTest(unittest.TestCase):
|
|
def test_no_minimum_passes(self):
|
|
config.validate_minimum(10, None)
|
|
|
|
def test_valid_value_passes(self):
|
|
config.validate_minimum(10, 5)
|
|
|
|
def test_to_small_value_fails(self):
|
|
self.assertRaises(ValueError, config.validate_minimum, 10, 20)
|
|
|
|
def test_to_small_value_fails_with_zero_as_minimum(self):
|
|
self.assertRaises(ValueError, config.validate_minimum, -1, 0)
|
|
|
|
|
|
class ValidateMaximumTest(unittest.TestCase):
|
|
def test_no_maximum_passes(self):
|
|
config.validate_maximum(5, None)
|
|
|
|
def test_valid_value_passes(self):
|
|
config.validate_maximum(5, 10)
|
|
|
|
def test_to_large_value_fails(self):
|
|
self.assertRaises(ValueError, config.validate_maximum, 10, 5)
|
|
|
|
def test_to_large_value_fails_with_zero_as_maximum(self):
|
|
self.assertRaises(ValueError, config.validate_maximum, 5, 0)
|
|
|
|
|
|
class ValidateRequiredTest(unittest.TestCase):
|
|
def test_passes_when_false(self):
|
|
config.validate_required('foo', False)
|
|
config.validate_required('', False)
|
|
config.validate_required(' ', False)
|
|
|
|
def test_passes_when_required_and_set(self):
|
|
config.validate_required('foo', True)
|
|
config.validate_required(' foo ', True)
|
|
|
|
def test_blocks_when_required_and_emtpy(self):
|
|
self.assertRaises(ValueError, config.validate_required, '', True)
|
|
self.assertRaises(ValueError, config.validate_required, ' ', True)
|
|
|
|
|
|
class ConfigValueTest(unittest.TestCase):
|
|
def test_init(self):
|
|
value = config.ConfigValue()
|
|
self.assertIsNone(value.choices)
|
|
self.assertIsNone(value.maximum)
|
|
self.assertIsNone(value.minimum)
|
|
self.assertIsNone(value.optional)
|
|
self.assertIsNone(value.secret)
|
|
|
|
def test_init_with_params(self):
|
|
kwargs = {'choices': ['foo'], 'minimum': 0, 'maximum': 10,
|
|
'secret': True, 'optional': True}
|
|
value = config.ConfigValue(**kwargs)
|
|
self.assertEqual(['foo'], value.choices)
|
|
self.assertEqual(0, value.minimum)
|
|
self.assertEqual(10, value.maximum)
|
|
self.assertEqual(True, value.optional)
|
|
self.assertEqual(True, value.secret)
|
|
|
|
def test_deserialize_passes_through(self):
|
|
value = config.ConfigValue()
|
|
obj = object()
|
|
self.assertEqual(obj, value.deserialize(obj))
|
|
|
|
def test_serialize_conversion_to_string(self):
|
|
value = config.ConfigValue()
|
|
self.assertIsInstance(value.serialize(object()), basestring)
|
|
|
|
def test_format_uses_serialize(self):
|
|
value = config.ConfigValue()
|
|
obj = object()
|
|
self.assertEqual(value.serialize(obj), value.format(obj))
|
|
|
|
def test_format_masks_secrets(self):
|
|
value = config.ConfigValue(secret=True)
|
|
self.assertEqual('********', value.format(object()))
|
|
|
|
|
|
class StringTest(unittest.TestCase):
|
|
def test_deserialize_conversion_success(self):
|
|
value = config.String()
|
|
self.assertEqual('foo', value.deserialize(' foo '))
|
|
|
|
def test_deserialize_enforces_choices(self):
|
|
value = config.String(choices=['foo', 'bar', 'baz'])
|
|
self.assertEqual('foo', value.deserialize('foo'))
|
|
self.assertRaises(ValueError, value.deserialize, 'foobar')
|
|
|
|
def test_deserialize_enforces_required(self):
|
|
value = config.String()
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
|
|
def test_deserialize_respects_optional(self):
|
|
value = config.String(optional=True)
|
|
self.assertIsNone(value.deserialize(''))
|
|
self.assertIsNone(value.deserialize(' '))
|
|
|
|
def test_serialize_string_escapes(self):
|
|
value = config.String()
|
|
self.assertEqual(r'\r\n\t', value.serialize('\r\n\t'))
|
|
|
|
def test_format_masks_secrets(self):
|
|
value = config.String(secret=True)
|
|
self.assertEqual('********', value.format('s3cret'))
|
|
|
|
|
|
class IntegerTest(unittest.TestCase):
|
|
def test_deserialize_conversion_success(self):
|
|
value = config.Integer()
|
|
self.assertEqual(123, value.deserialize('123'))
|
|
self.assertEqual(0, value.deserialize('0'))
|
|
self.assertEqual(-10, value.deserialize('-10'))
|
|
|
|
def test_deserialize_conversion_failure(self):
|
|
value = config.Integer()
|
|
self.assertRaises(ValueError, value.deserialize, 'asd')
|
|
self.assertRaises(ValueError, value.deserialize, '3.14')
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
|
|
def test_deserialize_enforces_choices(self):
|
|
value = config.Integer(choices=[1, 2, 3])
|
|
self.assertEqual(3, value.deserialize('3'))
|
|
self.assertRaises(ValueError, value.deserialize, '5')
|
|
|
|
def test_deserialize_enforces_minimum(self):
|
|
value = config.Integer(minimum=10)
|
|
self.assertEqual(15, value.deserialize('15'))
|
|
self.assertRaises(ValueError, value.deserialize, '5')
|
|
|
|
def test_deserialize_enforces_maximum(self):
|
|
value = config.Integer(maximum=10)
|
|
self.assertEqual(5, value.deserialize('5'))
|
|
self.assertRaises(ValueError, value.deserialize, '15')
|
|
|
|
def test_format_masks_secrets(self):
|
|
value = config.Integer(secret=True)
|
|
self.assertEqual('********', value.format('1337'))
|
|
|
|
|
|
class BooleanTest(unittest.TestCase):
|
|
def test_deserialize_conversion_success(self):
|
|
value = config.Boolean()
|
|
for true in ('1', 'yes', 'true', 'on'):
|
|
self.assertIs(value.deserialize(true), True)
|
|
self.assertIs(value.deserialize(true.upper()), True)
|
|
self.assertIs(value.deserialize(true.capitalize()), True)
|
|
for false in ('0', 'no', 'false', 'off'):
|
|
self.assertIs(value.deserialize(false), False)
|
|
self.assertIs(value.deserialize(false.upper()), False)
|
|
self.assertIs(value.deserialize(false.capitalize()), False)
|
|
|
|
def test_deserialize_conversion_failure(self):
|
|
value = config.Boolean()
|
|
self.assertRaises(ValueError, value.deserialize, 'nope')
|
|
self.assertRaises(ValueError, value.deserialize, 'sure')
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
|
|
def test_serialize(self):
|
|
value = config.Boolean()
|
|
self.assertEqual('true', value.serialize(True))
|
|
self.assertEqual('false', value.serialize(False))
|
|
|
|
def test_format_masks_secrets(self):
|
|
value = config.Boolean(secret=True)
|
|
self.assertEqual('********', value.format('true'))
|
|
|
|
|
|
class ListTest(unittest.TestCase):
|
|
def test_deserialize_conversion_success(self):
|
|
value = config.List()
|
|
|
|
expected = ('foo', 'bar', 'baz')
|
|
self.assertEqual(expected, value.deserialize('foo, bar ,baz '))
|
|
|
|
expected = ('foo,bar', 'bar', 'baz')
|
|
self.assertEqual(expected, value.deserialize(' foo,bar\nbar\nbaz'))
|
|
|
|
def test_deserialize_enforces_required(self):
|
|
value = config.List()
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
|
|
def test_deserialize_respects_optional(self):
|
|
value = config.List(optional=True)
|
|
self.assertEqual(tuple(), value.deserialize(''))
|
|
self.assertEqual(tuple(), value.deserialize(' '))
|
|
|
|
def test_serialize(self):
|
|
value = config.List()
|
|
result = value.serialize(('foo', 'bar', 'baz'))
|
|
self.assertRegexpMatches(result, r'foo\n\s*bar\n\s*baz')
|
|
|
|
|
|
class BooleanTest(unittest.TestCase):
|
|
levels = {'critical': logging.CRITICAL,
|
|
'error': logging.ERROR,
|
|
'warning': logging.WARNING,
|
|
'info': logging.INFO,
|
|
'debug': logging.DEBUG}
|
|
|
|
def test_deserialize_conversion_success(self):
|
|
value = config.LogLevel()
|
|
for name, level in self.levels.items():
|
|
self.assertEqual(level, value.deserialize(name))
|
|
self.assertEqual(level, value.deserialize(name.upper()))
|
|
self.assertEqual(level, value.deserialize(name.capitalize()))
|
|
|
|
def test_deserialize_conversion_failure(self):
|
|
value = config.LogLevel()
|
|
self.assertRaises(ValueError, value.deserialize, 'nope')
|
|
self.assertRaises(ValueError, value.deserialize, 'sure')
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
|
|
def test_serialize(self):
|
|
value = config.LogLevel()
|
|
for name, level in self.levels.items():
|
|
self.assertEqual(name, value.serialize(level))
|
|
self.assertIsNone(value.serialize(1337))
|
|
|
|
|
|
class HostnameTest(unittest.TestCase):
|
|
@mock.patch('socket.getaddrinfo')
|
|
def test_deserialize_conversion_success(self, getaddrinfo_mock):
|
|
value = config.Hostname()
|
|
value.deserialize('example.com')
|
|
getaddrinfo_mock.assert_called_once_with('example.com', None)
|
|
|
|
@mock.patch('socket.getaddrinfo')
|
|
def test_deserialize_conversion_failure(self, getaddrinfo_mock):
|
|
value = config.Hostname()
|
|
getaddrinfo_mock.side_effect = socket.error
|
|
self.assertRaises(ValueError, value.deserialize, 'example.com')
|
|
|
|
@mock.patch('socket.getaddrinfo')
|
|
def test_deserialize_enforces_required(self, getaddrinfo_mock):
|
|
value = config.Hostname()
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
self.assertEqual(0, getaddrinfo_mock.call_count)
|
|
|
|
@mock.patch('socket.getaddrinfo')
|
|
def test_deserialize_respects_optional(self, getaddrinfo_mock):
|
|
value = config.Hostname(optional=True)
|
|
self.assertIsNone(value.deserialize(''))
|
|
self.assertIsNone(value.deserialize(' '))
|
|
self.assertEqual(0, getaddrinfo_mock.call_count)
|
|
|
|
|
|
class PortTest(unittest.TestCase):
|
|
def test_valid_ports(self):
|
|
value = config.Port()
|
|
self.assertEqual(1, value.deserialize('1'))
|
|
self.assertEqual(80, value.deserialize('80'))
|
|
self.assertEqual(6600, value.deserialize('6600'))
|
|
self.assertEqual(65535, value.deserialize('65535'))
|
|
|
|
def test_invalid_ports(self):
|
|
value = config.Port()
|
|
self.assertRaises(ValueError, value.deserialize, '65536')
|
|
self.assertRaises(ValueError, value.deserialize, '100000')
|
|
self.assertRaises(ValueError, value.deserialize, '0')
|
|
self.assertRaises(ValueError, value.deserialize, '-1')
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
|
|
|
|
class ExpandedPathTest(unittest.TestCase):
|
|
def test_is_bytes(self):
|
|
self.assertIsInstance(config.ExpandedPath('/tmp'), bytes)
|
|
|
|
@mock.patch('mopidy.utils.path.expand_path')
|
|
def test_defaults_to_expanded(self, expand_path_mock):
|
|
expand_path_mock.return_value = 'expanded_path'
|
|
self.assertEqual('expanded_path', config.ExpandedPath('~'))
|
|
|
|
@mock.patch('mopidy.utils.path.expand_path')
|
|
def test_orginal_stores_unexpanded(self, expand_path_mock):
|
|
self.assertEqual('~', config.ExpandedPath('~').original)
|
|
|
|
|
|
class PathTest(unittest.TestCase):
|
|
def test_deserialize_conversion_success(self):
|
|
result = config.Path().deserialize('/foo')
|
|
self.assertEqual('/foo', result)
|
|
self.assertIsInstance(result, config.ExpandedPath)
|
|
self.assertIsInstance(result, bytes)
|
|
|
|
def test_deserialize_enforces_choices(self):
|
|
value = config.Path(choices=['/foo', '/bar', '/baz'])
|
|
self.assertEqual('/foo', value.deserialize('/foo'))
|
|
self.assertRaises(ValueError, value.deserialize, '/foobar')
|
|
|
|
def test_deserialize_enforces_required(self):
|
|
value = config.Path()
|
|
self.assertRaises(ValueError, value.deserialize, '')
|
|
self.assertRaises(ValueError, value.deserialize, ' ')
|
|
|
|
def test_deserialize_respects_optional(self):
|
|
value = config.Path(optional=True)
|
|
self.assertIsNone(value.deserialize(''))
|
|
self.assertIsNone(value.deserialize(' '))
|
|
|
|
@mock.patch('mopidy.utils.path.expand_path')
|
|
def test_serialize_uses_original(self, expand_path_mock):
|
|
expand_path_mock.return_value = 'expanded_path'
|
|
path = config.ExpandedPath('original_path')
|
|
value = config.Path()
|
|
self.assertEqual('expanded_path', path)
|
|
self.assertEqual('original_path', value.serialize(path))
|
|
|
|
def test_serialize_plain_string(self):
|
|
value = config.Path()
|
|
self.assertEqual('path', value.serialize('path'))
|
|
|
|
|
|
class ConfigSchemaTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.schema = config.ConfigSchema()
|
|
self.schema['foo'] = mock.Mock()
|
|
self.schema['bar'] = mock.Mock()
|
|
self.schema['baz'] = mock.Mock()
|
|
self.values = {'bar': '123', 'foo': '456', 'baz': '678'}
|
|
|
|
def test_format(self):
|
|
self.schema['foo'].format.return_value = 'qwe'
|
|
self.schema['bar'].format.return_value = 'asd'
|
|
self.schema['baz'].format.return_value = 'zxc'
|
|
|
|
expected = ['[qwerty]', 'foo = qwe', 'bar = asd', 'baz = zxc']
|
|
result = self.schema.format('qwerty', self.values)
|
|
self.assertEqual('\n'.join(expected), result)
|
|
|
|
def test_format_unkwown_value(self):
|
|
self.schema['foo'].format.return_value = 'qwe'
|
|
self.schema['bar'].format.return_value = 'asd'
|
|
self.schema['baz'].format.return_value = 'zxc'
|
|
self.values['unknown'] = 'rty'
|
|
|
|
result = self.schema.format('qwerty', self.values)
|
|
self.assertNotIn('unknown = rty', result)
|
|
|
|
def test_convert(self):
|
|
self.schema.convert(self.values.items())
|
|
|
|
def test_convert_with_missing_value(self):
|
|
del self.values['foo']
|
|
|
|
with self.assertRaises(exceptions.ConfigError) as cm:
|
|
self.schema.convert(self.values.items())
|
|
|
|
self.assertIn('not found', cm.exception['foo'])
|
|
|
|
def test_convert_with_extra_value(self):
|
|
self.values['extra'] = '123'
|
|
|
|
with self.assertRaises(exceptions.ConfigError) as cm:
|
|
self.schema.convert(self.values.items())
|
|
|
|
self.assertIn('unknown', cm.exception['extra'])
|
|
|
|
def test_convert_with_deserialization_error(self):
|
|
self.schema['foo'].deserialize.side_effect = ValueError('failure')
|
|
|
|
with self.assertRaises(exceptions.ConfigError) as cm:
|
|
self.schema.convert(self.values.items())
|
|
|
|
self.assertIn('failure', cm.exception['foo'])
|
|
|
|
def test_convert_with_multiple_deserialization_errors(self):
|
|
self.schema['foo'].deserialize.side_effect = ValueError('failure')
|
|
self.schema['bar'].deserialize.side_effect = ValueError('other')
|
|
|
|
with self.assertRaises(exceptions.ConfigError) as cm:
|
|
self.schema.convert(self.values.items())
|
|
|
|
self.assertIn('failure', cm.exception['foo'])
|
|
self.assertIn('other', cm.exception['bar'])
|
|
|
|
def test_convert_deserialization_unknown_and_missing_errors(self):
|
|
self.values['extra'] = '123'
|
|
self.schema['bar'].deserialize.side_effect = ValueError('failure')
|
|
del self.values['baz']
|
|
|
|
with self.assertRaises(exceptions.ConfigError) as cm:
|
|
self.schema.convert(self.values.items())
|
|
|
|
self.assertIn('unknown', cm.exception['extra'])
|
|
self.assertNotIn('foo', cm.exception)
|
|
self.assertIn('failure', cm.exception['bar'])
|
|
self.assertIn('not found', cm.exception['baz'])
|
|
|
|
|
|
class ExtensionConfigSchemaTest(unittest.TestCase):
|
|
def test_schema_includes_enabled(self):
|
|
schema = config.ExtensionConfigSchema()
|
|
self.assertIsInstance(schema['enabled'], config.Boolean)
|
|
|
|
|
|
class LogLevelConfigSchemaTest(unittest.TestCase):
|
|
def test_conversion(self):
|
|
schema = config.LogLevelConfigSchema()
|
|
result = schema.convert([('foo.bar', 'DEBUG'), ('baz', 'INFO')])
|
|
|
|
self.assertEqual(logging.DEBUG, result['foo.bar'])
|
|
self.assertEqual(logging.INFO, result['baz'])
|
|
|
|
def test_format(self):
|
|
schema = config.LogLevelConfigSchema()
|
|
expected = ['[levels]', 'baz = info', 'foo.bar = debug']
|
|
result = schema.format(
|
|
'levels', {'foo.bar': logging.DEBUG, 'baz': logging.INFO})
|
|
self.assertEqual('\n'.join(expected), result)
|
|
|
|
|
|
class DidYouMeanTest(unittest.TestCase):
|
|
def testSuggestoins(self):
|
|
choices = ('enabled', 'username', 'password', 'bitrate', 'timeout')
|
|
|
|
suggestion = config.did_you_mean('bitrate', choices)
|
|
self.assertEqual(suggestion, 'bitrate')
|
|
|
|
suggestion = config.did_you_mean('bitrote', choices)
|
|
self.assertEqual(suggestion, 'bitrate')
|
|
|
|
suggestion = config.did_you_mean('Bitrot', choices)
|
|
self.assertEqual(suggestion, 'bitrate')
|
|
|
|
suggestion = config.did_you_mean('BTROT', choices)
|
|
self.assertEqual(suggestion, 'bitrate')
|
|
|
|
suggestion = config.did_you_mean('btro', choices)
|
|
self.assertEqual(suggestion, None)
|