Skip to content

Commit

Permalink
feat: improve test coverage and use pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun committed Nov 8, 2024
1 parent aae0fac commit 6145fe4
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 19 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ jobs:
pip install -r tests/requirements/${{ matrix.requirements-file }}
python setup.py install
- name: Run coverage
run: coverage run ./tests/settings.py
- name: Run test coverage
run: coverage run -m pytest

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ You can run tests by executing::
virtualenv env
source env/bin/activate
pip install -r tests/requirements.txt
python setup.py test
pytest


Upgrading from version 4 or lower
Expand Down
123 changes: 123 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import os
import sys

import django
from django.conf import global_settings, settings
from django.test.utils import get_runner

from tests.settings import HELPER_SETTINGS


CMS_APP = [
"cms",
"menus",
"easy_thumbnails",
"treebeard",
"sekizai",
"djangocms_link",
]
CMS_APP_STYLE = []
CMS_PROCESSORS = []
CMS_MIDDLEWARE = [
"cms.middleware.user.CurrentUserMiddleware",
"cms.middleware.page.CurrentPageMiddleware",
"cms.middleware.toolbar.ToolbarMiddleware",
"cms.middleware.language.LanguageCookieMiddleware",
]

INSTALLED_APPS = (
[
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.staticfiles",
]
+ CMS_APP_STYLE
+ ["django.contrib.admin", "django.contrib.messages"]
+ CMS_APP
)
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
TEMPLATE_LOADERS = [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
]
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
TEMPLATE_CONTEXT_PROCESSORS = [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
] + CMS_PROCESSORS
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
os.path.join(os.path.dirname(__file__), "templates"),
# insert your TEMPLATE_DIRS here
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": TEMPLATE_CONTEXT_PROCESSORS,
},
},
]
MIDDLEWARE = [
"django.middleware.http.ConditionalGetMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
] + CMS_MIDDLEWARE
SITE_ID = 1
LANGUAGE_CODE = "en"
LANGUAGES = (("en", "English"),)
STATIC_URL = "/static/"
MEDIA_URL = "/media/"
DEBUG = True
CMS_TEMPLATES = (("fullwidth.html", "Fullwidth"), ("page.html", "Normal page"))
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
MIGRATION_MODULES = {}
URL_CONF = "tests.utils.urls"


def pytest_configure():
INSTALLED_APPS.extend(HELPER_SETTINGS.pop("INSTALLED_APPS"))

settings.configure(
default_settings=global_settings,
**{
**dict(
INSTALLED_APPS=INSTALLED_APPS,
TEMPLATES=TEMPLATES,
DATABASES=DATABASES,
SITE_ID=SITE_ID,
LANGUAGES=LANGUAGES,
CMS_CONFIRM_VERSION4=True,
MIGRATION_MODULES=MIGRATION_MODULES,
ROOT_URLCONF=URL_CONF,
STATIC_URL=STATIC_URL,
MEDIA_URL=MEDIA_URL,
SECRET_KEY="Secret!",
MIDDLEWARE=MIDDLEWARE,
),
**HELPER_SETTINGS,
}
)
django.setup()


if __name__ == "__main__":
pytest_configure()

argv = ["tests"] if sys.argv is None else sys.argv
tests = argv[1:] if len(argv) > 1 else ["tests"]
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(tests)
sys.exit(bool(failures))
8 changes: 8 additions & 0 deletions djangocms_link/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,14 @@ def formfield(self, **kwargs):
kwargs.setdefault("form_class", LinkFormField)
return super().formfield(**kwargs)

def get_prep_value(self, value):
if isinstance(value, dict):
# Drop any cached value without changing the original value
return super().get_prep_value(dict(**{
key: val for key, val in value.items() if key != "__cache__"
}))
return super().get_prep_value(value)

