diff options
Diffstat (limited to 'src/leap/common/config')
-rw-r--r-- | src/leap/common/config/baseconfig.py | 65 | ||||
-rw-r--r-- | src/leap/common/config/prefixers.py | 2 | ||||
-rw-r--r-- | src/leap/common/config/tests/test_baseconfig.py | 268 |
3 files changed, 326 insertions, 9 deletions
diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py index e6bd9c4..e310bc0 100644 --- a/src/leap/common/config/baseconfig.py +++ b/src/leap/common/config/baseconfig.py @@ -26,7 +26,7 @@ import os from abc import ABCMeta, abstractmethod -from leap.common.check import leap_assert +from leap.common.check import leap_assert, leap_check from leap.common.files import mkdir_p from leap.common.config.pluggableconfig import PluggableConfig from leap.common.config.prefixers import get_platform_prefixer @@ -34,6 +34,12 @@ from leap.common.config.prefixers import get_platform_prefixer logger = logging.getLogger(__name__) +class NonExistingSchema(Exception): + """ + Raised if the schema needed to verify the config is None. + """ + + class BaseConfig: """ Abstract base class for any JSON based configuration. @@ -55,13 +61,27 @@ class BaseConfig: def __init__(self): self._data = {} self._config_checker = None + self._api_version = None @abstractmethod + def _get_schema(self): + """ + Returns the schema corresponding to the version given. + + :rtype: dict or None if the version is not supported. + """ + pass + def _get_spec(self): """ Returns the spec object for the specific configuration. + + :rtype: dict or None if the version is not supported. """ - return None + leap_assert(self._api_version is not None, + "You should set the API version.") + + return self._get_schema() def _safe_get_value(self, key): """ @@ -73,6 +93,17 @@ class BaseConfig: leap_assert(self._config_checker, "Load the config first") return self._config_checker.config.get(key, None) + def set_api_version(self, version): + """ + Sets the supported api version. + + :param api_version: the version of the api supported by the provider. + :type api_version: str + """ + self._api_version = version + leap_assert(self._get_schema() is not None, + "Version %s is not supported." % (version, )) + def get_path_prefix(self): """ Returns the platform dependant path prefixer @@ -112,6 +143,7 @@ class BaseConfig: def load(self, path="", data=None, mtime=None, relative=True): """ Loads the configuration from disk. + It may raise NonExistingSchema exception. :param path: if relative=True, this is a relative path to configuration. The absolute path @@ -131,8 +163,12 @@ class BaseConfig: else: config_path = path + schema = self._get_spec() + leap_check(schema is not None, + "There is no schema to use.", NonExistingSchema) + self._config_checker = PluggableConfig(format="json") - self._config_checker.options = copy.deepcopy(self._get_spec()) + self._config_checker.options = copy.deepcopy(schema) try: if data is None: @@ -155,26 +191,39 @@ class LocalizedKey(object): def __init__(self, func, **kwargs): self._func = func - def __call__(self, instance, lang="en"): + def __call__(self, instance, lang=None): """ Tries to return the string for the specified language, otherwise - informs the problem and returns an empty string. + returns the default language string. :param lang: language code :type lang: str :return: localized value from the possible values returned by self._func + It returns None in case that the provider does not provides + a matching pair of default_language and string for + that language. + e.g.: + 'default_language': 'es', + 'description': {'en': 'test description'} + Note that the json schema can't check that. """ descriptions = self._func(instance) - description_lang = "" - config_lang = "en" + config_lang = instance.get_default_language() + if lang is None: + lang = config_lang + for key in descriptions.keys(): if lang.startswith(key): config_lang = key break - description_lang = descriptions[config_lang] + description_lang = descriptions.get(config_lang) + if description_lang is None: + logger.error("There is a misconfiguration in the " + "provider's language strings.") + return description_lang def __get__(self, instance, instancetype): diff --git a/src/leap/common/config/prefixers.py b/src/leap/common/config/prefixers.py index 050d4cd..9a5b043 100644 --- a/src/leap/common/config/prefixers.py +++ b/src/leap/common/config/prefixers.py @@ -95,7 +95,7 @@ class DarwinPrefixer(Prefixer): config_dir = BaseDirectory.xdg_config_home if not standalone: return config_dir - return os.getenv(os.getcwd(), "config") + return os.path.join(os.getcwd(), "config") class WindowsPrefixer(Prefixer): diff --git a/src/leap/common/config/tests/test_baseconfig.py b/src/leap/common/config/tests/test_baseconfig.py new file mode 100644 index 0000000..8a2915e --- /dev/null +++ b/src/leap/common/config/tests/test_baseconfig.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +# test_baseconfig.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Tests for baseconfig +""" +import json +import unittest +import copy + +from leap.common.config.baseconfig import BaseConfig, LocalizedKey +from leap.common.testing.basetest import BaseLeapTest + +from mock import Mock + +# reduced eipconfig sample config +sample_config = { + "gateways": [ + { + "capabilities": { + "adblock": False, + "transport": ["openvpn"], + "user_ips": False + }, + "host": "host.dev.example.org", + }, { + "capabilities": { + "adblock": False, + "transport": ["openvpn"], + "user_ips": False + }, + "host": "host2.dev.example.org", + } + ], + "default_language": "en", + "languages": [ + "en", + "es" + ], + "name": { + "en": "Baseconfig testing environment", + "es": "Entorno de pruebas de Baseconfig" + }, + "serial": 1, + "version": 1 +} + +# reduced eipconfig.spec version +sample_spec = { + 'description': 'sample eip service config', + 'type': 'object', + 'properties': { + 'serial': { + 'type': int, + 'default': 1, + 'required': ["True"] + }, + 'version': { + 'type': int, + 'default': 1, + 'required': ["True"] + }, + "default_language": { + 'type': unicode, + 'default': 'en' + }, + 'languages': { + 'type': list, + 'default': ['en'] + }, + 'name': { + 'type': dict, + 'format': 'translatable', + 'default': {u'en': u'Test Provider'} + }, + 'gateways': { + 'type': list, + 'default': [ + {"capabilities": { + "adblock": True, + "transport": ["openvpn"], + "user_ips": False}, + "host": "location.example.org", + }] + }, + } +} + + +class TestConfig(BaseConfig): + """ + BaseConfig implementation for testing purposes only. + """ + def get_gateways(self): + return self._safe_get_value("gateways") + + def get_serial(self): + return self._safe_get_value("serial") + + def get_version(self): + return self._safe_get_value("version") + + def _get_spec(self): + return sample_spec + + def get_default_language(self): + return self._safe_get_value("default_language") + + @LocalizedKey + def get_name(self): + return self._safe_get_value("name") + + +class BaseConfigTest(BaseLeapTest): + + def setUp(self): + pass + + def tearDown(self): + pass + + def _write_config(self, data): + """ + Helper to write some data to a temp config file. + + :param data: data to be used to save in the config file. + :data type: dict (valid json) + """ + self.config_file = self.get_tempfile("config.json") + conf = open(self.config_file, "w") + conf.write(json.dumps(data)) + conf.close() + + def _get_config(self, fromfile=False, data=sample_config): + """ + Helper that returns a TestConfig object using the data parameter + or a sample data. + + :param fromfile: sets if we should use a file or a string + :fromfile type: bool + :param data: sets the data to be used to load in the TestConfig object + :data type: dict (valid json) + :rtype: TestConfig + """ + config = TestConfig() + + loaded = False + if fromfile: + self._write_config(data) + loaded = config.load(self.config_file, relative=False) + else: + json_string = json.dumps(data) + loaded = config.load(data=json_string) + + if not loaded: + return None + + return config + + def test_loads_from_file(self): + config = self._get_config(fromfile=True) + self.assertIsNotNone(config) + + def test_loads_from_data(self): + config = self._get_config() + self.assertIsNotNone(config) + + def test_load_valid_config_from_file(self): + config = self._get_config(fromfile=True) + self.assertIsNotNone(config) + + self.assertEqual(config.get_version(), sample_config["version"]) + self.assertEqual(config.get_serial(), sample_config["serial"]) + self.assertEqual(config.get_gateways(), sample_config["gateways"]) + + def test_load_valid_config_from_data(self): + config = self._get_config() + self.assertIsNotNone(config) + + self.assertEqual(config.get_version(), sample_config["version"]) + self.assertEqual(config.get_serial(), sample_config["serial"]) + self.assertEqual(config.get_gateways(), sample_config["gateways"]) + + def test_safe_get_value_no_config(self): + config = TestConfig() + + with self.assertRaises(AssertionError): + config.get_version() + + def test_safe_get_value_non_existent_value(self): + config = self._get_config() + + self.assertIsNone(config._safe_get_value('non-existent-value')) + + def test_loaded(self): + config = self._get_config() + self.assertTrue(config.loaded()) + + def test_not_loaded(self): + config = TestConfig() + self.assertFalse(config.loaded()) + + def test_save_and_load(self): + config = self._get_config() + config.get_path_prefix = Mock(return_value=self.tempdir) + config_file = 'test_config.json' + self.assertTrue(config.save([config_file])) + + config_saved = TestConfig() + config_file_path = self.get_tempfile(config_file) + self.assertTrue(config_saved.load(config_file_path, relative=False)) + + self.assertEqual(config.get_version(), config_saved.get_version()) + self.assertEqual(config.get_serial(), config_saved.get_serial()) + self.assertEqual(config.get_gateways(), config_saved.get_gateways()) + + def test_localizations(self): + conf = self._get_config() + + self.assertEqual(conf.get_name(lang='en'), sample_config['name']['en']) + self.assertEqual(conf.get_name(lang='es'), sample_config['name']['es']) + + def _localized_config(self, lang): + """ + Helper to change default language of the provider config. + """ + conf = copy.deepcopy(sample_config) + conf['default_language'] = lang + json_string = json.dumps(conf) + config = TestConfig() + config.load(data=json_string) + + return config + + def test_default_localization1(self): + default_language = sample_config['languages'][0] + config = self._localized_config(default_language) + + default_name = sample_config['name'][default_language] + + self.assertEqual(config.get_name(lang='xx'), default_name) + self.assertEqual(config.get_name(), default_name) + + def test_default_localization2(self): + default_language = sample_config['languages'][1] + config = self._localized_config(default_language) + + default_name = sample_config['name'][default_language] + + self.assertEqual(config.get_name(lang='xx'), default_name) + self.assertEqual(config.get_name(), default_name) + + +if __name__ == "__main__": + unittest.main(verbosity=2) |