from datetime import timedelta from django.contrib.auth import get_user_model from django.core import mail from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse from django.utils import timezone from bookings.models import Reservation, ReservationToken from checkins.models import CheckIn from shows.models import Performance, Show, Venue @override_settings( EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", SITE_BASE_URL="https://tickets.azionelab.example", ) class ReservationAdminTests(TestCase): def setUp(self): user_model = get_user_model() self.admin_user = user_model.objects.create_superuser( username="admin-bookings", email="admin@example.com", password="password123", ) self.client.force_login(self.admin_user) self.show = Show.objects.create( title="Open Stage", slug="open-stage-admin", is_published=True, ) self.venue = Venue.objects.create( name="AzioneLab Theatre", slug="azionelab-theatre-admin", 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, ) def test_reservation_add_page_accepts_preselected_performance(self): response = self.client.get( reverse("admin:bookings_reservation_add"), {"performance": self.performance.id}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, "Create manual reservation") self.assertContains(response, "The reservation stays pending") def test_admin_can_create_manual_reservation_with_standard_email_flow(self): with self.captureOnCommitCallbacks(execute=True): response = self.client.post( reverse("admin:bookings_reservation_add"), { "performance": self.performance.id, "name": "Maria Rossi", "email": "maria@example.com", "phone": "+390600000000", "party_size": 2, "notes": "Entered by staff at the venue desk.", "_save": "Save", }, ) reservation = Reservation.objects.get() self.assertEqual(response.status_code, 302) self.assertEqual(reservation.performance, self.performance) self.assertEqual(reservation.status, Reservation.Status.PENDING) self.assertEqual(reservation.party_size, 2) self.assertTrue( ReservationToken.objects.filter( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, ).exists() ) self.assertEqual(len(mail.outbox), 1) self.assertIn( "https://tickets.azionelab.example/api/reservations/confirm/?token=", mail.outbox[0].body, ) def test_token_hash_is_hidden_in_token_admin_views(self): reservation = Reservation.objects.create( performance=self.performance, name="Maria Rossi", email="maria@example.com", party_size=2, ) token, _ = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) changelist_response = self.client.get(reverse("admin:bookings_reservationtoken_changelist")) change_response = self.client.get( reverse("admin:bookings_reservationtoken_change", args=[token.id]), ) self.assertEqual(changelist_response.status_code, 200) self.assertEqual(change_response.status_code, 200) self.assertNotContains(changelist_response, token.token_hash) self.assertNotContains(change_response, token.token_hash) self.assertContains(change_response, token.get_purpose_display()) self.assertContains(change_response, "Expires at") self.assertContains(change_response, "Used at") def test_admin_can_confirm_pending_reservation_manually_and_get_qr_access(self): reservation = Reservation.objects.create( performance=self.performance, name="Maria Rossi", email="maria@example.com", party_size=2, ) confirmation_token, _ = ReservationToken.create_token( reservation=reservation, purpose=ReservationToken.Purpose.CONFIRMATION, expires_at=timezone.now() + timedelta(hours=2), ) response = self.client.post( reverse("admin:bookings_reservation_manual_confirm", args=[reservation.id]), ) reservation.refresh_from_db() confirmation_token.refresh_from_db() self.assertEqual(response.status_code, 200) self.assertEqual(reservation.status, Reservation.Status.CONFIRMED) self.assertIsNotNone(confirmation_token.used_at) self.assertContains(response, "Reservation confirmed manually") self.assertContains(response, "/api/check-ins/preview/?token=") self.assertTrue( ReservationToken.objects.filter( reservation=reservation, purpose=ReservationToken.Purpose.CHECK_IN, ).exists() ) def test_admin_can_mark_confirmed_reservation_as_checked_in(self): reservation = Reservation.objects.create( performance=self.performance, name="Checked Guest", email="checked@example.com", party_size=1, status=Reservation.Status.CONFIRMED, confirmed_at=timezone.now(), ) response = self.client.post( reverse("admin:bookings_reservation_manual_check_in", args=[reservation.id]), ) self.assertEqual(response.status_code, 302) check_in = CheckIn.objects.get(reservation=reservation) self.assertEqual(check_in.checked_in_by, self.admin_user) self.assertEqual(check_in.source, CheckIn.Source.MANUAL)