From bccba482b4ad5ec7c51c10f58064513120998398 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 5 Mar 2020 09:43:17 +0100
Subject: [PATCH 1/2] api: can not cancel, accept or suspend a secondary
 booking (#40039)

---
 chrono/api/views.py | 38 +++++++++++++++----
 tests/test_api.py   | 90 +++++++++++++++++++++++++++++++++------------
 2 files changed, 97 insertions(+), 31 deletions(-)

diff --git a/chrono/api/views.py b/chrono/api/views.py
index 66035e5..6dc6613 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -731,7 +731,8 @@ class CancelBooking(APIView):
     '''
     Cancel a booking.
 
-    It will return an error (code 1) if the booking was already cancelled.
+    It will return error codes if the booking was cancelled before (code 1) or
+    if the booking is not primary (code 2).
     '''
 
     permission_classes = (permissions.IsAuthenticated,)
@@ -745,6 +746,13 @@ class CancelBooking(APIView):
                 'err_desc': _('already cancelled'),
             }
             return Response(response)
+        if booking.primary_booking is not None:
+            response = {
+                'err': 2,
+                'err_class': 'secondary booking',
+                'err_desc': _('secondary booking'),
+            }
+            return Response(response)
         booking.cancel()
         response = {'err': 0, 'booking_id': booking.id}
         return Response(response)
@@ -757,8 +765,9 @@ class AcceptBooking(APIView):
     '''
     Accept a booking currently in the waiting list.
 
-    It will return error codes if the booking was cancelled before (code 1) and
-    if the booking was not in waiting list (code 2).
+    It will return error codes if the booking was cancelled before (code 1),
+    if the booking is not primary (code 2) or
+    if the booking was not in waiting list (code 3).
     '''
 
     permission_classes = (permissions.IsAuthenticated,)
@@ -772,9 +781,16 @@ class AcceptBooking(APIView):
                 'err_desc': _('booking is cancelled'),
             }
             return Response(response)
-        if not booking.in_waiting_list:
+        if booking.primary_booking is not None:
             response = {
                 'err': 2,
+                'err_class': 'secondary booking',
+                'err_desc': _('secondary booking'),
+            }
+            return Response(response)
+        if not booking.in_waiting_list:
+            response = {
+                'err': 3,
                 'err_class': 'booking is not in waiting list',
                 'err_desc': _('booking is not in waiting list'),
             }
@@ -791,8 +807,9 @@ class SuspendBooking(APIView):
     '''
     Suspend a accepted booking.
 
-    It will return error codes if the booking was cancelled before (code 1) and
-    if the bookingis already in waiting list (code 2).
+    It will return error codes if the booking was cancelled before (code 1)
+    if the booking is not primary (code 2) or
+    if the booking is already in waiting list (code 3).
     '''
 
     permission_classes = (permissions.IsAuthenticated,)
@@ -806,9 +823,16 @@ class SuspendBooking(APIView):
                 'err_desc': _('booking is cancelled'),
             }
             return Response(response)
-        if booking.in_waiting_list:
+        if booking.primary_booking is not None:
             response = {
                 'err': 2,
+                'err_class': 'secondary booking',
+                'err_desc': _('secondary booking'),
+            }
+            return Response(response)
+        if booking.in_waiting_list:
+            response = {
+                'err': 3,
                 'err_class': 'booking is already in waiting list',
                 'err_desc': _('booking is already in waiting list'),
             }
diff --git a/tests/test_api.py b/tests/test_api.py
index 5505030..c8c3a91 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1313,6 +1313,27 @@ def test_waiting_list_booking(app, some_data, user):
     assert resp.json['err_desc'] == 'sold out'
 
 
+def test_cancel_booking(app, some_data, user):
+    agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].pk
+    event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
+    primary = Booking.objects.create(event=event)
+    secondary = Booking.objects.create(event=event, primary_booking=primary)
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    resp = app.post('/api/booking/%s/cancel/' % secondary.pk)
+    assert resp.json['err'] == 2
+    assert resp.json['reason'] == 'secondary booking'  # legacy
+    assert resp.json['err_class'] == 'secondary booking'
+    assert resp.json['err_desc'] == 'secondary booking'
+
+    resp = app.post('/api/booking/%s/cancel/' % primary.pk)
+    assert resp.json['err'] == 0
+    primary.refresh_from_db()
+    secondary.refresh_from_db()
+    assert primary.cancellation_datetime is not None
+    assert secondary.cancellation_datetime is not None
+
+
 def test_accept_booking(app, some_data, user):
     agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
     event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
@@ -1320,30 +1341,40 @@ def test_accept_booking(app, some_data, user):
     event.save()
 
     # create a booking on the waiting list
