generated from bisco/codex-bootstrap
fix: use absolute public booking URLs
This commit is contained in:
@@ -15,6 +15,7 @@ DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
|
|||||||
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8080
|
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8080
|
||||||
DJANGO_DEBUG=false
|
DJANGO_DEBUG=false
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:4200,http://localhost:8080
|
CORS_ALLOWED_ORIGINS=http://localhost:4200,http://localhost:8080
|
||||||
|
SITE_BASE_URL=http://localhost:8080
|
||||||
TIME_ZONE=Europe/Rome
|
TIME_ZONE=Europe/Rome
|
||||||
|
|
||||||
POSTGRES_DB=azionelab
|
POSTGRES_DB=azionelab
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def csv_env(name, default=""):
|
|||||||
ALLOWED_HOSTS = csv_env("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1")
|
ALLOWED_HOSTS = csv_env("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1")
|
||||||
CSRF_TRUSTED_ORIGINS = csv_env("DJANGO_CSRF_TRUSTED_ORIGINS")
|
CSRF_TRUSTED_ORIGINS = csv_env("DJANGO_CSRF_TRUSTED_ORIGINS")
|
||||||
CORS_ALLOWED_ORIGINS = csv_env("CORS_ALLOWED_ORIGINS")
|
CORS_ALLOWED_ORIGINS = csv_env("CORS_ALLOWED_ORIGINS")
|
||||||
|
SITE_BASE_URL = os.environ.get("SITE_BASE_URL", "http://localhost:8080").rstrip("/")
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ CONFIRMATION_PATH = "/api/reservations/confirm/"
|
|||||||
|
|
||||||
|
|
||||||
def build_confirmation_link(raw_confirmation_token):
|
def build_confirmation_link(raw_confirmation_token):
|
||||||
return f"{CONFIRMATION_PATH}?token={raw_confirmation_token}"
|
return f"{settings.SITE_BASE_URL}{CONFIRMATION_PATH}?token={raw_confirmation_token}"
|
||||||
|
|
||||||
|
|
||||||
def send_confirmation_email(*, reservation, raw_confirmation_token):
|
def send_confirmation_email(*, reservation, raw_confirmation_token):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import base64
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import qrcode
|
import qrcode
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from .models import Reservation
|
from .models import Reservation
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ CHECK_IN_PREVIEW_PATH = "/api/check-ins/preview/"
|
|||||||
|
|
||||||
|
|
||||||
def build_check_in_preview_url(raw_check_in_token):
|
def build_check_in_preview_url(raw_check_in_token):
|
||||||
return f"{CHECK_IN_PREVIEW_PATH}?token={raw_check_in_token}"
|
return f"{settings.SITE_BASE_URL}{CHECK_IN_PREVIEW_PATH}?token={raw_check_in_token}"
|
||||||
|
|
||||||
|
|
||||||
def generate_check_in_qr_png(raw_check_in_token):
|
def generate_check_in_qr_png(raw_check_in_token):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
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.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 APITestCase
|
||||||
@@ -60,6 +61,7 @@ class BookingApiTests(APITestCase):
|
|||||||
self.assertEqual(response.data["performances"][0]["id"], self.performance.id)
|
self.assertEqual(response.data["performances"][0]["id"], self.performance.id)
|
||||||
self.assertEqual(response.data["performances"][0]["available_seats"], 3)
|
self.assertEqual(response.data["performances"][0]["available_seats"], 3)
|
||||||
|
|
||||||
|
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
|
||||||
def test_reservation_creation_success(self):
|
def test_reservation_creation_success(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
|
reverse("api-reservation-create", kwargs={"performance_id": self.performance.id}),
|
||||||
@@ -80,7 +82,10 @@ class BookingApiTests(APITestCase):
|
|||||||
self.assertNotIn("email", response.data)
|
self.assertNotIn("email", response.data)
|
||||||
self.assertEqual(Reservation.objects.count(), 1)
|
self.assertEqual(Reservation.objects.count(), 1)
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertIn("/api/reservations/confirm/?token=", mail.outbox[0].body)
|
self.assertIn(
|
||||||
|
"https://tickets.azionelab.example/api/reservations/confirm/?token=",
|
||||||
|
mail.outbox[0].body,
|
||||||
|
)
|
||||||
|
|
||||||
def test_reservation_creation_with_insufficient_seats(self):
|
def test_reservation_creation_with_insufficient_seats(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@@ -97,6 +102,7 @@ class BookingApiTests(APITestCase):
|
|||||||
self.assertEqual(response.data["status"], "booking_unavailable")
|
self.assertEqual(response.data["status"], "booking_unavailable")
|
||||||
self.assertEqual(Reservation.objects.count(), 0)
|
self.assertEqual(Reservation.objects.count(), 0)
|
||||||
|
|
||||||
|
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
|
||||||
def test_confirmation_success(self):
|
def test_confirmation_success(self):
|
||||||
reservation = self.create_reservation()
|
reservation = self.create_reservation()
|
||||||
_, raw_token = generate_confirmation_token(reservation)
|
_, raw_token = generate_confirmation_token(reservation)
|
||||||
@@ -112,7 +118,11 @@ class BookingApiTests(APITestCase):
|
|||||||
self.assertEqual(response.data["reservation_id"], reservation.id)
|
self.assertEqual(response.data["reservation_id"], reservation.id)
|
||||||
self.assertEqual(response.data["status"], Reservation.Status.CONFIRMED)
|
self.assertEqual(response.data["status"], Reservation.Status.CONFIRMED)
|
||||||
self.assertEqual(response.data["party_size"], reservation.party_size)
|
self.assertEqual(response.data["party_size"], reservation.party_size)
|
||||||
self.assertTrue(response.data["qr_code_url"].startswith("/api/check-ins/preview/?token="))
|
self.assertTrue(
|
||||||
|
response.data["qr_code_url"].startswith(
|
||||||
|
"https://tickets.azionelab.example/api/check-ins/preview/?token="
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertNotIn("token", response.data)
|
self.assertNotIn("token", response.data)
|
||||||
self.assertTrue(response.data["qr_code_image"].startswith("data:image/png;base64,"))
|
self.assertTrue(response.data["qr_code_image"].startswith("data:image/png;base64,"))
|
||||||
self.assertEqual(reservation.status, Reservation.Status.CONFIRMED)
|
self.assertEqual(reservation.status, Reservation.Status.CONFIRMED)
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ class BookingServiceTests(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertNotIn("maria@example.com", result.raw_confirmation_token)
|
self.assertNotIn("maria@example.com", result.raw_confirmation_token)
|
||||||
|
|
||||||
@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
|
@override_settings(
|
||||||
|
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
|
||||||
|
SITE_BASE_URL="https://tickets.azionelab.example",
|
||||||
|
)
|
||||||
def test_create_pending_reservation_sends_confirmation_email(self):
|
def test_create_pending_reservation_sends_confirmation_email(self):
|
||||||
result = create_pending_reservation(
|
result = create_pending_reservation(
|
||||||
performance_id=self.performance.id,
|
performance_id=self.performance.id,
|
||||||
@@ -72,7 +75,10 @@ class BookingServiceTests(TestCase):
|
|||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].to, ["maria@example.com"])
|
self.assertEqual(mail.outbox[0].to, ["maria@example.com"])
|
||||||
self.assertIn(result.raw_confirmation_token, mail.outbox[0].body)
|
self.assertIn(result.raw_confirmation_token, mail.outbox[0].body)
|
||||||
self.assertIn("/api/reservations/confirm/?token=", mail.outbox[0].body)
|
self.assertIn(
|
||||||
|
"https://tickets.azionelab.example/api/reservations/confirm/?token=",
|
||||||
|
mail.outbox[0].body,
|
||||||
|
)
|
||||||
|
|
||||||
@patch("bookings.emailing.send_mail", side_effect=RuntimeError("SMTP down"))
|
@patch("bookings.emailing.send_mail", side_effect=RuntimeError("SMTP down"))
|
||||||
def test_create_pending_reservation_logs_email_failure_without_crashing(self, mocked_send_mail):
|
def test_create_pending_reservation_logs_email_failure_without_crashing(self, mocked_send_mail):
|
||||||
@@ -96,6 +102,7 @@ class BookingServiceTests(TestCase):
|
|||||||
self.assertEqual(token.token_hash, ReservationToken.hash_token(raw_token))
|
self.assertEqual(token.token_hash, ReservationToken.hash_token(raw_token))
|
||||||
self.assertGreater(token.expires_at, timezone.now())
|
self.assertGreater(token.expires_at, timezone.now())
|
||||||
|
|
||||||
|
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
|
||||||
def test_confirm_reservation_from_valid_token(self):
|
def test_confirm_reservation_from_valid_token(self):
|
||||||
reservation = self.create_reservation(party_size=2)
|
reservation = self.create_reservation(party_size=2)
|
||||||
token, raw_token = generate_confirmation_token(reservation)
|
token, raw_token = generate_confirmation_token(reservation)
|
||||||
@@ -118,8 +125,14 @@ class BookingServiceTests(TestCase):
|
|||||||
result.qr_code_url,
|
result.qr_code_url,
|
||||||
build_check_in_preview_url(result.raw_check_in_token),
|
build_check_in_preview_url(result.raw_check_in_token),
|
||||||
)
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
result.qr_code_url.startswith(
|
||||||
|
"https://tickets.azionelab.example/api/check-ins/preview/?token="
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertTrue(result.qr_code_image.startswith("data:image/png;base64,"))
|
self.assertTrue(result.qr_code_image.startswith("data:image/png;base64,"))
|
||||||
|
|
||||||
|
@override_settings(SITE_BASE_URL="https://tickets.azionelab.example")
|
||||||
def test_qr_code_is_generated_for_confirmed_reservation(self):
|
def test_qr_code_is_generated_for_confirmed_reservation(self):
|
||||||
reservation = self.create_reservation(
|
reservation = self.create_reservation(
|
||||||
status=Reservation.Status.CONFIRMED,
|
status=Reservation.Status.CONFIRMED,
|
||||||
@@ -134,6 +147,10 @@ class BookingServiceTests(TestCase):
|
|||||||
|
|
||||||
self.assertTrue(qr_code_image.startswith("data:image/png;base64,"))
|
self.assertTrue(qr_code_image.startswith("data:image/png;base64,"))
|
||||||
self.assertGreater(len(qr_code_image), len("data:image/png;base64,"))
|
self.assertGreater(len(qr_code_image), len("data:image/png;base64,"))
|
||||||
|
self.assertEqual(
|
||||||
|
build_check_in_preview_url(raw_check_in_token),
|
||||||
|
"https://tickets.azionelab.example/api/check-ins/preview/?token=opaque-check-in-token",
|
||||||
|
)
|
||||||
|
|
||||||
def test_qr_code_is_not_generated_for_pending_reservation(self):
|
def test_qr_code_is_not_generated_for_pending_reservation(self):
|
||||||
reservation = self.create_reservation()
|
reservation = self.create_reservation()
|
||||||
|
|||||||
Reference in New Issue
Block a user