From b71c33ab38a96af7ff38d06f5362d31129b0f172 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 15 May 2023 12:28:10 +0530 Subject: [PATCH 1/6] feat: add create_or_update_tenant_config management command --- .../create_or_update_tenant_config.py | 102 +++++++++++++ .../test_create_or_udpate_tenant_config.py | 140 ++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 eox_tenant/management/commands/create_or_update_tenant_config.py create mode 100644 eox_tenant/test/test_create_or_udpate_tenant_config.py diff --git a/eox_tenant/management/commands/create_or_update_tenant_config.py b/eox_tenant/management/commands/create_or_update_tenant_config.py new file mode 100644 index 00000000..abe44264 --- /dev/null +++ b/eox_tenant/management/commands/create_or_update_tenant_config.py @@ -0,0 +1,102 @@ +""" +Create or updates the TenantConfig for given routes. +""" + +import codecs +import json +import logging + +from django.core.management import BaseCommand +from jsonfield.fields import JSONField + +from eox_tenant.models import Route, TenantConfig + +LOG = logging.getLogger(__name__) + + +def load_json_from_file(filename): + """ + Loads json content from file. + """ + with codecs.open(filename, encoding='utf-8') as file: + return json.load(file) + + +class Command(BaseCommand): + """ + Management command to create or update TenantConfig. + """ + help = 'Create or update TenantConfig' + + def add_arguments(self, parser): + parser.add_argument( + '--external-key', + action='store', + dest='external_key', + required=True, + type=str, + help='External key of the tenant config' + ) + parser.add_argument('routes', nargs='+', help='Routes to link to this tenant config') + + parser.add_argument( + '--config', + type=json.loads, + help="Enter JSON tenant configurations", + required=False + ) + parser.add_argument( + '-f', + '--config-file', + type=load_json_from_file, + dest='config_file_data', + help="Enter the path to the JSON file containing the tenant configuration", + required=False + ) + + def handle(self, *args, **options): + external_key = options['external_key'] + routes = options['routes'] + configuration = options.get('config') + config_file_data = options.get('config_file_data') + tenant_configuration_values = configuration or config_file_data + # pylint: disable=no-member,protected-access + external_key_length = TenantConfig._meta.get_field("external_key").max_length + if external_key: + if len(str(external_key)) > external_key_length: + LOG.warning( + "The external_key %s is too long, truncating to %s" + " characters. Please update external_key in admin.", + external_key, + external_key_length + ) + # trim name as the column has a limit of 63 characters + external_key = external_key[:external_key_length] + tenant, created = TenantConfig.objects.get_or_create( + external_key=external_key, + ) + if created: + LOG.info("Tenant does not exist. Created new tenant: '%s'", tenant.external_key) + else: + LOG.info("Found existing tenant for: '%s'", tenant.external_key) + + # split out lms, studio, theme, meta from configuration json + if tenant_configuration_values: + for field in TenantConfig._meta.get_fields(): + if isinstance(field, JSONField): + name = field.name + value = tenant_configuration_values.get(name) + if value is not None: + setattr(tenant, name, value) + + tenant.save() + # next add routes and link them + for route in routes: + route, created = Route.objects.update_or_create( + domain=route, + defaults={"config": tenant} + ) + if created: + LOG.info("Route does not exist. Created new route: '%s'", route.domain) + else: + LOG.info("Found existing route for: '%s'", route.domain) diff --git a/eox_tenant/test/test_create_or_udpate_tenant_config.py b/eox_tenant/test/test_create_or_udpate_tenant_config.py new file mode 100644 index 00000000..f6b891ca --- /dev/null +++ b/eox_tenant/test/test_create_or_udpate_tenant_config.py @@ -0,0 +1,140 @@ +"""This module include a class that checks the command create_or_update_tenant_config.py""" +import json +import tempfile + +from django.core.management import CommandError, call_command +from django.test import TestCase + +from eox_tenant.models import Route, TenantConfig + + +class CreateOrUpdateTenantConfigTestCase(TestCase): + """ This class checks the command create_or_update_tenant_config.py""" + + def setUp(self): + """This method creates TenantConfig objects in database""" + self.test_conf = { + "lms_configs": {"KEY": "value", "NESTED_KEY": {"key": "value"}}, + "studio_configs": {"STUDIO_KEY": "value", "STUDIO_NESTED_KEY": {"key": "value"}, } + } + self.external_key = "test" + TenantConfig.objects.create( + external_key=self.external_key, + lms_configs={ + "KEY": "value", + }, + ) + + def test_command_happy_path_with_cmd_config(self): + """Tests that command runs successfully if config is passed via cmd""" + self.assertFalse(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + call_command( + "create_or_update_tenant_config", + "--external-key", + "new.key", + "--config", + json.dumps(self.test_conf), + "test.domain", + "studio.test.domain" + ) + created_config = TenantConfig.objects.get(external_key="new.key") + self.assertTrue(created_config.lms_configs == self.test_conf["lms_configs"]) + self.assertTrue(created_config.studio_configs == self.test_conf["studio_configs"]) + created_routes = Route.objects.filter(domain__contains="test.domain").count() + self.assertTrue(created_routes == 2) + + def test_command_happy_path_with_file_config(self): + """Tests that command runs successfully if config is passed via file""" + self.assertFalse(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + with tempfile.NamedTemporaryFile('w') as fp: + fp.write(json.dumps(self.test_conf)) + fp.seek(0) + call_command( + "create_or_update_tenant_config", + "--external-key", + "new.key", + "--config-file", + fp.name, + "test.domain", + "studio.test.domain" + ) + created_config = TenantConfig.objects.get(external_key="new.key") + self.assertTrue(created_config.lms_configs == self.test_conf["lms_configs"]) + created_routes = Route.objects.filter(domain__contains="test.domain").count() + self.assertTrue(created_routes == 2) + + def test_command_invalid_config(self): + """Tests that command raises if config is invalid""" + self.assertFalse(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + with self.assertRaises(CommandError): + call_command( + "create_or_update_tenant_config", + "--external-key", + "new.key", + "--config", + '{"KEY": "value, "NESTED_KEY": {"key": "value"}}', + "test.domain", + "studio.test.domain" + ) + self.assertFalse(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + + def test_command_with_no_config(self): + """ + Tests that command works even if config is not passed, + i.e. it adds/updates an entry with external_key and links given routes. + """ + self.assertFalse(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + call_command( + "create_or_update_tenant_config", + "--external-key", + "new.key", + "test.domain", + "studio.test.domain" + ) + self.assertTrue(TenantConfig.objects.filter(external_key="new.key").exists()) + self.assertTrue(Route.objects.filter(domain__contains="test.domain").exists()) + + def test_command_with_long_external_key(self): + """Tests that command runs successfully even if external key is longer than limit.""" + long_external_key = "areallyreallyreallyreallyreallyreallylongexternalkey" + self.assertFalse( + TenantConfig.objects.filter(external_key__in=[long_external_key, long_external_key[:63]]).exists() + ) + self.assertFalse(Route.objects.filter(domain__contains="test.domain").exists()) + call_command( + "create_or_update_tenant_config", + "--external-key", + long_external_key, + "--config", + json.dumps(self.test_conf), + "test.domain", + "studio.test.domain" + ) + created_config = TenantConfig.objects.get(external_key=long_external_key[:63]) + self.assertTrue(created_config.lms_configs == self.test_conf["lms_configs"]) + created_routes = Route.objects.filter(domain__contains="test.domain").count() + self.assertTrue(created_routes == 2) + + def test_command_update_existing_tenant(self): + """Tests that command successfully updates existing TenantConfig.""" + config = TenantConfig.objects.get(external_key=self.external_key) + self.assertTrue(config.studio_configs == {}) + call_command( + "create_or_update_tenant_config", + "--external-key", + self.external_key, + "--config", + json.dumps(self.test_conf), + "test.domain", + "studio.test.domain" + ) + updated_config = TenantConfig.objects.get(external_key=self.external_key) + self.assertTrue(updated_config.lms_configs == self.test_conf["lms_configs"]) + self.assertTrue(updated_config.studio_configs == self.test_conf["studio_configs"]) + created_routes = Route.objects.filter(domain__contains="test.domain").count() + self.assertTrue(created_routes == 2) From 867571ac00a4868bad5e64d0a0938e59cd6ec797 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 16 May 2023 11:07:26 +0530 Subject: [PATCH 2/6] refactor: rename cmd edit_tenant_values to edit_microsite_values --- ...ant_values.py => edit_microsite_values.py} | 0 ...alues.py => test_edit_microsite_values.py} | 30 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename eox_tenant/management/commands/{edit_tenant_values.py => edit_microsite_values.py} (100%) rename eox_tenant/test/{test_edit_tenant_values.py => test_edit_microsite_values.py} (67%) diff --git a/eox_tenant/management/commands/edit_tenant_values.py b/eox_tenant/management/commands/edit_microsite_values.py similarity index 100% rename from eox_tenant/management/commands/edit_tenant_values.py rename to eox_tenant/management/commands/edit_microsite_values.py diff --git a/eox_tenant/test/test_edit_tenant_values.py b/eox_tenant/test/test_edit_microsite_values.py similarity index 67% rename from eox_tenant/test/test_edit_tenant_values.py rename to eox_tenant/test/test_edit_microsite_values.py index 56e8701f..ae3c9a47 100644 --- a/eox_tenant/test/test_edit_tenant_values.py +++ b/eox_tenant/test/test_edit_microsite_values.py @@ -8,7 +8,7 @@ class EditTenantValuesTestCase(TestCase): - """ This class checks the command edit_tenant_values.py""" + """ This class checks the command edit_microsite_values.py""" def setUp(self): """This method creates Microsite objects in database""" @@ -20,49 +20,49 @@ def setUp(self): "NESTED_KEY": {"key": "value"}, }) - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_can_be_called(self, _): """Tests that we can actually run the command""" - call_command('edit_tenant_values') + call_command('edit_microsite_values') - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='n') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='n') def test_command_exec_confirmation_false(self, _): """Tests that when the confirmation returns other than 'y' we raise the abort error""" with self.assertRaises(CommandError): - call_command('edit_tenant_values') + call_command('edit_microsite_values') - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_exec_confirmation_add(self, _): """Tests that we can add a new key""" - call_command('edit_tenant_values', '--add', 'NEW_KEY', 'NEW_VALUE') + call_command('edit_microsite_values', '--add', 'NEW_KEY', 'NEW_VALUE') tenant = Microsite.objects.get(key='test') self.assertIn('NEW_KEY', tenant.values) self.assertEqual('NEW_VALUE', tenant.values.get('NEW_KEY')) - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_exec_confirmation_add_nested(self, _): """Tests that we can add a new nested key""" - call_command('edit_tenant_values', '--add', 'NEW_KEY.nested', 'NEW_VALUE') + call_command('edit_microsite_values', '--add', 'NEW_KEY.nested', 'NEW_VALUE') tenant = Microsite.objects.get(key='test') self.assertIn('nested', tenant.values.get('NEW_KEY')) self.assertEqual('NEW_VALUE', tenant.values.get('NEW_KEY').get('nested')) - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_exec_confirmation_delete(self, _): """Tests that we can remove a key""" - call_command('edit_tenant_values', '--delete', 'KEY') + call_command('edit_microsite_values', '--delete', 'KEY') tenant = Microsite.objects.get(key='test') self.assertNotIn('KEY', tenant.values) - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_exec_confirmation_delete_chain(self, _): """Tests that we can remove a nested key""" - call_command('edit_tenant_values', '--delete', 'NESTED_KEY.key') + call_command('edit_microsite_values', '--delete', 'NESTED_KEY.key') tenant = Microsite.objects.get(key='test') self.assertIn('NESTED_KEY', tenant.values) self.assertNotIn('key', tenant.values.get('NESTED_KEY')) - @patch('eox_tenant.management.commands.edit_tenant_values.input', return_value='y') + @patch('eox_tenant.management.commands.edit_microsite_values.input', return_value='y') def test_command_exec_confirmation_pattern(self, _): """Tests that we can affect only the sites defined by a pattern in their subdomain""" Microsite.objects.create( @@ -73,7 +73,7 @@ def test_command_exec_confirmation_pattern(self, _): "NESTED_KEY": {"key": "value"}, }) - call_command('edit_tenant_values', '--delete', 'KEY', '--pattern', 'second.test.prod.edunext.co') + call_command('edit_microsite_values', '--delete', 'KEY', '--pattern', 'second.test.prod.edunext.co') tenant1 = Microsite.objects.get(key='test') tenant2 = Microsite.objects.get(key='test2') From f2a22e8756a09e84f5c78988a243d38ef48f2a65 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 16 May 2023 11:18:40 +0530 Subject: [PATCH 3/6] docs: update readme to add example of create_or_update_tenant_config command --- README.rst | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 823fe2ab..482cd5ad 100644 --- a/README.rst +++ b/README.rst @@ -130,7 +130,7 @@ Commands Synchronize Organizations ************************* -This comand will synchronize the course_org_filter values in lms_configs(TenantConfig model) or values(Microsite model) with the TenantOrganization registers, if the organization does not existe, it will be create, otherwise it will be add to the organizations model field. +This command will synchronize the course_org_filter values in lms_configs(TenantConfig model) or values(Microsite model) with the TenantOrganization registers, if the organization does not exist, it will be created, otherwise it will be add to the organizations model field. .. code-block:: bash @@ -139,6 +139,21 @@ This comand will synchronize the course_org_filter values in lms_configs(TenantC ./manage.py lms synchronize_organizations --model TenantConfig # only for TenantConfig ./manage.py lms synchronize_organizations --model Microsite # only for Microsite +Create/Edit tenant configuration +******************************** +`create_or_update_tenant_config` helps to add or edit ``TenantConfig`` and linked ``Routes`` via command line. + +.. code-block:: bash + + # this command will create/edit entry in TenantConfig with external_key lacolhost.com and update its JSONField(s) with passed json content. + ./manage.py lms create_or_update_tenant_config --external-key lacolhost.com --config '{"lms_configs": {"PLATFORM_NAME": "Lacolhost"}, "studio_configs": {"PLATFORM_NAME": "Lacolhost"}}' lacolhost.com studio.lacolhost.com preview.lacolhost.com + + # this command will create/edit entry in TenantConfig with external_key lacolhost.com and update its JSONField(s) with passed json config file content. + ./manage.py lms create_or_update_tenant_config --external-key lacolhost.com --config-file /tmp/some.json lacolhost.com studio.lacolhost.com preview.lacolhost.com + + # Same as above, but it will override configuration instead of updating it. + ./manage.py lms create_or_update_tenant_config --external-key lacolhost.com --config-file /tmp/some.json lacolhost.com studio.lacolhost.com preview.lacolhost.com --override + Caveats ------- From 6ba887fffcb8069da332ab8f1a42af3f804260c3 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 19 May 2023 11:40:46 +0530 Subject: [PATCH 4/6] refactor: update configs instead of overwriting --- .../create_or_update_tenant_config.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/eox_tenant/management/commands/create_or_update_tenant_config.py b/eox_tenant/management/commands/create_or_update_tenant_config.py index abe44264..81dfd6b3 100644 --- a/eox_tenant/management/commands/create_or_update_tenant_config.py +++ b/eox_tenant/management/commands/create_or_update_tenant_config.py @@ -8,6 +8,7 @@ from django.core.management import BaseCommand from jsonfield.fields import JSONField +import six from eox_tenant.models import Route, TenantConfig @@ -54,7 +55,24 @@ def add_arguments(self, parser): required=False ) + def merge_dict(self, base_dict: dict, override: dict): + """ + Merge two nested dicts. + """ + for key, value in override.items(): + if isinstance(value, dict): + merged = base_dict.get(key, {}).copy() + merged.update(value) + base_dict[key] = merged + continue + base_dict[key] = value + return base_dict + + def handle(self, *args, **options): + """ + Create or update TenantConfig and link related routes. + """ external_key = options['external_key'] routes = options['routes'] configuration = options.get('config') @@ -86,8 +104,10 @@ def handle(self, *args, **options): if isinstance(field, JSONField): name = field.name value = tenant_configuration_values.get(name) - if value is not None: - setattr(tenant, name, value) + if value: + base_value = getattr(tenant, name, {}) + merged = self.merge_dict(base_value, value) + setattr(tenant, name, merged) tenant.save() # next add routes and link them From 99de8bc049cd2c60f1af1975a3cfba06ecf80d0e Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 26 May 2023 15:14:28 +0530 Subject: [PATCH 5/6] refactor: add override argument and update merge logic --- .../create_or_update_tenant_config.py | 54 +++++++++++++------ .../commands/edit_microsite_values.py | 7 +-- .../test_create_or_udpate_tenant_config.py | 27 +++++++++- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/eox_tenant/management/commands/create_or_update_tenant_config.py b/eox_tenant/management/commands/create_or_update_tenant_config.py index 81dfd6b3..a346228e 100644 --- a/eox_tenant/management/commands/create_or_update_tenant_config.py +++ b/eox_tenant/management/commands/create_or_update_tenant_config.py @@ -8,7 +8,6 @@ from django.core.management import BaseCommand from jsonfield.fields import JSONField -import six from eox_tenant.models import Route, TenantConfig @@ -26,13 +25,31 @@ def load_json_from_file(filename): class Command(BaseCommand): """ Management command to create or update TenantConfig. + + Examples: + # create/update tenant config and link 2 routes + - python manage.py create_or_update_tenant_config --external-key lacolhost.com \ + --config '{"lms_configs": {"PLATFORM_NAME": "Lacolhost", "CONTACT_EMAIL": "edx@example.com"}}' \ + lacolhost.com preview.lacolhost.com + + # Override existing lms_configs under a tenant, for example, below command will overwrite `lms_configs` + # with given dictionary instead of updating it. + - python manage.py create_or_update_tenant_config --external-key lacolhost.com \ + --config '{"lms_configs": {"PLATFORM_NAME": "New name"}}' lacolhost.com preview.lacolhost.com + + # create/update tenant config using json file + - python manage.py create_or_update_tenant_config --external-key lacolhost.com \ + --config-file /tmp/lms.json lacolhost.com preview.lacolhost.com + + # Link studio.lacolhost.com route to existing/empty tenant config with given external key + - python manage.py create_or_update_tenant_config --external-key lacolhost.com studio.lacolhost.com + """ help = 'Create or update TenantConfig' def add_arguments(self, parser): parser.add_argument( '--external-key', - action='store', dest='external_key', required=True, type=str, @@ -54,20 +71,23 @@ def add_arguments(self, parser): help="Enter the path to the JSON file containing the tenant configuration", required=False ) + parser.add_argument( + '--override', + dest='override', + action='store_true', + required=False + ) - def merge_dict(self, base_dict: dict, override: dict): + def merge_dict(self, base_dict, override): """ Merge two nested dicts. """ - for key, value in override.items(): - if isinstance(value, dict): - merged = base_dict.get(key, {}).copy() - merged.update(value) - base_dict[key] = merged - continue - base_dict[key] = value - return base_dict + if isinstance(base_dict, dict) and isinstance(override, dict): + for key, value in override.items(): + base_dict[key] = self.merge_dict(base_dict.get(key, {}), value) + return base_dict + return override def handle(self, *args, **options): """ @@ -78,6 +98,7 @@ def handle(self, *args, **options): configuration = options.get('config') config_file_data = options.get('config_file_data') tenant_configuration_values = configuration or config_file_data + override = options.get('override') # pylint: disable=no-member,protected-access external_key_length = TenantConfig._meta.get_field("external_key").max_length if external_key: @@ -104,10 +125,13 @@ def handle(self, *args, **options): if isinstance(field, JSONField): name = field.name value = tenant_configuration_values.get(name) - if value: - base_value = getattr(tenant, name, {}) - merged = self.merge_dict(base_value, value) - setattr(tenant, name, merged) + if value is not None: + if override: + setattr(tenant, name, value) + else: + base_value = getattr(tenant, name, {}) + merged = self.merge_dict(base_value, value) + setattr(tenant, name, merged) tenant.save() # next add routes and link them diff --git a/eox_tenant/management/commands/edit_microsite_values.py b/eox_tenant/management/commands/edit_microsite_values.py index 68d0c77c..f9da4131 100644 --- a/eox_tenant/management/commands/edit_microsite_values.py +++ b/eox_tenant/management/commands/edit_microsite_values.py @@ -44,11 +44,12 @@ class Command(BaseCommand): Main class handling the execution of the command to alter the sites by adding or removing keys Examples: - - python manage.py lms edit_tenant_values --add EDNX_USE_SIGNAL True - - python manage.py lms edit_tenant_values --delete EDNX_USE_SIGNAL + - python manage.py lms edit_microsite_values --add EDNX_USE_SIGNAL True + - python manage.py lms edit_microsite_values --delete EDNX_USE_SIGNAL Advanced example: - - python manage.py lms edit_tenant_values --pattern yoursite.com -v 2 --add NESTED.KEY.NAME {interpolated_value} -f + - python manage.py lms edit_microsite_values --pattern yoursite.com -v 2 \ + --add NESTED.KEY.NAME {interpolated_value} -f """ help = """ Exposes a cli to perform bulk modification of eox_tenant sites diff --git a/eox_tenant/test/test_create_or_udpate_tenant_config.py b/eox_tenant/test/test_create_or_udpate_tenant_config.py index f6b891ca..decd7c4c 100644 --- a/eox_tenant/test/test_create_or_udpate_tenant_config.py +++ b/eox_tenant/test/test_create_or_udpate_tenant_config.py @@ -14,7 +14,7 @@ class CreateOrUpdateTenantConfigTestCase(TestCase): def setUp(self): """This method creates TenantConfig objects in database""" self.test_conf = { - "lms_configs": {"KEY": "value", "NESTED_KEY": {"key": "value"}}, + "lms_configs": {"NEW KEY": "value-updated", "NESTED_KEY": {"key": "value"}}, "studio_configs": {"STUDIO_KEY": "value", "STUDIO_NESTED_KEY": {"key": "value"}, } } self.external_key = "test" @@ -123,6 +123,7 @@ def test_command_with_long_external_key(self): def test_command_update_existing_tenant(self): """Tests that command successfully updates existing TenantConfig.""" config = TenantConfig.objects.get(external_key=self.external_key) + self.assertTrue(config.lms_configs == {"KEY": "value"}) self.assertTrue(config.studio_configs == {}) call_command( "create_or_update_tenant_config", @@ -134,7 +135,29 @@ def test_command_update_existing_tenant(self): "studio.test.domain" ) updated_config = TenantConfig.objects.get(external_key=self.external_key) - self.assertTrue(updated_config.lms_configs == self.test_conf["lms_configs"]) + for key, value in self.test_conf["lms_configs"].items(): + self.assertTrue(updated_config.lms_configs[key] == value) + self.assertTrue(updated_config.lms_configs["KEY"] == "value") self.assertTrue(updated_config.studio_configs == self.test_conf["studio_configs"]) created_routes = Route.objects.filter(domain__contains="test.domain").count() self.assertTrue(created_routes == 2) + + def test_command_update_existing_tenant_override(self): + """Tests that command successfully replaces existing TenantConfig with override param.""" + config = TenantConfig.objects.get(external_key=self.external_key) + self.assertTrue(config.studio_configs == {}) + new_conf = {"lms_configs": {"NEW_KEY": "new value"}} + call_command( + "create_or_update_tenant_config", + "--external-key", + self.external_key, + "--config", + json.dumps(new_conf), + "test.domain", + "studio.test.domain", + "--override", + ) + updated_config = TenantConfig.objects.get(external_key=self.external_key) + self.assertTrue(updated_config.lms_configs == new_conf["lms_configs"]) + created_routes = Route.objects.filter(domain__contains="test.domain").count() + self.assertTrue(created_routes == 2) From 4046f372881dc3512abcc7747c76becb8f17bc18 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 23 Jun 2023 10:53:38 +0530 Subject: [PATCH 6/6] refactor: use inbuilt open method --- .../management/commands/create_or_update_tenant_config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eox_tenant/management/commands/create_or_update_tenant_config.py b/eox_tenant/management/commands/create_or_update_tenant_config.py index a346228e..47490247 100644 --- a/eox_tenant/management/commands/create_or_update_tenant_config.py +++ b/eox_tenant/management/commands/create_or_update_tenant_config.py @@ -2,7 +2,6 @@ Create or updates the TenantConfig for given routes. """ -import codecs import json import logging @@ -18,7 +17,7 @@ def load_json_from_file(filename): """ Loads json content from file. """ - with codecs.open(filename, encoding='utf-8') as file: + with open(filename, encoding='utf-8') as file: return json.load(file)