-    booking = Booking(event=event, in_waiting_list=True)
-    booking.save()
+    primary = Booking.objects.create(event=event, in_waiting_list=True)
+    secondary = Booking.objects.create(event=event, in_waiting_list=True, primary_booking=primary)
 
-    assert Booking.objects.filter(in_waiting_list=True).count() == 1
+    assert Booking.objects.filter(in_waiting_list=True).count() == 2
 
     app.authorization = ('Basic', ('john.doe', 'password'))
-    resp = app.post('/api/booking/%s/accept/' % booking.id)
+
+    resp = app.post('/api/booking/%s/accept/' % secondary.id)
+    assert resp.json['err'] == 2
+    assert resp.json['reason'] == 'secondary booking'  # legacy
+    assert resp.json['err_class'] == 'secondary booking'
+    assert resp.json['err_desc'] == 'secondary booking'
+
+    resp = app.post('/api/booking/%s/accept/' % primary.id)
     assert Booking.objects.filter(in_waiting_list=True).count() == 0
-    assert Booking.objects.filter(in_waiting_list=False).count() == 1
+    assert Booking.objects.filter(in_waiting_list=False).count() == 2
+    primary.refresh_from_db()
+    secondary.refresh_from_db()
+    assert primary.in_waiting_list is False
+    assert secondary.in_waiting_list is False
 
     # accept a booking that doesn't exist
-    resp = app.post('/api/booking/%s/accept/' % 0, status=404)
+    resp = app.post('/api/booking/0/accept/', status=404)
 
     # accept a booking that was not in the waiting list
-    resp = app.post('/api/booking/%s/accept/' % booking.id, status=200)
-    assert resp.json['err'] == 2
+    resp = app.post('/api/booking/%s/accept/' % primary.id, status=200)
+    assert resp.json['err'] == 3
 
     # accept a booking that was cancelled before
-    booking = Booking.objects.get(id=booking.id)
-    booking.in_waiting_list = True
-    booking.cancel()
-    resp = app.post('/api/booking/%s/accept/' % booking.id, status=200)
+    primary.suspend()
+    primary.cancel()
+    resp = app.post('/api/booking/%s/accept/' % primary.id, status=200)
     assert resp.json['err'] == 1
-    assert Booking.objects.filter(in_waiting_list=True).count() == 1
+    assert Booking.objects.filter(in_waiting_list=True).count() == 2
     assert Booking.objects.filter(in_waiting_list=False).count() == 0
 
 
@@ -1354,27 +1385,38 @@ def test_suspend_booking(app, some_data, user):
     event.save()
 
     # create a booking not on the waiting list
-    booking = Booking.objects.create(event=event, in_waiting_list=False)
-    assert booking.in_waiting_list is False
+    primary = Booking.objects.create(event=event, in_waiting_list=False)
+    secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary)
+
+    assert Booking.objects.filter(in_waiting_list=False).count() == 2
 
     app.authorization = ('Basic', ('john.doe', 'password'))
-    resp = app.post('/api/booking/%s/suspend/' % booking.pk)
-    booking.refresh_from_db()
-    assert booking.in_waiting_list is True
+
+    resp = app.post('/api/booking/%s/suspend/' % secondary.id)
+    assert resp.json['err'] == 2
+    assert resp.json['reason'] == 'secondary booking'  # legacy
+    assert resp.json['err_class'] == 'secondary booking'
+    assert resp.json['err_desc'] == 'secondary booking'
+
+    resp = app.post('/api/booking/%s/suspend/' % primary.pk)
+    primary.refresh_from_db()
+    secondary.refresh_from_db()
+    assert primary.in_waiting_list is True
+    assert secondary.in_waiting_list is True
 
     # suspend a booking that doesn't exist
     resp = app.post('/api/booking/0/suspend/', status=404)
 
     # suspend a booking that is in the waiting list
-    resp = app.post('/api/booking/%s/suspend/' % booking.pk, status=200)
-    assert resp.json['err'] == 2
+    resp = app.post('/api/booking/%s/suspend/' % primary.pk, status=200)
+    assert resp.json['err'] == 3
 
     # suspend a booking that was cancelled before
-    booking.in_waiting_list = False
-    booking.cancel()
-    resp = app.post('/api/booking/%s/suspend/' % booking.pk, status=200)
+    primary.accept()
+    primary.cancel()
+    resp = app.post('/api/booking/%s/suspend/' % primary.pk, status=200)
     assert resp.json['err'] == 1
-    assert booking.in_waiting_list is False
+    assert primary.in_waiting_list is False
 
 
 def test_multiple_booking_api(app, some_data, user):
-- 
2.20.1

