from datetime import timedelta from django.contrib.auth import get_user_model from django.test import TestCase from django.utils import timezone from bookings.models import Reservation, ReservationToken from checkins.models import CheckIn from checkins.services import ( AlreadyCheckedIn, InvalidToken, MissingStaffUser, ReservationNotConfirmed, confirm_check_in_from_token, preview_check_in_token, ) from shows.models import Performance, Show, Venue class CheckInServiceTests(TestCase): def setUp(self): self.show = Show.objects.create( title="Open Stage", slug="open-stage-checkins", is_published=True, ) self.venue = Venue.objects.create( name="AzioneLab Theatre", slug="azionelab-theatre-checkins", 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", password="test", is_staff=True, ) def test_successful_preview_returns_minimum_admission_data(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) preview = preview_check_in_token(raw_token, staff_user=self.staff_user) self.assertEqual(preview.reservation_id, reservation.id) self.assertEqual(preview.performance_id, self.performance.id) self.assertEqual(preview.show_title, self.show.title) self.assertEqual(preview.venue_name, self.venue.name) self.assertEqual(preview.party_size, reservation.party_size) self.assertFalse(preview.checked_in) self.assertFalse(hasattr(preview, "name")) self.assertFalse(hasattr(preview, "email")) self.assertFalse(hasattr(preview, "phone")) def test_preview_fails_for_invalid_token(self): with self.assertRaises(InvalidToken): preview_check_in_token("invalid-token", staff_user=self.staff_user) def test_preview_rejects_confirmation_token_even_for_confirmed_reservation(self): reservation = self.create_reservation() _, raw_token = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) with self.assertRaises(InvalidToken): preview_check_in_token(raw_token, staff_user=self.staff_user) def test_check_in_succeeds_for_confirmed_reservation(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) result = confirm_check_in_from_token(raw_token, staff_user=self.staff_user) self.assertEqual(result.check_in.reservation, reservation) self.assertEqual(result.check_in.checked_in_by, self.staff_user) self.assertEqual(result.check_in.source, CheckIn.Source.QR_SCAN) self.assertTrue(result.preview.checked_in) 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) with self.assertRaises(ReservationNotConfirmed): confirm_check_in_from_token(raw_token, staff_user=self.staff_user) 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) confirm_check_in_from_token(raw_token, staff_user=self.staff_user) with self.assertRaises(AlreadyCheckedIn): confirm_check_in_from_token(raw_token, staff_user=self.staff_user) self.assertEqual(CheckIn.objects.filter(reservation=reservation).count(), 1) def test_check_in_stores_timestamp_and_staff_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) before_check_in = timezone.now() result = confirm_check_in_from_token( raw_token, staff_user=self.staff_user, source=CheckIn.Source.MANUAL, ) self.assertGreaterEqual(result.check_in.checked_in_at, before_check_in) self.assertEqual(result.check_in.checked_in_by, self.staff_user) self.assertEqual(result.check_in.source, CheckIn.Source.MANUAL) def test_check_in_requires_staff_user(self): reservation = self.create_reservation() _, raw_token = self.create_check_in_token(reservation) with self.assertRaises(MissingStaffUser): confirm_check_in_from_token(raw_token, staff_user=None) def test_check_in_rejects_confirmation_token_even_for_confirmed_reservation(self): reservation = self.create_reservation() _, raw_token = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) with self.assertRaises(InvalidToken): confirm_check_in_from_token(raw_token, staff_user=self.staff_user) 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), )