def from_db_value(self, value, expression, connection):
value = super().from_db_value(value, expression, connection)
return LinkDict(value)
Expand Down
7 changes: 5 additions & 2 deletions djangocms_link/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class LinkDict(dict):
the url of the link. The url property is cached to avoid multiple db lookups."""

def __init__(self, initial=None, **kwargs):
anchor = kwargs.pop("anchor", None)
super().__init__(**kwargs)
if initial:
if isinstance(initial, dict):
Expand All @@ -85,8 +86,10 @@ def __init__(self, initial=None, **kwargs):
self["internal_link"] = (
f"{initial._meta.app_label}.{initial._meta.model_name}:{initial.pk}"
)
if "anchor" in kwargs:
self["anchor"] = kwargs["anchor"]
self["__cache__"] = initial.get_absolute_url()
if anchor:
self["anchor"] = anchor
self["__cache__"] += anchor

@property
def url(self):
Expand Down
11 changes: 2 additions & 9 deletions djangocms_link/templatetags/djangocms_link_tags.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django import template
from django.db import models

from djangocms_link.helpers import get_link
from djangocms_link.helpers import LinkDict, get_link


try:
Expand All @@ -22,10 +21,4 @@ def to_url(value):

@register.filter
def to_link(value):
if isinstance(value, File):
return {"file_link": value.pk}
elif isinstance(value, models.Model):
return {
"internal_link": f"{value._meta.app_label}.{value._meta.model_name}:{value.pk}"
}
return {"external_link": value}
return LinkDict(value)
2 changes: 2 additions & 0 deletions tests/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ isort
flake8
pyflakes>=2.1
django-test-migrations
pytest
pytest-django
1 change: 0 additions & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"easy_thumbnails.processors.filters",
),
"ALLOWED_HOSTS": ["localhost"],
"DJANGOCMS_LINK_USE_SELECT2": True,
"CMS_TEMPLATES": (
("page.html", "Normal page"),
("static_placeholder.html", "Page with static placeholder"),
Expand Down
28 changes: 25 additions & 3 deletions tests/test_link_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from filer.models import File

from djangocms_link.helpers import LinkDict
from djangocms_link.models import Link
from tests.utils.models import ThirdPartyModel


Expand Down Expand Up @@ -46,12 +47,11 @@ def test_internal_link(self):
{"internal_link": f"{obj._meta.app_label}.{obj._meta.model_name}:{obj.pk}"}
)
link2 = LinkDict(obj)
link3 = LinkDict(obj, anchor="test")
link3 = LinkDict(obj, anchor="#test")

self.assertEqual(link1, link2)
self.assertEqual(link1.url, obj.get_absolute_url())
self.assertEqual(link2.url, obj.get_absolute_url())
self.assertEqual(link3.url, f"{obj.get_absolute_url()}test")
self.assertEqual(link3.url, f"{obj.get_absolute_url()}#test")
self.assertEqual(link1.type, "internal_link")
self.assertEqual(link2.type, "internal_link")
self.assertEqual(link3.type, "internal_link")
Expand All @@ -66,3 +66,25 @@ def test_link_types(self):
self.assertEqual(external.type, "external_link")
self.assertEqual(phone.type, "tel")
self.assertEqual(mail.type, "mailto")

def test_db_queries(self):
obj = ThirdPartyModel.objects.create(
name=get_random_string(5), path=get_random_string(5)
)
link = LinkDict(obj)
with self.assertNumQueries(0):
self.assertEqual(link.url, obj.get_absolute_url())

def test_cache_no_written_to_db(self):
obj = ThirdPartyModel.objects.create(
name=get_random_string(5), path=get_random_string(5)
)
link = Link.objects.create(
link=LinkDict(obj)
)
self.assertEqual(link.link.url, link.link["__cache__"]) # populates cache
link.save()

link = Link.objects.get(pk=link.pk) # load from db

self.assertNotIn("__cache__", link.link)
11 changes: 10 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pprint import pprint

from django.core.exceptions import ValidationError
from django.test import TestCase

Expand Down Expand Up @@ -125,7 +127,10 @@ def test_to_link_template_tag(self):

self.assertEqual(to_link(self.file), {"file_link": self.file.pk})
self.assertEqual(
to_link(self.page), {"internal_link": f"cms.page:{self.page.pk}"}
to_link(self.page), {
"internal_link": f"cms.page:{self.page.pk}",
"__cache__": self.page.get_absolute_url(),
}
)
self.assertEqual(
to_link("https://www.django-cms.org/#some_id"),
Expand All @@ -143,3 +148,7 @@ def test_respect_link_is_optional(self):
# now we allow the link to be empty
instance.link_is_optional = True
instance.clean()

def test_settings(self):
from django.conf import settings
pprint(settings.__dict__)
9 changes: 9 additions & 0 deletions tests/utils/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@
{% show_menu 0 100 100 100 %}
</ul>
{% block content %}
{% endblock content %}
</div>
{% render_block "js" %}
{% with_data "js-script" as jsset %}
{% for js in jsset %}<script type="text/javascript" src="{% static js %}"></script>{% endfor %}
{% end_with_data %}
{% render_block "js_end" %}
</body>
</html>
8 changes: 8 additions & 0 deletions tests/utils/templates/fullwidth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% load cms_tags %}

{% block title %}{% page_attribute 'title' %}{% endblock title %}

{% block content %}
{% placeholder "content" %}
{% endblock content %}
8 changes: 8 additions & 0 deletions tests/utils/templates/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% load cms_tags %}

{% block title %}{% page_attribute 'title' %}{% endblock title %}

{% block content %}
{% placeholder "content" %}
{% endblock content %}
23 changes: 23 additions & 0 deletions tests/utils/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import include, path, re_path
from django.views.i18n import JavaScriptCatalog
from django.views.static import serve


admin.autodiscover()

urlpatterns = [
re_path(r"^media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT, "show_indexes": True}), # NOQA
re_path(r"^jsi18n/(?P<packages>\S+?)/$", JavaScriptCatalog.as_view()), # NOQA
]
i18n_urls = [
re_path(r"^admin/", admin.site.urls),
]

i18n_urls.append(path("", include("cms.urls"))) # NOQA

urlpatterns += i18n_patterns(*i18n_urls)
urlpatterns += staticfiles_urlpatterns()

0 comments on commit 6145fe4

Please sign in to comment.