diff --git a/backend/kesaseteli/applications/api/v1/views.py b/backend/kesaseteli/applications/api/v1/views.py index de71f36287..4654071263 100644 --- a/backend/kesaseteli/applications/api/v1/views.py +++ b/backend/kesaseteli/applications/api/v1/views.py @@ -18,6 +18,7 @@ from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.generics import ListAPIView +from rest_framework.pagination import LimitOffsetPagination from rest_framework.parsers import MultiPartParser from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response @@ -657,6 +658,7 @@ class EmployerApplicationViewSet(AuditLoggingModelViewSet): queryset = EmployerApplication.objects.all() serializer_class = EmployerApplicationSerializer permission_classes = [IsAuthenticated, EmployerApplicationPermission] + pagination_class = LimitOffsetPagination def get_queryset(self): """ diff --git a/backend/kesaseteli/applications/migrations/0046_make_employerapplication_ordering_consistent.py b/backend/kesaseteli/applications/migrations/0046_make_employerapplication_ordering_consistent.py new file mode 100644 index 0000000000..3ef8cb8cd6 --- /dev/null +++ b/backend/kesaseteli/applications/migrations/0046_make_employerapplication_ordering_consistent.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.15 on 2026-02-04 12:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("applications", "0045_employersummervoucher_job_type_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="employerapplication", + options={ + "ordering": ["-created_at", "id"], + "verbose_name": "employer application", + "verbose_name_plural": "employer applications", + }, + ), + ] diff --git a/backend/kesaseteli/applications/models.py b/backend/kesaseteli/applications/models.py index c298004c6d..2058b6e196 100644 --- a/backend/kesaseteli/applications/models.py +++ b/backend/kesaseteli/applications/models.py @@ -1257,7 +1257,7 @@ class EmployerApplication(HistoricalModel, TimeStampedModel, UUIDModel): class Meta: verbose_name = _("employer application") verbose_name_plural = _("employer applications") - ordering = ["-created_at"] + ordering = ["-created_at", "id"] class EmployerSummerVoucher(HistoricalModel, TimeStampedModel, UUIDModel): diff --git a/backend/kesaseteli/applications/tests/test_applications_api.py b/backend/kesaseteli/applications/tests/test_applications_api.py index 62d4fbd0ba..8697e6de89 100644 --- a/backend/kesaseteli/applications/tests/test_applications_api.py +++ b/backend/kesaseteli/applications/tests/test_applications_api.py @@ -1,5 +1,6 @@ import pytest from django.test import override_settings +from freezegun import freeze_time from rest_framework.reverse import reverse from applications.api.v1.serializers import ( @@ -428,3 +429,97 @@ def test_applications_view_permissions(api_client, application, submitted_applic assert response.status_code == 200 assert any(x["id"] == str(application.id) for x in response.data) assert any(x["id"] == str(submitted_application.id) for x in response.data) + + +@pytest.mark.django_db +def test_pagination_is_disabled_with_default_settings( + api_client, company, user, settings +): + """Test that pagination is disabled with the default settings.""" + assert settings.REST_FRAMEWORK.get("PAGE_SIZE") is None + EmployerApplicationFactory.create_batch( + company=company, user=user, status=EmployerApplicationStatus.DRAFT, size=10 + ) + + response = api_client.get(get_list_url()) + + assert response.status_code == 200 + assert len(response.data) == 10 + assert "count" not in response.data + assert isinstance(response.data, list) + + +@pytest.mark.django_db +def test_pagination_with_small_limit(api_client, company, user): + """Test pagination with small limit using LimitOffsetPagination.""" + EmployerApplicationFactory.create_batch( + company=company, user=user, status=EmployerApplicationStatus.DRAFT, size=5 + ) + + response = api_client.get(get_list_url(), {"limit": 2}) + + assert response.status_code == 200 + assert response.data["count"] == 5 + assert len(response.data["results"]) == 2 + assert response.data["next"] is not None + assert response.data["previous"] is None + + # Test offset parameter + response = api_client.get(get_list_url(), {"limit": 2, "offset": 2}) + assert response.status_code == 200 + assert len(response.data["results"]) == 2 + assert response.data["next"] is not None + assert response.data["previous"] is not None + + # Test last page + response = api_client.get(get_list_url(), {"limit": 2, "offset": 4}) + assert response.status_code == 200 + assert len(response.data["results"]) == 1 + assert response.data["next"] is None + assert response.data["previous"] is not None + + +@pytest.mark.django_db +def test_pagination_with_large_limit(api_client, company, user): + """Test pagination with large limit returns all results in single page.""" + EmployerApplicationFactory.create_batch( + company=company, user=user, status=EmployerApplicationStatus.SUBMITTED, size=50 + ) + + response = api_client.get(get_list_url(), {"limit": 100}) + + assert response.status_code == 200 + assert response.data["count"] == 50 + assert len(response.data["results"]) == 50 + assert response.data["next"] is None + assert response.data["previous"] is None + + +@pytest.mark.django_db +def test_applications_ordering_by_created_at(api_client, company, user): + """Test that employer applications are ordered by -created_at (newest first).""" + # Create applications in non-chronological order to verify ordering works + with freeze_time("2024-01-01 11:00:00"): + app_middle = EmployerApplicationFactory( + company=company, user=user, status=EmployerApplicationStatus.SUBMITTED + ) + + with freeze_time("2024-01-01 12:00:00"): + app_newest = EmployerApplicationFactory( + company=company, user=user, status=EmployerApplicationStatus.DRAFT + ) + + with freeze_time("2024-01-01 10:00:00"): + app_oldest = EmployerApplicationFactory( + company=company, user=user, status=EmployerApplicationStatus.DRAFT + ) + + response = api_client.get(get_list_url(), {"limit": 10}) + + assert response.status_code == 200 + assert response.data["count"] == 3 + results = response.data["results"] + + assert str(results[0]["id"]) == str(app_newest.id) + assert str(results[1]["id"]) == str(app_middle.id) + assert str(results[2]["id"]) == str(app_oldest.id)