generated from bisco/codex-bootstrap
feat: add booking REST API
This commit is contained in:
153
backend/bookings/test_api.py
Normal file
153
backend/bookings/test_api.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from bookings.models import Reservation
|
||||
from bookings.services import generate_confirmation_token
|
||||
from shows.models import Performance, Show, Venue
|
||||
|
||||
|
||||
class BookingApiTests(APITestCase):
|
||||
def setUp(self):
|
||||
self.show = Show.objects.create(
|
||||
title="Open Stage",
|
||||
slug="open-stage-api",
|
||||
summary="A contemporary theatre performance.",
|
||||
description="Full public show description.",
|
||||
is_published=True,
|
||||
)
|
||||
self.hidden_show = Show.objects.create(
|
||||
title="Hidden Stage",
|
||||
slug="hidden-stage-api",
|
||||
is_published=False,
|
||||
)
|
||||
self.venue = Venue.objects.create(
|
||||
name="AzioneLab Theatre",
|
||||
slug="azionelab-theatre-api",
|
||||
address="Via Example 1",
|
||||
city="Rome",
|
||||
)
|
||||
self.performance = Performance.objects.create(
|
||||
show=self.show,
|
||||
venue=self.venue,
|
||||
starts_at=timezone.now() + timedelta(days=7),
|
||||
room_capacity=3,
|
||||
)
|
||||
Performance.objects.create(
|
||||
show=self.hidden_show,
|
||||
venue=self.venue,
|
||||
starts_at=timezone.now() + timedelta(days=8),
|
||||
room_capacity=5,
|
||||
)
|
||||
|
||||
def test_show_list_returns_published_shows(self):
|
||||
response = self.client.get(reverse("api-show-list"))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data["results"]), 1)
|
||||
self.assertEqual(response.data["results"][0]["slug"], self.show.slug)
|
||||
|
||||
def test_show_detail_returns_public_performances(self):
|
||||
response = self.client.get(reverse("api-show-detail", kwargs={"slug": self.show.slug}))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["slug"], self.show.slug)
|
||||
self.assertEqual(len(response.data["performances"]), 1)
|
||||
self.assertEqual(response.data["performances"][0]["id"], self.performance.id)
|
||||
self.assertEqual(response.data["performances"][0]["available_seats"], 3)
|
||||
|
||||
def test_reservation_creation_success(self):
|
||||
response = self.client.post(
|
||||
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
|
||||
{
|
||||
"name": "Maria Rossi",
|
||||
"email": "maria@example.com",
|
||||
"phone": "+390600000000",
|
||||
"party_size": 2,
|
||||
"notes": "Front row if possible.",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data["status"], Reservation.Status.PENDING)
|
||||
self.assertEqual(response.data["performance"], self.performance.id)
|
||||
self.assertNotIn("token", response.data)
|
||||
self.assertNotIn("email", response.data)
|
||||
self.assertEqual(Reservation.objects.count(), 1)
|
||||
|
||||
def test_reservation_creation_with_insufficient_seats(self):
|
||||
response = self.client.post(
|
||||
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
|
||||
{
|
||||
"name": "Maria Rossi",
|
||||
"email": "maria@example.com",
|
||||
"party_size": 4,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
|
||||
self.assertEqual(response.data["status"], "booking_unavailable")
|
||||
self.assertEqual(Reservation.objects.count(), 0)
|
||||
|
||||
def test_confirmation_success(self):
|
||||
reservation = self.create_reservation()
|
||||
_, raw_token = generate_confirmation_token(reservation)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("api-reservation-confirm"),
|
||||
{"token": raw_token},
|
||||
format="json",
|
||||
)
|
||||
|
||||
reservation.refresh_from_db()
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["reservation_id"], reservation.id)
|
||||
self.assertEqual(response.data["status"], Reservation.Status.CONFIRMED)
|
||||
self.assertEqual(response.data["party_size"], reservation.party_size)
|
||||
self.assertEqual(response.data["qr_code_url"], f"/api/reservations/{reservation.id}/qr-code/")
|
||||
self.assertNotIn("token", response.data)
|
||||
self.assertEqual(reservation.status, Reservation.Status.CONFIRMED)
|
||||
|
||||
def test_confirmation_with_invalid_token(self):
|
||||
response = self.client.post(
|
||||
reverse("api-reservation-confirm"),
|
||||
{"token": "invalid-token"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
self.assertEqual(response.data["status"], "invalid_token")
|
||||
|
||||
def test_duplicate_confirmation_behavior(self):
|
||||
reservation = self.create_reservation()
|
||||
_, raw_token = generate_confirmation_token(reservation)
|
||||
first_response = self.client.post(
|
||||
reverse("api-reservation-confirm"),
|
||||
{"token": raw_token},
|
||||
format="json",
|
||||
)
|
||||
|
||||
second_response = self.client.post(
|
||||
reverse("api-reservation-confirm"),
|
||||
{"token": raw_token},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(first_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(second_response.status_code, status.HTTP_409_CONFLICT)
|
||||
self.assertEqual(second_response.data["status"], "already_confirmed")
|
||||
|
||||
def create_reservation(self, **overrides):
|
||||
data = {
|
||||
"performance": self.performance,
|
||||
"name": "Maria Rossi",
|
||||
"email": "maria@example.com",
|
||||
"party_size": 1,
|
||||
}
|
||||
data.update(overrides)
|
||||
return Reservation.objects.create(**data)
|
||||
Reference in New Issue
Block a user