summaryrefslogtreecommitdiff
path: root/src/leap/common/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/common/config')
-rw-r--r--src/leap/common/config/baseconfig.py65
-rw-r--r--src/leap/common/config/prefixers.py2
-rw-r--r--src/leap/common/config/tests/test_baseconfig.py268
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)