Projet

Général

Profil

0001-api-fix-resize-endpoint-44739.patch

Lauréline Guérin, 21 juillet 2020 10:34

Télécharger (20,9 ko)

Voir les différences:

Subject: [PATCH] api: fix resize endpoint (#44739)

 chrono/agendas/models.py |   3 +-
 chrono/api/views.py      |  80 ++++++++----
 tests/test_api.py        | 256 +++++++++++++++++++++++++++------------
 3 files changed, 234 insertions(+), 105 deletions(-)
chrono/agendas/models.py
966 966
        ics.add(vevent)
967 967
        return ics.serialize()
968 968

  
969
    def clone(self, in_waiting_list=False, primary_booking=None, save=True):
969
    def clone(self, primary_booking=None, save=True):
970 970
        new_booking = copy.deepcopy(self)
971 971
        new_booking.id = None
972
        new_booking.in_waiting_list = in_waiting_list
973 972
        new_booking.primary_booking = primary_booking
974 973
        if save:
975 974
            new_booking.save()
chrono/api/views.py
1247 1247
            }
1248 1248
            return Response(response)
1249 1249
        event_ids = set([event.pk])
1250
        in_waiting_list = set([booking.in_waiting_list])
1250 1251
        secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime')
1251 1252
        for secondary in secondary_bookings:
1252 1253
            event_ids.add(secondary.event_id)
1254
            in_waiting_list.add(secondary.in_waiting_list)
1253 1255
        if len(event_ids) > 1:
1254 1256
            response = {
1255 1257
                'err': 4,
......
1257 1259
                'err_desc': _('can not resize multi event booking'),
1258 1260
            }
1259 1261
            return Response(response)
1262
        if len(in_waiting_list) > 1:
1263
            response = {
1264
                'err': 5,
1265
                'err_class': 'can not resize booking: waiting list inconsistency',
1266
                'err_desc': _('can not resize booking: waiting list inconsistency'),
1267
            }
1268
            return Response(response)
1260 1269

  
1261
        count = payload['count']
1262
        booked_places = event.waiting_list_places and event.waiting_list or event.booked_places
1263
        if booked_places < count:
1264
            if (
1265
                event.waiting_list
1266
                and count > event.waiting_list_places
1267
                or not event.waiting_list
1268
                and count > event.places
1269
            ):
1270
        # total places for the event (in waiting or main list, depending on the primary booking location)
1271
        places = booking.in_waiting_list and event.waiting_list_places or event.places
1272
        # total booked places for the event (in waiting or main list, depending on the primary booking location)
1273
        booked_places = booking.in_waiting_list and event.waiting_list or event.booked_places
1274

  
1275
        # places to book for this primary booking
1276
        primary_wanted_places = payload['count']
1277
        # already booked places for this primary booking
1278
        primary_booked_places = 1 + len(secondary_bookings)
1279

  
1280
        if primary_booked_places > primary_wanted_places:
1281
            # it is always ok to decrease booking
1282
            return self.decrease(booking, secondary_bookings, primary_booked_places, primary_wanted_places)
1283
        if primary_booked_places == primary_wanted_places:
1284
            # it is always ok to do nothing
1285
            return self.success(booking)
1286

  
1287
        # else, increase places if allowed
1288
        if booked_places - primary_booked_places + primary_wanted_places > places:
1289
            # oversized request
1290
            if booking.in_waiting_list:
1291
                # booking in waiting list: can not be overbooked
1292
                response = {
1293
                    'err': 3,
1294
                    'err_class': 'sold out',
1295
                    'err_desc': _('sold out'),
1296
                }
1297
                return Response(response)
1298
            if event.booked_places <= event.places:
1299
                # in main list and no overbooking for the moment: can not be overbooked
1270 1300
                response = {
1271 1301
                    'err': 3,
1272 1302
                    'err_class': 'sold out',
1273 1303
                    'err_desc': _('sold out'),
1274 1304
                }
1275 1305
                return Response(response)
1306
        return self.increase(booking, secondary_bookings, primary_booked_places, primary_wanted_places)
1276 1307

  
1308
    def increase(self, booking, secondary_bookings, primary_booked_places, primary_wanted_places):
1277 1309
        with transaction.atomic():
1278
            if booked_places > count:
1279
                # decrease places
1280
                for secondary in secondary_bookings[: booked_places - count]:
1281
                    secondary.delete()
1282
            elif booked_places < count:
1283
                # increase places
1284
                bulk_bookings = []
1285
                for i in range(0, count - booked_places):
