diff --git a/headphones/config_test.py b/headphones/config_test.py new file mode 100644 index 00000000..e321d4af --- /dev/null +++ b/headphones/config_test.py @@ -0,0 +1,439 @@ +import mock +from mock import MagicMock +import headphones.config +import re +import unittestcompat +from unittestcompat import TestCase, TestArgs + +class ConfigApiTest(TestCase): + """ Common tests for headphones.Config + + Common tests for headphones.Config This test suite guarantees, that external + API of the Config class conforms all expectations of other modules. + """ + + def _setUpConfigMock(self, mock, sections): + # every constructor `xx = ConfigObj()` in headphones.config will return + # this mock: + self.config_mock = self.config_module_mock.return_value = mock + + if sections: + mock.__contains__.side_effect = sections.__contains__ + mock.__getitem__.side_effect = sections.__getitem__ + mock.__setitem__.side_effect = sections.__setitem__ + mock.items.side_effect = sections.items + + return mock + + def setUp(self): + # patch for low-level ConfigObj for entire test class + # result - each test_* method will get one additional + # argument during testing + self.config_module_mock_patcher = mock.patch('headphones.config.ConfigObj', name='ConfigObjModuleMock') + self.config_module_mock = self.config_module_mock_patcher.start() + + existing_sections = {'General': {}, 'Email': {}} + # every constructor `xx = ConfigObj()` in headphones.config will return + # this mock: + self._setUpConfigMock(MagicMock(), existing_sections) + + def tearDown(self): + self.config_module_mock_patcher.stop() + + def test_constructor(self): + """ Config : creating """ + + cf = headphones.config.Config('/tmp/notexist') + self.assertIsInstance(cf, headphones.config.Config) + + @TestArgs( + # this sections are explicitly added in the test body: + ('General', False), + ('Email', False), + + # this sections will not be created nor in the test, either in the + # Config module + ('some_new_section_never_defined', True), + ('another_new_section_never_defined', True), + ) + def test_check_section(self, section_name, expected_return): + """ Config : check_section """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + res = c.check_section(section_name) + res2 = c.check_section(section_name) + + # assertions: + self.assertEqual(res, expected_return) + self.assertFalse(res2) + + @TestArgs( + ('api_enabled', 0, int), + ('Api_Key', '', str), + ) + def test_check_setting(self, setting_name, expected_return, expected_instance): + """ Config: check_setting , basic cases """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + res = c.check_setting(setting_name) + res2 = c.check_setting(setting_name) + + # assertions: + self.assertIsInstance(res, expected_instance) + self.assertEqual(res, expected_return) + self.assertEqual(res, res2) + + @TestArgs( + (''), + ('This_IsNew_Name'), + ) + def test_check_setting_raise_on_unknown_settings(self, setting_name): + """ Config: check_setting should raise on unknown """ + path = '/tmp/notexist' + + exc_regex = re.compile(setting_name, re.IGNORECASE) + + # call methods + c = headphones.config.Config(path) + # assertions: + with self.assertRaisesRegexp(KeyError, exc_regex): + c.check_setting(setting_name) + pass + + @TestArgs( + (None) + ) + def test_check_setting_raise_on_none(self, setting_name): + """ Config: check_setting shoud raise on None name """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + # assertions: + with self.assertRaises(AttributeError): + c.check_setting(setting_name) + pass + + def test_write(self): + """ Config : write """ + path = '/tmp/notexist' + + # overload mocks, defined in setUp: + old_conf_mock = self._setUpConfigMock(MagicMock(), {'a': {}}) + + option_name_not_from_definitions = 'some_invalid_option_with_super_uniq1_name' + option_name_not_from_definitions_value = 1 + old_conf_mock['asdf'] = {option_name_not_from_definitions: option_name_not_from_definitions_value} + + # call methods + cf = headphones.config.Config(path) + + # overload mock-patching for NEW CONFIG + new_patcher = mock.patch('headphones.config.ConfigObj', name='NEW_ConfigObjModuleMock_FOR_WRITE') + + new_conf_module_mock = new_patcher.start() + new_conf_mock = \ + new_conf_module_mock.return_value = \ + MagicMock() + cf.write() + new_patcher.stop() + + # assertions: + self.assertFalse(old_conf_mock.write.called, 'write not called for old config') + self.assertTrue(new_conf_mock.write.called, 'write called for new config') + self.assertEqual(new_conf_mock.filename, path) + + new_conf_mock['General'].__setitem__.assert_any_call('download_dir', '') + # from 3.5... new_conf_mock['asdf'].__setitem__.assert_not_called('download_dir', '') + new_conf_mock['asdf'].__setitem__.assert_any_call(option_name_not_from_definitions, option_name_not_from_definitions_value) + + @unittestcompat.skip("process_kwargs should be removed") + def test_process_kwargs(self): + self.assertTrue(True) + + # =========================================================== + # GET ATTR + # =========================================================== + + @TestArgs( + ('ADD_ALBUM_ART', True), + ('ALBUM_ART_FORMAT', 'shmolder'), + ('API_ENABLED', 1), + ('API_KEY', 'Hello'), + ) + def test__getattr__ConfValues(self, name, value): + """ Config: __getattr__ with setting value explicit """ + path = '/tmp/notexist' + + self.config_mock["General"] = {name.lower(): value} + + # call methods + c = headphones.config.Config(path) + act = c.__getattr__(name) + + # assertions: + self.assertEqual(act, value) + + @TestArgs( + ('ADD_ALBUM_ART', 0), + ('ALBUM_ART_FORMAT', 'folder'), + ('API_ENABLED', 0), + ('API_KEY', ''), + ) + def test__getattr__ConfValuesDefault(self, name, value): + """ Config: __getattr__ from config(by braces), default values """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + res = c.__getattr__(name) + + # assertions: + self.assertEqual(res, value) + + def test__getattr__ConfValuesDefaultUsingDotNotation(self): + """ Config: __getattr__ from config (by dot), default values """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + + # assertions: + self.assertEqual(c.ALBUM_ART_FORMAT, 'folder') + self.assertEqual(c.API_ENABLED, 0) + self.assertEqual(c.API_KEY, '') + + def test__getattr__OwnAttributes(self): + """ Config: __getattr__ access own attrs """ + path = '/tmp/notexist' + + # call methods + c = headphones.config.Config(path) + + # assertions: + self.assertIsNotNone(c) + self.assertIn('