From fa0de6c3ce125f51e709e0039f6870a194418cac Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Sat, 14 Dec 2024 12:19:44 +0100 Subject: [PATCH] chore: add pytest and github workflows --- .github/workflows/ruff.yml | 8 ++ .github/workflows/run-pytest.yml | 54 +++++++++++++ .python-version | 2 +- tests/__init__.py | 0 tests/pytest/__init__.py | 0 tests/pytest/_testsite/__init__.py | 0 tests/pytest/_testsite/urls.py | 38 +++++++++ tests/pytest/_testsite/views.py | 2 + tests/pytest/settings.py | 41 ++++++++++ .../test_commands_dump_routes_resolver.py | 79 +++++++++++++++++++ 10 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ruff.yml create mode 100644 .github/workflows/run-pytest.yml create mode 100644 tests/__init__.py create mode 100644 tests/pytest/__init__.py create mode 100644 tests/pytest/_testsite/__init__.py create mode 100644 tests/pytest/_testsite/urls.py create mode 100644 tests/pytest/_testsite/views.py create mode 100644 tests/pytest/settings.py create mode 100644 tests/pytest/test_commands_dump_routes_resolver.py diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..15b907d --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,8 @@ +name: Ruff +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/ruff-action@v2 diff --git a/.github/workflows/run-pytest.yml b/.github/workflows/run-pytest.yml new file mode 100644 index 0000000..f55131b --- /dev/null +++ b/.github/workflows/run-pytest.yml @@ -0,0 +1,54 @@ +name: Run Pytest + +on: [push, pull_request] + +env: + UV_SYSTEM_PYTHON: 1 + +jobs: + test: + name: Test on Python ${{ matrix.python-version }} and Django ${{ matrix.django-version }} + runs-on: ubuntu-latest + outputs: + failing-test: ${{ steps.test.outputs.fail }} + strategy: + matrix: + python-version: [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", + "3.13", + ] + django-version: [ + "django>=4.2,<5.0", + "django>=5.0,<5.1", + "django>=5.1,<5.2", + ] + exclude: + - python-version: "3.8" + django-version: "django>=5.0,<5.1" + - python-version: "3.8" + django-version: "django>=5.1,<5.2" + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: '${{ matrix.python-version }}' + + - name: Install the project + run: | + uv sync --all-extras --dev + uv add '${{ matrix.django-version }}; python_version == "${{ matrix.python-version }}"' + + - name: Run tests + id: test + run: | + uv run pytest diff --git a/.python-version b/.python-version index 24ee5b1..221a964 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13 +>=3.8 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/pytest/__init__.py b/tests/pytest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/pytest/_testsite/__init__.py b/tests/pytest/_testsite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/pytest/_testsite/urls.py b/tests/pytest/_testsite/urls.py new file mode 100644 index 0000000..a30861e --- /dev/null +++ b/tests/pytest/_testsite/urls.py @@ -0,0 +1,38 @@ +from django.conf.urls.i18n import i18n_patterns +from django.contrib import admin +from django.urls import include, path, re_path + +from . import views + +included_urlpatterns = [ + re_path(r"^test/(\d+)/foo/(\w+)/bar/$", views.ping, name="included_test_with_args"), +] + +urlpatterns = [ + re_path(r"^admin/", admin.site.urls), + re_path(r"^ping/$", views.ping, name="ping"), + re_path(r"^ping/(\d+)/foo/(\w+)/bar/$", views.ping, name="ping_with_args"), + re_path( + r"^ping/(?P\w+)/foo/(?P\d+)/bar/$", + views.ping, + name="ping_with_kwargs", + ), + re_path( + r"^ping/(\d+)/fooo?/baa?r/$", views.ping, name="ping_with_optional_character" + ), + re_path(r"^ping/(\d+)/foo(?:bar)?/$", views.ping, name="ping_with_optional_group"), + re_path( + r"^ping/(\d+)/(?:/(?P[a-zA-Z]+)/)?$", + views.ping, + name="ping_with_optional_kwarg", + ), + re_path(r"^included/(?P\w+)/", include(included_urlpatterns)), + path("ping//", views.ping, name="ping_with_path"), + path("ping///", views.ping, name="ping_with_paths"), + re_path(r"ping/(?P[0-9]{4})/$", views.ping, name="ping_with_re_path"), + path("ping//", views.ping, name="ping_with_path_without_converter"), +] + +urlpatterns += i18n_patterns( + path("ping-i18n/", views.ping, name="ping_i18n"), +) diff --git a/tests/pytest/_testsite/views.py b/tests/pytest/_testsite/views.py new file mode 100644 index 0000000..e264253 --- /dev/null +++ b/tests/pytest/_testsite/views.py @@ -0,0 +1,2 @@ +def ping(request): + return "pong" diff --git a/tests/pytest/settings.py b/tests/pytest/settings.py new file mode 100644 index 0000000..aeb1f17 --- /dev/null +++ b/tests/pytest/settings.py @@ -0,0 +1,41 @@ +SECRET_KEY = "test" + +ROOT_URLCONF = "tests.pytest._testsite.urls" + +USE_TZ = True +LANGUAGE_CODE = "en" +LANGUAGES = [("en", "English")] + +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.humanize", + "django.contrib.sessions", + "django.contrib.sitemaps", + "django.contrib.messages", + "django.contrib.staticfiles", + "ts_routes", + "django.contrib.admin", +] + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + }, +] + +TS_ROUTES_INCLUSION_LIST = [ + "included_test_with_args", + "ping", + "ping_with_args", + "ping_with_kwargs", + "ping_with_optional_character", + "ping_with_optional_group", + "ping_with_optional_kwarg", + "ping_with_path", + "ping_with_paths", + "ping_with_re_path", + "ping_with_path_without_converter", + "ping_i18n", +] diff --git a/tests/pytest/test_commands_dump_routes_resolver.py b/tests/pytest/test_commands_dump_routes_resolver.py new file mode 100644 index 0000000..d72b1c1 --- /dev/null +++ b/tests/pytest/test_commands_dump_routes_resolver.py @@ -0,0 +1,79 @@ +import tempfile +from os import path + +from django.core.management import call_command +from django.template.loader import render_to_string +from django.test.testcases import SimpleTestCase, override_settings +from django.utils import translation + +from ts_routes.serializers import url_patterns_serializer + + +class TestDumpRoutesResolverCommand(SimpleTestCase): + @override_settings(LANGUAGES=[("en", "English")]) + def test_can_export_the_routes_and_resolver_module_into_a_specific_directory(self): + export_dir = tempfile.mkdtemp() + call_command("dump_routes_resolver", output_dir=export_dir) + + index_filename = path.join(export_dir, "index.ts") + en_filename = path.join(export_dir, "en.ts") + nl_filename = path.join(export_dir, "nl.ts") + + assert path.exists(index_filename) + assert path.exists(en_filename) + assert not path.exists(nl_filename) + + result = render_to_string( + "ts_routes/_dump/resolver.ts", {"routes": url_patterns_serializer.to_json()} + ) + + with open(index_filename) as fd: + assert fd.read() == result + + with open(en_filename) as fd: + assert fd.read() == result + + @override_settings(LANGUAGES=[("en", "English"), ("nl", "Dutch")]) + def test_can_export_the_routes_and_resolver_module_into_a_specific_directory_languages( + self, + ): + export_dir = tempfile.mkdtemp() + call_command("dump_routes_resolver", output_dir=export_dir) + + index_filename = path.join(export_dir, "index.ts") + en_filename = path.join(export_dir, "en.ts") + nl_filename = path.join(export_dir, "nl.ts") + + assert path.exists(index_filename) + assert path.exists(en_filename) + assert path.exists(nl_filename) + + with translation.override("en"): + result = render_to_string( + "ts_routes/_dump/resolver.ts", + {"routes": url_patterns_serializer.to_json()}, + ) + + with open(index_filename) as fd: + assert fd.read() == result + + with open(en_filename) as fd: + assert fd.read() == result + + with open(nl_filename) as fd: + assert fd.read() != result, result + + with translation.override("nl"): + result = render_to_string( + "ts_routes/_dump/resolver.ts", + {"routes": url_patterns_serializer.to_json()}, + ) + + with open(index_filename) as fd: + assert fd.read() != result + + with open(en_filename) as fd: + assert fd.read() != result + + with open(nl_filename) as fd: + assert fd.read() == result