1286
                    bulk_bookings.append(
1287
                        booking.clone(
1288
                            in_waiting_list=bool(event.waiting_list_places and event.waiting_list),
1289
                            primary_booking=booking,
1290
                            save=False,
1291
                        )
1292
                    )
1293
                Booking.objects.bulk_create(bulk_bookings)
1310
            bulk_bookings = []
1311
            for i in range(0, primary_wanted_places - primary_booked_places):
1312
                bulk_bookings.append(booking.clone(primary_booking=booking, save=False,))
1313
            Booking.objects.bulk_create(bulk_bookings)
1314

  
1315
        return self.success(booking)
1316

  
1317
    def decrease(self, booking, secondary_bookings, primary_booked_places, primary_wanted_places):
1318
        with transaction.atomic():
1319
            for secondary in secondary_bookings[: primary_booked_places - primary_wanted_places]:
1320
                secondary.delete()
1321

  
1322
        return self.success(booking)
1294 1323

  
1324
    def success(self, booking):
1295 1325
        response = {'err': 0, 'booking_id': booking.pk}
1296 1326
        return Response(response)
1297 1327

  
tests/test_api.py
2009 2009
    assert primary.in_waiting_list is False
2010 2010

  
2011 2011

  
2012
def test_resize_booking(app, some_data, user):
2013
    agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
2014
    event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
2015
    event.places = 5
2016
    event.waiting_list_places = 5
2017
    event.save()
2018
    event2 = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[1]
2012
def test_resize_booking_invalid_payload(app, user):
2013
    app.authorization = ('Basic', ('john.doe', 'password'))
2014
    # decrease a booking to 0
2015
    resp = app.post('/api/booking/0/resize/', params={'count': 0}, status=400)
2016
    assert resp.json['err'] == 1
2017
    assert resp.json['err_desc'] == 'invalid payload'
2019 2018

  
2020
    # create a booking not on the waiting list
2021
    primary = Booking.objects.create(event=event, in_waiting_list=False)
2022
    secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary)
2023 2019

  
2024
    assert Booking.objects.filter(in_waiting_list=False).count() == 2
2020
def test_resize_booking_not_found(app, user):
2021
    app.authorization = ('Basic', ('john.doe', 'password'))
2022
    app.post('/api/booking/0/resize/', params={'count': 42}, status=404)
2023

  
2025 2024

  
2025
def test_resize_booking_not_primary(app, user):
2026
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2027
    event = Event.objects.create(
2028
        slug='event', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2029
    )
2030
    primary = Booking.objects.create(event=event)
2031
    secondary = Booking.objects.create(event=event, primary_booking=primary)
2026 2032
    app.authorization = ('Basic', ('john.doe', 'password'))
2027 2033

  
2034
    # resize a booking that is not primary
2028 2035
    resp = app.post('/api/booking/%s/resize/' % secondary.pk, params={'count': 42})
2029 2036
    assert resp.json['err'] == 2
2030
    assert resp.json['reason'] == 'secondary booking'  # legacy
2031
    assert resp.json['err_class'] == 'secondary booking'
2032 2037
    assert resp.json['err_desc'] == 'secondary booking'
2033 2038

  
2034
    # resize a booking that doesn't exist
2035
    resp = app.post('/api/booking/0/resize/', params={'count': 42}, status=404)
2036 2039

  
2037
    # decrease a booking to 0
2038
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 0}, status=400)
2040
def test_resize_booking_cancelled(app, user):
2041
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2042
    event = Event.objects.create(
2043
        slug='event', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2044
    )
2045
    primary = Booking.objects.create(event=event)
2046
    primary.cancel()
2047

  
2048
    # resize a booking that was cancelled before
2049
    app.authorization = ('Basic', ('john.doe', 'password'))
2050
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
2039 2051
    assert resp.json['err'] == 1
2052
    assert resp.json['err_desc'] == 'booking is cancelled'
2040 2053

  
2041
    # decrease a booking not in waiting list
2042
    assert Booking.objects.filter(in_waiting_list=False).count() == 2
2043
    assert Booking.objects.filter().count() == 2
2044
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2045
    assert resp.json['err'] == 0
2046
    assert Booking.objects.filter(in_waiting_list=False).count() == 1
2047
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2048
    assert Booking.objects.filter().count() == 1
2049 2054

  
2050
    # no changes
2051
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2052
    assert resp.json['err'] == 0
2053
    assert Booking.objects.filter(in_waiting_list=False).count() == 1
2054
    assert Booking.objects.filter().count() == 1
