from datetime import timedelta from django.contrib.auth import get_user_model 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, ReservationToken from checkins.models import CheckIn from shows.models import Performance, Show, Venue class CheckInApiTests(APITestCase): def setUp(self): self.show = Show.objects.create( title="Open Stage", slug="open-stage-checkin-api", is_published=True, ) self.venue = Venue.objects.create( name="AzioneLab Theatre", slug="azionelab-theatre-checkin-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=20, ) self.staff_user = get_user_model().objects.create_user( username="staff-api", password="test", is_staff=True, ) self.regular_user = get_user_model().objects.create_user( username="regular-api", password="test", is_staff=False, ) def test_preview_success_as_staff_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-preview"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["status"], "valid") self.assertEqual(response.data["reservation_id"], reservation.id) self.assertEqual(response.data["performance_id"], self.performance.id) self.assertEqual(response.data["show_title"], self.show.title) self.assertEqual(response.data["venue_name"], self.venue.name) self.assertEqual(response.data["party_size"], reservation.party_size) self.assertNotIn("name", response.data) self.assertNotIn("email", response.data) self.assertNotIn("phone", response.data) def test_preview_denied_for_anonymous_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) response = self.client.post( reverse("api-check-in-preview"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_preview_fails_for_invalid_token(self): self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-preview"), {"token": "invalid-token"}, format="json", ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.data["status"], "invalid_token") def test_preview_rejects_confirmation_token(self): reservation = self.create_reservation() _, raw_token = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-preview"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.data["status"], "invalid_token") def test_check_in_success_as_staff_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) check_in = CheckIn.objects.get(reservation=reservation) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["status"], "checked_in") self.assertEqual(response.data["reservation_id"], reservation.id) self.assertEqual(response.data["performance_id"], self.performance.id) self.assertEqual(response.data["party_size"], reservation.party_size) self.assertEqual(response.data["checked_in_by"], self.staff_user.id) self.assertIsNotNone(response.data["checked_in_at"]) self.assertEqual(check_in.checked_in_by, self.staff_user) def test_check_in_denied_for_anonymous_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertFalse(CheckIn.objects.filter(reservation=reservation).exists()) def test_check_in_denied_for_non_staff_authenticated_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) self.client.force_authenticate(user=self.regular_user) response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertFalse(CheckIn.objects.filter(reservation=reservation).exists()) def test_check_in_fails_for_pending_reservation(self): reservation = self.create_reservation(status=Reservation.Status.PENDING, confirmed_at=None) _, raw_token = self.create_check_in_token(reservation) self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_409_CONFLICT) self.assertEqual(response.data["status"], "reservation_not_confirmed") self.assertFalse(CheckIn.objects.filter(reservation=reservation).exists()) def test_duplicate_check_in_fails(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) self.client.force_authenticate(user=self.staff_user) first_response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) second_response = self.client.post( reverse("api-check-in-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_checked_in") self.assertEqual(CheckIn.objects.filter(reservation=reservation).count(), 1) def test_check_in_rejects_confirmation_token(self): reservation = self.create_reservation() _, raw_token = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) self.client.force_authenticate(user=self.staff_user) response = self.client.post( reverse("api-check-in-confirm"), {"token": raw_token}, format="json", ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.data["status"], "invalid_token") self.assertFalse(CheckIn.objects.filter(reservation=reservation).exists()) def create_reservation(self, **overrides): data = { "performance": self.performance, "name": "Maria Rossi", "email": "maria@example.com", "party_size": 2, "status": Reservation.Status.CONFIRMED, "confirmed_at": timezone.now(), } data.update(overrides) return Reservation.objects.create(**data) def create_check_in_token(self, reservation): return ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CHECK_IN, expires_at=self.performance.starts_at + timedelta(days=1), )