diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 000000000..30e6f2741 --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE = test_project.settings +python_files = test_*.py +addopts = -v --durations=0 \ No newline at end of file diff --git a/backend/src/zango/apps/dynamic_models/workspace/base.py b/backend/src/zango/apps/dynamic_models/workspace/base.py index 5628c6c85..12104f259 100644 --- a/backend/src/zango/apps/dynamic_models/workspace/base.py +++ b/backend/src/zango/apps/dynamic_models/workspace/base.py @@ -64,6 +64,7 @@ def __init__(self, wobj: object, request=None, as_systemuser=False) -> None: self.modules = self.get_ws_modules() self.packages = self.get_packages() self.models = [] # sorted with bfs + self._module_paths_cache = None # cache @classmethod def get_plugin_source(cls): @@ -113,6 +114,11 @@ def get_all_module_paths(self) -> list[str]: """ returns path of all ws modules as well as package modules """ + + # Return cached result if available + if self._module_paths_cache is not None: + return self._module_paths_cache + modules = [] ws_tree = self.get_wtree() bfs = ws_tree.bfs() @@ -127,6 +133,9 @@ def get_all_module_paths(self) -> list[str]: path = self.path + item["path"] if path not in modules: modules.append(path) + + # Cache the result + self._module_paths_cache = modules return modules def get_models(self) -> list[str]: diff --git a/backend/src/zango/test/timing_runner.py b/backend/src/zango/test/timing_runner.py new file mode 100644 index 000000000..8d93d940d --- /dev/null +++ b/backend/src/zango/test/timing_runner.py @@ -0,0 +1,17 @@ +import time +from django.test.runner import DiscoverRunner + +class TimingDiscoverRunner(DiscoverRunner): + def get_resultclass(self): + return TimingTextTestResult + +class TimingTextTestResult(DiscoverRunner.test_runner().resultclass): + def startTest(self, test): + self._started_at = time.time() + super().startTest(test) + + def addSuccess(self, test): + elapsed = time.time() - self._started_at + name = self.getDescription(test) + self.stream.writeln(f"\n{name} ... ok ({elapsed:.3f}s)") + super().addSuccess(test) \ No newline at end of file diff --git a/backend/test_project/test_project/pytest.ini b/backend/test_project/test_project/pytest.ini new file mode 100644 index 000000000..4ee569447 --- /dev/null +++ b/backend/test_project/test_project/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE = test_project.settings +python_files = test_*.py *_test.py *_tests.py +testpaths = tests \ No newline at end of file diff --git a/backend/test_project/test_project/settings.py b/backend/test_project/test_project/settings.py new file mode 100644 index 000000000..941b3b51d --- /dev/null +++ b/backend/test_project/test_project/settings.py @@ -0,0 +1,50 @@ +from pathlib import Path + +from zango.config.settings.base import * # noqa: F403 + + +BASE_DIR = Path(__file__).resolve().parent.parent + +environ.Env.read_env(os.path.join(BASE_DIR, ".env")) + + +class AttrDict(dict): + """ + A dictionary subclass for managing global settings with attribute-style access. + + This class allows getting and setting items in the global namespace + using both attribute and item notation. + """ + + def __getattr__(self, item): + return globals()[item] + + def __setattr__(self, item, value): + globals()[item] = value + + def __setitem__(self, key, value): + globals()[key] = value + + +# Call setup_settings to initialize the settings +settings_result = setup_settings(AttrDict(vars()), BASE_DIR) + +# Setting Overrides +# Any settings that need to be overridden or added should be done below this line +# to ensure they take effect after the initial setup + +SECRET_KEY = "django-insecure-3uvu-m1-#a7sm8f#nr@p@&e6t70^q67uzfz^rmn7nyd*8)jc*4" # pragma: allowlist secret +TEST_MIGRATION_RUNNING = True +PROJECT_NAME = "test_project" +TEST_RUNNER = 'zango.test.timing_runner.TimingDiscoverRunner' + +# To change the media storage to S3 you can use the BACKEND class provided by the default storage +# To change the static storage to S3 you can use the BACKEND class provided by the staticfiles storage +# STORAGES = { +# "default": {"BACKEND": "zango.core.storage_utils.S3MediaStorage"}, +# "staticfiles": {"BACKEND": "zango.core.storage_utils.S3StaticStorage"}, +# } + + +# INTERNAL_IPS can contain a list of IP addresses or CIDR blocks that are considered internal. +# Both individual IP addresses and CIDR notation (e.g., '192.168.1.1' or '192.168.1.0/24') can be provided. diff --git a/frontend/src/pages/platform/components/Modals/LaunchNewAppModal/LaunchNewAppForm.jsx b/frontend/src/pages/platform/components/Modals/LaunchNewAppModal/LaunchNewAppForm.jsx index ec931f593..a42bfd2f9 100644 --- a/frontend/src/pages/platform/components/Modals/LaunchNewAppModal/LaunchNewAppForm.jsx +++ b/frontend/src/pages/platform/components/Modals/LaunchNewAppModal/LaunchNewAppForm.jsx @@ -19,7 +19,11 @@ const LaunchNewAppForm = ({ closeModal }) => { }; let validationSchema = Yup.object({ - name: Yup.string().required('Required'), + name: Yup.string() + .required('Required') + .test('no-spaces', + 'App name cannot contain spaces', + value => !/\s/.test(value)), description: Yup.string().required('Required'), }); diff --git a/runtests.sh b/runtests.sh new file mode 100644 index 000000000..8c591c9c9 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +# Colorful output. +function greenprint { + echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m" +} + + +DATABASE=${DATABASE_HOST:-localhost} +DATABASE_PORT=${DATABASE_PORT:-5432} + +echo "Database: $DATABASE" + +while ! nc -v -w 1 "$DATABASE" "$DATABASE_PORT" > /dev/null 2>&1 < /dev/null; do + i=`expr $i + 1` + if [ $i -ge 50 ]; then + echo "$(date) - $DATABASE:$DATABASE_PORT still not reachable, giving up" + exit 1 + fi + echo "$(date) - waiting for $DATABASE:$DATABASE_PORT..." + sleep 1 +done +echo "postgres connection established" + +pushd backend/test_project + + +# PYTHOWARNINGS=d coverage run manage.py test -v2 zango --timing +PYTHOWARNINGS=d coverage run manage.py test -v2 zango \ No newline at end of file