fix(events): keep registration status reads nonblocking

This commit is contained in:
2026-10-01 13:35:17 +03:30
parent 962238c32c
commit f924fcc52e
16 changed files with 498 additions and 107 deletions

View File

@@ -6,7 +6,7 @@ import pytest
from django.test import Client
from django.utils import timezone
from apps.events.models import Event, Registration
from apps.events.models import Event, Registration, RegistrationNotification
from apps.events.checkout import cancel_event_checkouts, expire_stale_registration_checkouts
from apps.payments.models import Payment
from apps.users.models import User
@@ -142,6 +142,23 @@ class TestRegistrationCheckout:
assert payload["payment"] is None
assert payload["can_register"] is True
def test_status_reads_do_not_cancel_an_expired_checkout_when_notifications_are_unavailable(self, mocker):
registration, _ = self.create_pending_checkout(expires_at=timezone.now() - timedelta(seconds=1))
mocker.patch(
"apps.events.tasks.send_registration_cancellation_notification.delay",
side_effect=RuntimeError("broker unavailable"),
)
hub = self.client.get(f"/api/events/{self.event.slug}/registration/status", **self.auth_headers())
legacy = self.client.get(f"/api/events/{self.event.id}/is-registered", **self.auth_headers())
registration.refresh_from_db()
assert hub.status_code == 200
assert hub.json()["state"] == "verification_pending"
assert legacy.status_code == 200
assert registration.status == Registration.StatusChoices.PENDING
assert not RegistrationNotification.objects.filter(registration=registration).exists()
def test_registration_hub_requires_authentication(self):
response = self.client.get(f"/api/events/{self.event.slug}/registration/status")
assert response.status_code == 401
@@ -376,6 +393,30 @@ class TestRegistrationCheckout:
assert payment.status == Payment.OrderStatusChoices.PENDING
assert Payment.objects.filter(event=self.event, user=self.user).count() == 1
def test_payment_creation_rechecks_window_after_expiring_checkout_without_payment(self):
registration = Registration.objects.create(
event=self.event,
user=self.user,
status=Registration.StatusChoices.PENDING,
checkout_expires_at=timezone.now() - timedelta(seconds=1),
final_price=self.event.price,
)
self.event.registration_end_date = timezone.now() - timedelta(minutes=1)
self.event.save(update_fields=["registration_end_date"])
response = self.client.post(
"/api/payments/create",
data=json.dumps({"event_id": self.event.id, "description": "Expired checkout",
"idempotency_key": str(uuid.uuid4())}),
content_type="application/json",
**self.auth_headers(),
)
registration.refresh_from_db()
assert response.status_code == 400
assert registration.status == Registration.StatusChoices.CANCELLED
assert not Payment.objects.filter(registration=registration).exists()
def test_expiry_sweep_cancels_only_new_expired_checkouts_and_releases_the_seat(self):
expired_at = timezone.now() - timedelta(seconds=1)
registration, payment = self.create_pending_checkout(expires_at=expired_at)
@@ -463,10 +504,11 @@ class TestRegistrationCheckout:
assert payment_response.status_code == 409
assert Registration.objects.filter(event=self.event, user=self.user).count() == 1
def test_event_cancellation_ends_checkouts_and_queues_payment_reconciliation(self, mocker):
def test_event_cancellation_ends_checkouts_and_recovers_payment_reconciliation(self, mocker):
registration, payment = self.create_pending_checkout()
from apps.payments.tasks import sweep_stale_payment_verifications
enqueue = mocker.patch("apps.payments.tasks.reconcile_payment.apply_async")
mocker.patch("apps.events.checkout.transaction.on_commit", side_effect=lambda callback: callback())
self.event.status = Event.StatusChoices.CANCELLED
self.event.save(update_fields=["status"])
@@ -477,4 +519,9 @@ class TestRegistrationCheckout:
assert registration.status == Registration.StatusChoices.CANCELLED
assert registration.cancellation_reason == Registration.CancellationReasonChoices.EVENT_CANCELLED
assert Event.objects.get(pk=self.event.pk).current_attendees_count == 0
enqueue.assert_called_once_with(args=(payment.id,), countdown=0)
assert RegistrationNotification.objects.filter(
registration=registration,
kind=RegistrationNotification.Kind.CANCELLED,
).exists()
assert sweep_stale_payment_verifications() == 1
enqueue.assert_called_once_with(args=(payment.id,))