From 202cfe47f3e77e9746e40b594d5727c941b5b6fe Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Fri, 15 May 2026 02:58:41 +0200 Subject: [PATCH] dnscms: add some basic tests --- dnscms/associations/tests.py | 2 - dnscms/dnscms/settings/test.py | 23 +++++++ dnscms/events/tests.py | 2 - dnscms/openinghours/tests.py | 3 - dnscms/pyproject.toml | 8 +++ dnscms/tests/__init__.py | 0 dnscms/tests/conftest.py | 78 +++++++++++++++++++++++ dnscms/tests/test_events.py | 88 ++++++++++++++++++++++++++ dnscms/tests/test_graphql.py | 38 +++++++++++ dnscms/uv.lock | 111 ++++++++++++++++++++++++++++++++- 10 files changed, 345 insertions(+), 8 deletions(-) delete mode 100644 dnscms/associations/tests.py create mode 100644 dnscms/dnscms/settings/test.py delete mode 100644 dnscms/events/tests.py delete mode 100644 dnscms/openinghours/tests.py create mode 100644 dnscms/tests/__init__.py create mode 100644 dnscms/tests/conftest.py create mode 100644 dnscms/tests/test_events.py create mode 100644 dnscms/tests/test_graphql.py diff --git a/dnscms/associations/tests.py b/dnscms/associations/tests.py deleted file mode 100644 index 4929020..0000000 --- a/dnscms/associations/tests.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your tests here. diff --git a/dnscms/dnscms/settings/test.py b/dnscms/dnscms/settings/test.py new file mode 100644 index 0000000..6ecc72c --- /dev/null +++ b/dnscms/dnscms/settings/test.py @@ -0,0 +1,23 @@ +from .base import * # noqa: F401, F403 + +SECRET_KEY = "test-secret-key" +DEBUG = False +ALLOWED_HOSTS = ["*"] + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} + +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + +PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] diff --git a/dnscms/events/tests.py b/dnscms/events/tests.py deleted file mode 100644 index 4929020..0000000 --- a/dnscms/events/tests.py +++ /dev/null @@ -1,2 +0,0 @@ - -# Create your tests here. diff --git a/dnscms/openinghours/tests.py b/dnscms/openinghours/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/dnscms/openinghours/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/dnscms/pyproject.toml b/dnscms/pyproject.toml index 5820bd3..bd3e52d 100644 --- a/dnscms/pyproject.toml +++ b/dnscms/pyproject.toml @@ -18,6 +18,9 @@ dependencies = [ [dependency-groups] dev = [ "ruff>=0.15.1", + "pytest>=8.3", + "pytest-django>=4.9", + "wagtail-factories>=4.2", ] [tool.uv] @@ -34,3 +37,8 @@ line-length = 99 select = ["F", "E", "W", "Q", "UP", "DJ"] ignore = [] exclude = ["**/migrations/*.py"] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "dnscms.settings.test" +python_files = ["test_*.py"] +testpaths = ["tests"] diff --git a/dnscms/tests/__init__.py b/dnscms/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dnscms/tests/conftest.py b/dnscms/tests/conftest.py new file mode 100644 index 0000000..f42a214 --- /dev/null +++ b/dnscms/tests/conftest.py @@ -0,0 +1,78 @@ +import json + +import factory +import pytest +import wagtail_factories +from wagtail.models import Page + +from events.models import EventIndex, EventPage +from venues.models import VenueIndex, VenuePage + + +class EventIndexFactory(wagtail_factories.PageFactory): + title = factory.Sequence(lambda n: f"Events {n}") + + class Meta: + model = EventIndex + + +class EventPageFactory(wagtail_factories.PageFactory): + title = factory.Sequence(lambda n: f"Event {n}") + + class Meta: + model = EventPage + + +class VenueIndexFactory(wagtail_factories.PageFactory): + title = factory.Sequence(lambda n: f"Venues {n}") + + class Meta: + model = VenueIndex + + +class VenuePageFactory(wagtail_factories.PageFactory): + title = factory.Sequence(lambda n: f"Venue {n}") + + class Meta: + model = VenuePage + + +@pytest.fixture +def root_page(db): + return Page.objects.get(depth=1) + + +@pytest.fixture +def home_page(root_page): + # Wagtail's initial migration creates a default "Welcome" page at depth=2. + # Reuse it so we don't fight slug collisions across tests. + return root_page.get_children().first() or root_page.add_child( + instance=Page(title="Home", slug="home") + ) + + +@pytest.fixture +def event_index(home_page): + return EventIndexFactory(parent=home_page) + + +@pytest.fixture +def venue(home_page): + venue_index = VenueIndexFactory(parent=home_page) + return VenuePageFactory(parent=venue_index) + + +@pytest.fixture +def graphql_post(client): + def _post(query, variables=None): + payload = {"query": query} + if variables is not None: + payload["variables"] = variables + response = client.post( + "/api/graphql/", + data=json.dumps(payload), + content_type="application/json", + ) + return response, response.json() + + return _post diff --git a/dnscms/tests/test_events.py b/dnscms/tests/test_events.py new file mode 100644 index 0000000..56d17b7 --- /dev/null +++ b/dnscms/tests/test_events.py @@ -0,0 +1,88 @@ +from datetime import timedelta + +import pytest +from django.core.exceptions import ValidationError +from django.utils import timezone + +from events.models import ( + EventOccurrence, + EventOrganizer, + EventOrganizerLink, + EventPage, +) +from tests.conftest import EventPageFactory + + +def test_eventpage_clean_unsets_specific_pricing_when_free(): + page = EventPage( + title="Free event", + slug="free-event", + free=True, + price_regular="100", + price_student="50", + price_member="25", + ) + + page.clean() + + assert page.price_regular == "" + assert page.price_student == "" + assert page.price_member == "" + + +def test_eventoccurrence_clean_rejects_both_venue_and_venue_custom(event_index, venue): + event = EventPageFactory(parent=event_index) + occurrence = EventOccurrence( + event=event, + start=timezone.now(), + venue=venue, + venue_custom="Frederikkeplassen", + ) + + with pytest.raises(ValidationError) as exc: + occurrence.clean() + assert "venue_custom" in exc.value.message_dict + + +def test_eventoccurrence_clean_requires_venue_or_venue_custom(event_index): + event = EventPageFactory(parent=event_index) + occurrence = EventOccurrence(event=event, start=timezone.now()) + + with pytest.raises(ValidationError) as exc: + occurrence.clean() + assert "venue" in exc.value.message_dict + + +def test_eventpage_manager_future_filters_past_and_annotates(event_index): + now = timezone.now() + + past = EventPageFactory(parent=event_index, title="Past") + EventOccurrence.objects.create( + event=past, start=now - timedelta(days=7), venue_custom="Old" + ) + + future = EventPageFactory(parent=event_index, title="Future") + EventOccurrence.objects.create( + event=future, start=now + timedelta(days=7), venue_custom="New" + ) + + results = list(EventPage.objects.live().future().order_by("next_occurrence")) + + assert [p.pk for p in results] == [future.pk] + assert results[0].next_occurrence is not None + + +def test_eventpage_clean_dedupes_organizers_by_name(event_index): + org_a = EventOrganizer.objects.create(name="DNS", slug="dns-a") + org_b = EventOrganizer.objects.create(name="DNS", slug="dns-b") + + event = EventPageFactory(parent=event_index) + EventOrganizerLink.objects.create(event=event, organizer=org_a) + EventOrganizerLink.objects.create(event=event, organizer=org_b) + + event = EventPage.objects.get(pk=event.pk) + assert event.organizer_links.count() == 2 + + event.clean() + + assert event.organizer_links.count() == 1 diff --git a/dnscms/tests/test_graphql.py b/dnscms/tests/test_graphql.py new file mode 100644 index 0000000..ed5d80c --- /dev/null +++ b/dnscms/tests/test_graphql.py @@ -0,0 +1,38 @@ +from datetime import timedelta + +from django.utils import timezone + +from events.models import EventOccurrence +from tests.conftest import EventPageFactory + + +def test_graphql_endpoint_responds(db, graphql_post): + response, body = graphql_post("{ __schema { queryType { name } } }") + + assert response.status_code == 200 + assert "errors" not in body + assert body["data"]["__schema"]["queryType"]["name"] == "Query" + + +def test_event_index_future_events_query(event_index, graphql_post): + upcoming = EventPageFactory(parent=event_index, title="Upcoming gig") + EventOccurrence.objects.create( + event=upcoming, + start=timezone.now() + timedelta(days=3), + venue_custom="Storsalen", + ) + + response, body = graphql_post( + """ + query { + eventIndex { + futureEvents { title } + } + } + """ + ) + + assert response.status_code == 200 + assert "errors" not in body, body + titles = [e["title"] for e in body["data"]["eventIndex"]["futureEvents"]] + assert "Upcoming gig" in titles diff --git a/dnscms/uv.lock b/dnscms/uv.lock index 2ce4538..b9ef0e4 100644 --- a/dnscms/uv.lock +++ b/dnscms/uv.lock @@ -62,6 +62,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -212,7 +221,10 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pytest" }, + { name = "pytest-django" }, { name = "ruff" }, + { name = "wagtail-factories" }, ] [package.metadata] @@ -227,7 +239,12 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.15.1" }] +dev = [ + { name = "pytest", specifier = ">=8.3" }, + { name = "pytest-django", specifier = ">=4.9" }, + { name = "ruff", specifier = ">=0.15.1" }, + { name = "wagtail-factories", specifier = ">=4.2" }, +] [[package]] name = "draftjs-exporter" @@ -247,6 +264,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, ] +[[package]] +name = "factory-boy" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "faker" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146, upload-time = "2025-02-03T09:49:04.433Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036, upload-time = "2025-02-03T09:49:01.659Z" }, +] + +[[package]] +name = "faker" +version = "40.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/06/70886e82d8f1d2b73454f3a7c1b7405300128df22e70d85a828951366932/faker-40.18.0.tar.gz", hash = "sha256:2207575c0e8f90e6ccd6dbef764de875c614d16d3db4eee9712d9a00087f2e70", size = 1968243, upload-time = "2026-05-14T16:43:04.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/5c0b2d3a4b7a715f1835dd3f963bfbe841a02ae5cad1df8ee0325dfad235/faker-40.18.0-py3-none-any.whl", hash = "sha256:61a6b94b74605ddb090a065deb197a1c585ae7a874c094cf6693671d271e6083", size = 2006355, upload-time = "2026-05-14T16:43:02.489Z" }, +] + [[package]] name = "filetype" version = "1.2.0" @@ -330,6 +371,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "laces" version = "0.1.2" @@ -424,6 +474,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/3c/63cbbe3a6a54e3ec925d2433bb431a4bf61695f34b5f1e689db642fb20c1/pillow_heif-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:514c856230995dc2f918fb12d83906885d42829e911868e23035e2d916c4c7c5", size = 5573890, upload-time = "2025-08-02T09:58:05.049Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "promise" version = "2.3" @@ -452,6 +511,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, ] +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-django" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/2b/db9a193df89e5660137f5428063bcc2ced7ad790003b26974adf5c5ceb3b/pytest_django-4.12.0.tar.gz", hash = "sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758", size = 91156, upload-time = "2026-02-14T18:40:49.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a5/41d091f697c09609e7ef1d5d61925494e0454ebf51de7de05f0f0a728f1d/pytest_django-4.12.0-py3-none-any.whl", hash = "sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85", size = 26123, upload-time = "2026-02-14T18:40:47.381Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -605,6 +701,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bb/0e/5efc903966b966df2261a66cce8cb88909e4ade86f1173a156aadbbd1a06/wagtail-7.3.1-py3-none-any.whl", hash = "sha256:eab131e15ab9edc7ed24143d44271e92af79239e105bc3e173d26c95d2b489b3", size = 9479191, upload-time = "2026-03-03T15:54:42.644Z" }, ] +[[package]] +name = "wagtail-factories" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "factory-boy" }, + { name = "wagtail" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/8c/2ece22c2757bb3f6ef34f36cf185246b136134cf594b7b1dde92cb0a331f/wagtail_factories-4.4.0.tar.gz", hash = "sha256:c77c13d438a2e999a9220ff1829f060116013aa519a81944577353f460ba8cba", size = 9939, upload-time = "2026-02-10T15:14:13.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/85/bd5aeaa54a10b6defca927f75bc3890acb7c3a9a23df8e10f3aa42e75807/wagtail_factories-4.4.0-py2.py3-none-any.whl", hash = "sha256:be0abb96d36bf0e3c733d7520fc1944441a379ab06d93af447fc4e71b67e2a01", size = 10754, upload-time = "2026-02-10T15:14:12.773Z" }, +] + [[package]] name = "wagtail-grapple" version = "0.29.0"