Merge branch 'fix/public-booking-csrf' into develop

This commit is contained in:
2026-04-30 11:34:31 +02:00
2 changed files with 50 additions and 2 deletions

View File

@@ -1,12 +1,14 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.core import mail from django.core import mail
from django.urls import reverse from django.urls import reverse
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APIClient, APITestCase
from bookings.models import Reservation from bookings.models import Reservation
from bookings.services import generate_confirmation_token from bookings.services import generate_confirmation_token
@@ -16,6 +18,7 @@ from shows.models import Performance, Show, Venue
class BookingApiTests(APITestCase): class BookingApiTests(APITestCase):
def setUp(self): def setUp(self):
cache.clear()
self.show = Show.objects.create( self.show = Show.objects.create(
title="Open Stage", title="Open Stage",
slug="open-stage-api", slug="open-stage-api",
@@ -90,6 +93,48 @@ class BookingApiTests(APITestCase):
mail.outbox[0].body, mail.outbox[0].body,
) )
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
def test_reservation_creation_allows_anonymous_post_without_csrf(self):
csrf_client = APIClient(enforce_csrf_checks=True)
with self.captureOnCommitCallbacks(execute=True):
response = csrf_client.post(
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
{
"name": "Maria Rossi",
"email": "maria@example.com",
"party_size": 2,
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data["status"], Reservation.Status.PENDING)
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
def test_reservation_creation_ignores_session_csrf_for_public_endpoint(self):
csrf_client = APIClient(enforce_csrf_checks=True)
user = get_user_model().objects.create_user(
username="box-office",
email="staff@example.com",
password="test-pass-123",
)
csrf_client.force_login(user)
with self.captureOnCommitCallbacks(execute=True):
response = csrf_client.post(
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
{
"name": "Maria Rossi",
"email": "maria@example.com",
"party_size": 2,
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data["status"], Reservation.Status.PENDING)
def test_reservation_creation_schedules_email_after_commit(self): def test_reservation_creation_schedules_email_after_commit(self):
with self.captureOnCommitCallbacks(execute=False) as callbacks: with self.captureOnCommitCallbacks(execute=False) as callbacks:
response = self.client.post( response = self.client.post(

View File

@@ -1,6 +1,7 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, throttle_classes from rest_framework.decorators import api_view, authentication_classes, permission_classes, throttle_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle from rest_framework.throttling import AnonRateThrottle
@@ -35,6 +36,8 @@ class ReservationConfirmThrottle(AnonRateThrottle):
@api_view(["POST"]) @api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
@throttle_classes([ReservationCreateThrottle]) @throttle_classes([ReservationCreateThrottle])
def create_reservation(request, performance_id): def create_reservation(request, performance_id):
get_object_or_404(Performance, pk=performance_id, show__is_published=True) get_object_or_404(Performance, pk=performance_id, show__is_published=True)