2055
def test_resize_booking_multi_events(app, user):
2056
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2057
    event1 = Event.objects.create(
2058
        slug='event1', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2059
    )
2060
    event2 = Event.objects.create(
2061
        slug='event2', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2062
    )
2063
    primary = Booking.objects.create(event=event1, in_waiting_list=False)
2064
    Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary)
2065
    app.authorization = ('Basic', ('john.doe', 'password'))
2066

  
2067
    # resize a booking that is on multi events
2068
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
2069
    assert resp.json['err'] == 4
2070
    assert resp.json['err_desc'] == 'can not resize multi event booking'
2071

  
2072

  
2073
def test_resize_booking_mixed_waiting_list(app, user):
2074
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2075
    event = Event.objects.create(
2076
        slug='event', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2077
    )
2078
    primary = Booking.objects.create(event=event, in_waiting_list=False)
2079
    Booking.objects.create(event=event, primary_booking=primary, in_waiting_list=True)
2080
    app.authorization = ('Basic', ('john.doe', 'password'))
2081

  
2082
    # booking is in main list and waiting list in the same time
2083
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
2084
    assert resp.json['err'] == 5
2085
    assert resp.json['err_desc'] == 'can not resize booking: waiting list inconsistency'
2086

  
2055 2087

  
2056
    # increase a booking not in waiting list
2088
def test_resize_booking(app, user):
2089
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2090
    event = Event.objects.create(
2091
        slug='event', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2092
    )
2093
    primary = Booking.objects.create(event=event, in_waiting_list=False)
2094
    # there is other bookings
2095
    Booking.objects.create(event=event, in_waiting_list=False)
2096
    app.authorization = ('Basic', ('john.doe', 'password'))
2097

  
2098
    # increase
2057 2099
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
2058 2100
    assert resp.json['err'] == 0
2059
    assert Booking.objects.filter(in_waiting_list=False).count() == 2
2060
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2061
    assert Booking.objects.filter().count() == 2
2062
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
2101
    assert Booking.objects.filter(in_waiting_list=False).count() == 3
2102
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2103
    assert Booking.objects.count() == 3
2104
    assert primary.secondary_booking_set.count() == 1
2105
    # too much
2106
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2063 2107
    assert resp.json['err'] == 3
2064
    assert resp.json['reason'] == 'sold out'  # legacy
2065
    assert resp.json['err_class'] == 'sold out'
2066 2108
    assert resp.json['err_desc'] == 'sold out'
2067
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2109
    # max
2110
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
2068 2111
    assert resp.json['err'] == 0
2069 2112
    assert Booking.objects.filter(in_waiting_list=False).count() == 5
2070
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2071
    assert Booking.objects.filter().count() == 5
2072
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
2073
    assert resp.json['err'] == 3
2074
    assert resp.json['reason'] == 'sold out'  # legacy
2075
    assert resp.json['err_class'] == 'sold out'
2076
    assert resp.json['err_desc'] == 'sold out'
2113
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2114
    assert Booking.objects.count() == 5
2115
    assert primary.secondary_booking_set.count() == 3
2077 2116

  
2078
    # decrease a booking in waiting list
2079
    primary.suspend()
2080
    assert Booking.objects.filter(in_waiting_list=True).count() == 5
2081
    assert Booking.objects.filter().count() == 5
2117
    # booking is overbooked - it is ok to increase
2118
    event.places = 4
2119
    event.save()
2120
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2121
    assert resp.json['err'] == 0
2122
    assert Booking.objects.filter(in_waiting_list=False).count() == 6
2123
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2124
    assert Booking.objects.count() == 6
2125
    assert primary.secondary_booking_set.count() == 4
2126

  
2127
    # decrease
2128
    # booking is overbooked, but it is always ok to do nothing
2129
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2130
    assert resp.json['err'] == 0
2131
    assert Booking.objects.filter(in_waiting_list=False).count() == 6
2132
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2133
    assert Booking.objects.count() == 6
2134
    assert primary.secondary_booking_set.count() == 4
2135
    # or to decrease
2136
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
2137
    assert resp.json['err'] == 0
2138
    assert Booking.objects.filter(in_waiting_list=False).count() == 5
2139
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2140
    assert Booking.objects.count() == 5
2141
    assert primary.secondary_booking_set.count() == 3
2142
    # again
2082 2143
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2083 2144
    assert resp.json['err'] == 0
2084
    assert Booking.objects.filter(in_waiting_list=True).count() == 1
2085
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2086
    assert Booking.objects.filter().count() == 1
2145
    assert Booking.objects.filter(in_waiting_list=False).count() == 2
2146
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2147
    assert Booking.objects.count() == 2
