diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index 41a94badd..deda33932 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -145,9 +145,14 @@ class Command(BaseCommand): os.path.dirname(__file__), "../../../frontend_dist/" ) frontend_target_dir = f"{settings.BASE_DIR}/frontend" - if os.path.exists(base_frontend_dir): + if os.path.isdir(base_frontend_dir): shutil.rmtree(frontend_target_dir, ignore_errors=True) - shutil.copytree(base_frontend_dir, frontend_target_dir) + os.makedirs(frontend_target_dir, exist_ok=True) + for entry in os.scandir(base_frontend_dir): + if entry.name != "config.json": + os.symlink(f"{base_frontend_dir}/{entry.name}", + f"{frontend_target_dir}/{entry.name}", + target_is_directory=entry.is_dir()) with open(f"{frontend_target_dir}/config.json", "w") as fp: fp.write( f"""{{ diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py index f47097c18..c9a086a81 100644 --- a/modoboa/core/tests/test_core.py +++ b/modoboa/core/tests/test_core.py @@ -1,11 +1,15 @@ """Tests for core application.""" from io import StringIO +import json +import pathlib +import shutil import httmock from dateutil.relativedelta import relativedelta from oauth2_provider.models import get_application_model +from django.conf import settings from django.core import mail from django.core import management from django.urls import reverse @@ -44,6 +48,11 @@ class AuthenticationTestCase(ModoTestCase): class ManagementCommandsTestCase(SimpleModoTestCase): """Test management commands.""" + def tearDown(self): + frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend" + if frontend_dir.exists(): + shutil.rmtree(frontend_dir) + def test_change_default_admin(self): """Use dedicated option.""" management.call_command("load_initial_data", "--admin-username", "modoadmin") @@ -58,8 +67,8 @@ class ManagementCommandsTestCase(SimpleModoTestCase): management.call_command("cleanlogs") self.assertEqual(models.Log.objects.count(), 1) - def test_non_duplicate_client_creation(self): - """Test that the load_initial_data command do not create duplicates + def test_init_data_non_duplicate_client_creation(self): + """Test that the load_initial_data command does not create duplicates client for the frontend deployment.""" app_model = get_application_model() management.call_command("load_initial_data") @@ -67,6 +76,31 @@ class ManagementCommandsTestCase(SimpleModoTestCase): management.call_command("load_initial_data") self.assertEqual(1, app_model.objects.filter(name="modoboa_frontend").count()) + def test_init_data_frontend_dir_creation(self): + """Test that the load_initial_data command creates a frontend directory + with symlinks to the shipped frontend files.""" + # Create our own dummy frontend directory if `vite` hasn’t been invoked yet + frontend_src = pathlib.Path(__file__).parent.parent.parent / "frontend_dist" + if not frontend_src.is_dir(): + (frontend_src / "dummy").mkdir(parents=True) + self.addCleanup(shutil.rmtree, frontend_src) + + frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend" + self.assertFalse(frontend_dir.exists(), f"frontend directory {frontend_dir} doesn’t exist before load_initial_data") + management.call_command("load_initial_data") + self.assertTrue(frontend_dir.is_dir(), f"frontend directory {frontend_dir} is directory after load_initial_data") + for entry in frontend_dir.iterdir(): + if entry.name == "config.json": + self.assertTrue(entry.is_file() and not entry.is_symlink(), f"frontend directory entry {entry} is regular file") + try: + content = json.loads(entry.read_text("utf-8")) + except (ValueError, UnicodeDecodeError): + self.fail(f"frontend directory entry {entry} is not UTF-8 encoded JSON") + for key_name in ("API_BASE_URL", "OAUTH_AUTHORITY_URL", "OAUTH_CLIENT_ID", "OAUTH_REDIRECT_URI", "OAUTH_POST_REDIRECT_URI"): + self.assertIn(key_name, content) + else: + self.assertTrue(entry.is_symlink(), f"frontend directory entry {entry} is symlink") + def test_clean_inactive_accounts(self): """Run clean_inactive_accounts command.""" management.call_command("load_initial_data")