2148
    assert primary.secondary_booking_set.count() == 0
2087 2149

  
2088 2150
    # no changes
2089 2151
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2090 2152
    assert resp.json['err'] == 0
2091
    assert Booking.objects.filter(in_waiting_list=True).count() == 1
2092
    assert Booking.objects.filter().count() == 1
2153
    assert Booking.objects.filter(in_waiting_list=False).count() == 2
2154
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2155
    assert Booking.objects.count() == 2
2156
    assert primary.secondary_booking_set.count() == 0
2157

  
2158

  
2159
def test_resize_booking_in_waiting_list(app, user):
2160
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
2161
    event = Event.objects.create(
2162
        slug='event', start_datetime=now(), places=5, waiting_list_places=5, agenda=agenda,
2163
    )
2164
    primary = Booking.objects.create(event=event, in_waiting_list=True)
2165
    # there is other bookings
2166
    Booking.objects.create(event=event, in_waiting_list=True)
2167
    app.authorization = ('Basic', ('john.doe', 'password'))
2093 2168

  
2094
    # increase a booking in waiting list
2169
    # increase
2095 2170
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
2096 2171
    assert resp.json['err'] == 0
2097
    assert Booking.objects.filter(in_waiting_list=True).count() == 2
2098
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2099
    assert Booking.objects.filter().count() == 2
2100
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
2172
    assert Booking.objects.filter(in_waiting_list=True).count() == 3
2173
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2174
    assert Booking.objects.count() == 3
2175
    assert primary.secondary_booking_set.count() == 1
2176
    # too much
2177
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2101 2178
    assert resp.json['err'] == 3
2102
    assert resp.json['reason'] == 'sold out'  # legacy
2103
    assert resp.json['err_class'] == 'sold out'
2104 2179
    assert resp.json['err_desc'] == 'sold out'
2105
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2180
    # max
2181
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
2106 2182
    assert resp.json['err'] == 0
2107 2183
    assert Booking.objects.filter(in_waiting_list=True).count() == 5
2108
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
2109
    assert Booking.objects.filter().count() == 5
2110
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
2184
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2185
    assert Booking.objects.count() == 5
2186
    assert primary.secondary_booking_set.count() == 3
2187

  
2188
    # booking is overbooked (on waiting list, that shoud not happen)
2189
    event.waiting_list_places = 4
2190
    event.save()
2191
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
2111 2192
    assert resp.json['err'] == 3
2112
    assert resp.json['reason'] == 'sold out'  # legacy
2113
    assert resp.json['err_class'] == 'sold out'
2114 2193
    assert resp.json['err_desc'] == 'sold out'
2115 2194

  
2116
    # resize a booking that is on multi events
2117
    secondary = Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary)
2118
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
2119
    assert resp.json['err'] == 4
2120
    assert resp.json['reason'] == 'can not resize multi event booking'  # legacy
2121
    assert resp.json['err_class'] == 'can not resize multi event booking'
2122
    assert resp.json['err_desc'] == 'can not resize multi event booking'
2123
    # resize a booking that was cancelled before
2124
    primary.cancel()
2125
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
2126
    assert resp.json['err'] == 1
2195
    # decrease
2196
    # booking is overbooked, but it is always ok to do nothing
2197
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
2198
    assert resp.json['err'] == 0
2199
    assert Booking.objects.filter(in_waiting_list=True).count() == 5
2200
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2201
    assert Booking.objects.count() == 5
2202
    assert primary.secondary_booking_set.count() == 3
2203
    # or to decrease
2204
    event.waiting_list_places = 3
2205
    event.save()
2206
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 3})
2207
    assert resp.json['err'] == 0
2208
    assert Booking.objects.filter(in_waiting_list=True).count() == 4
2209
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2210
    assert Booking.objects.count() == 4
2211
    assert primary.secondary_booking_set.count() == 2
2212
    # again
2213
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2214
    assert resp.json['err'] == 0
2215
    assert Booking.objects.filter(in_waiting_list=True).count() == 2
2216
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2217
    assert Booking.objects.count() == 2
2218
    assert primary.secondary_booking_set.count() == 0
2219

  
2220
    # no changes
2221
    resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
2222
    assert resp.json['err'] == 0
2223
    assert Booking.objects.filter(in_waiting_list=True).count() == 2
2224
    assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
2225
    assert Booking.objects.count() == 2
2226
    assert primary.secondary_booking_set.count() == 0
2127 2227

  
2128 2228

  
2129 2229
def test_multiple_booking_api(app, some_data, user):
2130
-