Projet

Général

Profil

0014-api-endpoint-to-get-pricing-data-for-flat-fee-schedu.patch

Lauréline Guérin, 29 juillet 2022 22:03

Télécharger (32,8 ko)

Voir les différences:

Subject: [PATCH 14/14] api: endpoint to get pricing data for flat fee schedule
 mode (#67675)

 lingo/api/serializers.py  | 216 +++++++++++++---
 tests/api/test_pricing.py | 504 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 673 insertions(+), 47 deletions(-)
lingo/api/serializers.py
36 36

  
37 37
class PricingComputeSerializer(serializers.Serializer):
38 38
    slots = CommaSeparatedStringField(
39
        required=True, child=serializers.CharField(max_length=160, allow_blank=False)
39
        required=False, child=serializers.CharField(max_length=160, allow_blank=False)
40 40
    )
41
    agenda = serializers.SlugField(required=False, allow_blank=False, max_length=160)
42
    agenda_pricing = serializers.SlugField(required=False, allow_blank=False, max_length=160)
43
    start_date = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d'])
41 44
    user_external_id = serializers.CharField(required=True, max_length=250)
42 45
    adult_external_id = serializers.CharField(required=True, max_length=250)
43 46

  
44
    agenda_slugs = []
45
    agendas = {}
46
    serialized_events = {}
47
    event_subscriptions = {}
47
    def __init__(self, *args, **kwargs):
48
        super().__init__(*args, **kwargs)
49

  
50
        self._agenda_slugs = []
51
        self._agendas = {}
52
        self._serialized_events = {}
53
        self._event_subscriptions = {}
54
        self._agenda = None
55
        self._agenda_subscription = None
56
        self._agenda_pricing = None
57
        self._billing_date = None
48 58

  
49 59
    def validate_slots(self, value):
50
        self.agendas = {a.slug: a for a in Agenda.objects.all()}
51
        allowed_agenda_slugs = self.agendas.keys()
60
        self._agendas = {a.slug: a for a in Agenda.objects.all()}
61
        allowed_agenda_slugs = self._agendas.keys()
52 62
        agenda_slugs = set()
53 63
        for slot in value:
54 64
            try:
......
64 74
        if extra_agendas:
65 75
            extra_agendas = ', '.join(sorted(extra_agendas))
66 76
            raise ValidationError(_('Unknown agendas: %s') % extra_agendas)
67
        self.agenda_slugs = sorted(agenda_slugs)
77
        self._agenda_slugs = sorted(agenda_slugs)
68 78

  
69 79
        try:
70 80
            serialized_events = get_events(value)
......
73 83
        else:
74 84
            for serialized_event in serialized_events:
75 85
                event_slug = '%s@%s' % (serialized_event['agenda'], serialized_event['slug'])
76
                self.serialized_events[event_slug] = serialized_event
86
                self._serialized_events[event_slug] = serialized_event
77 87

  
78 88
        return value
79 89

  
80
    def get_subscriptions(self, user_external_id):
90
    def _validate_agenda(self, value, start_date):
91
        try:
92
            self._agenda = Agenda.objects.get(slug=value)
93
            try:
94
                self._agenda_pricing = AgendaPricing.get_agenda_pricing(
95
                    agenda=self._agenda,
96
                    start_date=start_date.date(),
97
                    flat_fee_schedule=True,
98
                )
99
                if not self._agenda_pricing.subscription_required:
100
                    self._agenda_pricing = None
101
            except AgendaPricingNotFound:
102
                self._agenda_pricing = None
103
        except Agenda.DoesNotExist:
104
            raise ValidationError({'agenda': _('Unknown agenda: %s') % value})
105
        return self._agenda
106

  
107
    def _validate_agenda_pricing(self, value, start_date):
108
        try:
109
            self._agenda_pricing = AgendaPricing.objects.get(
110
                slug=value,
111
                flat_fee_schedule=True,
112
                subscription_required=False,
113
                date_start__lte=start_date.date(),
114
                date_end__gt=start_date.date(),
115
            )
116
        except AgendaPricing.DoesNotExist:
117
            raise ValidationError({'agenda_pricing': _('Unknown pricing: %s') % value})
118
        return self._agenda_pricing
119

  
120
    def validate(self, attrs):
121
        super().validate(attrs)
122
        if 'slots' not in attrs and 'agenda' not in attrs and 'agenda_pricing' not in attrs:
123
            raise ValidationError(_('Either "slots", "agenda" or "agenda_pricing" parameter is required.'))
124
        if 'agenda' in attrs:
125
            # flat_fee_schedule mode + subscription_required True
126
            if 'start_date' not in attrs:
127
                raise ValidationError(
128
                    {'start_date': _('This field is required when using "agenda" parameter.')}
129
                )
130
            self._validate_agenda(attrs['agenda'], attrs['start_date'])
131
        if 'agenda_pricing' in attrs:
132
            # flat_fee_schedule mode + subscription_required False
133
            if 'start_date' not in attrs:
134
                raise ValidationError(
135
                    {'start_date': _('This field is required when using "agenda_pricing" parameter.')}
136
                )
137
            self._validate_agenda_pricing(attrs['agenda_pricing'], attrs['start_date'])
138
        if attrs.get('start_date'):
139
            # flat_fee_schedule mode: get billing_date from start_date param
140
            self.get_billing_date(attrs['start_date'])
141
        if attrs.get('user_external_id') and not attrs.get('agenda_pricing'):
142
            # don't check subscriptions if agenda_pricing in params (== subscription_required is False)
143
            self.get_subscriptions(attrs['user_external_id'], attrs.get('start_date'))
144
        return attrs
145

  
146
    def get_billing_date(self, start_date):
147
        if self._agenda_pricing:
148
            self._billing_date = (
149
                self._agenda_pricing.billingdates.filter(date_start__lte=start_date)
150
                .order_by('date_start')
151
                .last()
152
            )
153
            if not self._billing_date:
154
                self._billing_date = self._agenda_pricing.billingdates.order_by('date_start').first()
155

  
156
    def get_subscriptions(self, user_external_id, start_date):
157
        if self._serialized_events:
158
            # event mode
159
            self.get_subscriptions_for_events(user_external_id)
160
        elif self._agenda and self._agenda_pricing:
161
            # flat_fee_schedule mode
162
            self.get_subscriptions_for_agenda(user_external_id, start_date)
163

  
164
    def _get_subscription(self, subscriptions, start_date, end_date):
165
        # get subscription matching start_date & end_date
166
        for subscription in subscriptions:
167
            sub_start_date = datetime.date.fromisoformat(subscription['date_start'])
168
            sub_end_date = datetime.date.fromisoformat(subscription['date_end'])
169
            if sub_start_date >= end_date:
170
                continue
171
            if sub_end_date <= start_date:
172
                continue
173
            return subscription
174

  
175
    def get_subscriptions_for_events(self, user_external_id):
81 176
        agenda_subscriptions = {}
82
        for agenda_slug in self.agenda_slugs:
177
        # get subscription list for each agenda
178
        for agenda_slug in self._agenda_slugs:
83 179
            try:
84 180
                agenda_subscriptions[agenda_slug] = get_subscriptions(agenda_slug, user_external_id)
85 181
            except ChronoError as e:
86 182
                raise ValidationError({'user_external_id': e})
87 183

  
88
        self.event_subscriptions = {}
89
        for event_slug, serialized_event in self.serialized_events.items():
184
        # for each event, get a matching subscription
185
        for event_slug, serialized_event in self._serialized_events.items():
90 186
            start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
91 187
            end_date = start_date + datetime.timedelta(days=1)
92 188
            agenda_slug = serialized_event['agenda']
93
            event_subscription = None
94
            for subscription in agenda_subscriptions[agenda_slug]:
95
                sub_start_date = datetime.date.fromisoformat(subscription['date_start'])
96
                sub_end_date = datetime.date.fromisoformat(subscription['date_end'])
97
                if sub_start_date >= end_date:
98
                    continue
99
                if sub_end_date <= start_date:
100
                    continue
101
                event_subscription = subscription
102
                break
189
            event_subscription = self._get_subscription(
190
                agenda_subscriptions[agenda_slug], start_date, end_date
191
            )
103 192
            if event_subscription is None:
104 193
                raise ValidationError(
105 194
                    {'user_external_id': _('No subscription found for event %s') % event_slug}
106 195
                )
107
            self.event_subscriptions[event_slug] = event_subscription
196
            self._event_subscriptions[event_slug] = event_subscription
108 197

  
109
    def validate(self, attrs):
110
        super().validate(attrs)
111
        if attrs.get('user_external_id') and self.serialized_events:
112
            self.get_subscriptions(attrs['user_external_id'])
113
        return attrs
198
    def get_subscriptions_for_agenda(self, user_external_id, start_date):
199
        # get subscription list for this agenda
200
        try:
201
            subscriptions = get_subscriptions(self._agenda.slug, user_external_id)
202
        except ChronoError as e:
203
            raise ValidationError({'user_external_id': e})
204

  
205
        # get correct period, from billing_date or agenda_pricing dates
206
        if not self._agenda_pricing.billingdates.exists():
207
            start_date = self._agenda_pricing.date_start
208
            end_date = self._agenda_pricing.date_end
209
        else:
210
            start_date = self._billing_date.date_start
211
            next_billing_date = self._agenda_pricing.billingdates.filter(date_start__gt=start_date).first()
212
            end_date = next_billing_date.date_start if next_billing_date else self._agenda_pricing.date_end
213

  
214
        # get a matching subscriptions
215
        self._agenda_subscription = self._get_subscription(subscriptions, start_date, end_date)
216
        if not self._agenda_subscription:
217
            raise ValidationError(
218
                {'user_external_id': _('No subscription found for agenda %s') % self._agenda.slug}
219
            )
114 220

  
115 221
    def compute(self, request):
116
        if not self.serialized_events or not self.event_subscriptions:
117
            return
222
        try:
223
            if not self.validated_data.get('slots'):
224
                return self.compute_for_flat_fee_schedule(request)
225
            return self.compute_for_event(request)
226
        except PricingError as e:
227
            return {
228
                'error': type(e),
229
                'error_details': e.details,
230
            }
231

  
232
    def compute_for_event(self, request):
118 233
        result = []
119
        event_slugs = sorted(self.serialized_events.keys())
234
        event_slugs = sorted(self._serialized_events.keys())
120 235
        for event_slug in event_slugs:
121
            serialized_event = self.serialized_events[event_slug]
236
            serialized_event = self._serialized_events[event_slug]
122 237
            start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
123
            agenda = self.agendas[serialized_event['agenda']]
238
            agenda = self._agendas[serialized_event['agenda']]
124 239
            try:
125
                agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
240
                agenda_pricing = AgendaPricing.get_agenda_pricing(
241
                    agenda=agenda, start_date=start_date, flat_fee_schedule=False
242
                )
126 243
                pricing_data = agenda_pricing.get_pricing_data_for_event(
127 244
                    request=request,
128 245
                    agenda=agenda,
129 246
                    event=serialized_event,
130
                    subscription=self.event_subscriptions[event_slug],
247
                    subscription=self._event_subscriptions[event_slug],
131 248
                    check_status={
132 249
                        'status': 'presence',
133 250
                        'check_type': None,
......
151 268

  
152 269
        result = sorted(result, key=lambda d: d['event'])
153 270
        return result
271

  
272
    def compute_for_flat_fee_schedule(self, request):
273
        result = {}
274
        if self._agenda:
275
            result['agenda'] = self._agenda.slug
276
            if not self._agenda_pricing:
277
                result['error'] = _('No agenda pricing found for agenda %s') % self._agenda.slug
278
                return result
279
        else:
280
            result['agenda_pricing'] = self._agenda_pricing.slug
281

  
282
        try:
283
            pricing_data = self._agenda_pricing.get_pricing_data(
284
                request=request,
285
                pricing_date=(
286
                    self._billing_date.date_start if self._billing_date else self._agenda_pricing.date_start
287
                ),
288
                subscription=self._agenda_subscription,
289
                user_external_id=self.validated_data['user_external_id'],
290
                adult_external_id=self.validated_data['adult_external_id'],
291
            )
292
            result['pricing_data'] = pricing_data
293
            return result
294
        except PricingError as e:
295
            result['error'] = type(e).__name__
296
            result['error_details'] = e.details
297
            return result
tests/api/test_pricing.py
13 13
def test_pricing_compute_params(app, user):
14 14
    app.authorization = ('Basic', ('john.doe', 'password'))
15 15

  
16
    # missing slots
16
    Agenda.objects.create(label='Foo')
17
    pricing = Pricing.objects.create(label='Foo bar')
18
    AgendaPricing.objects.create(
19
        label='Foo',
20
        pricing=pricing,
21
        date_start=datetime.date(year=2021, month=9, day=1),
22
        date_end=datetime.date(year=2021, month=10, day=1),
23
        flat_fee_schedule=True,
24
    )
25

  
26
    # missing slots, agenda, agenda_pricing
17 27
    resp = app.get(
18 28
        '/api/pricing/compute/',
19 29
        params={'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
......
21 31
    )
22 32
    assert resp.json['err'] == 1
23 33
    assert resp.json['err_desc'] == 'invalid payload'
24
    assert resp.json['errors']['slots'] == ['This field is required.']
34
    assert resp.json['errors'] == {
35
        'non_field_errors': ['Either "slots", "agenda" or "agenda_pricing" parameter is required.']
36
    }
25 37

  
26
    # missing user_external_id
38
    # missing start_date
27 39
    resp = app.get(
28 40
        '/api/pricing/compute/',
29
        params={'slots': 'foo@foo', 'adult_external_id': 'adult:1'},
41
        params={'agenda': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
30 42
        status=400,
31 43
    )
32 44
    assert resp.json['err'] == 1
33 45
    assert resp.json['err_desc'] == 'invalid payload'
34
    assert resp.json['errors']['user_external_id'] == ['This field is required.']
35

  
36
    # missing adult_external_id
46
    assert resp.json['errors']['start_date'] == ['This field is required when using "agenda" parameter.']
37 47
    resp = app.get(
38 48
        '/api/pricing/compute/',
39
        params={'slots': 'foo@foo', 'user_external_id': 'user:1'},
49
        params={'agenda_pricing': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
40 50
        status=400,
41 51
    )
42 52
    assert resp.json['err'] == 1
43 53
    assert resp.json['err_desc'] == 'invalid payload'
44
    assert resp.json['errors']['adult_external_id'] == ['This field is required.']
54
    assert resp.json['errors']['start_date'] == [
55
        'This field is required when using "agenda_pricing" parameter.'
56
    ]
57

  
58
    params = [
59
        {'slots': 'foo@foo'},
60
        {'agenda': 'foo', 'start_date': '2021-09-01'},
61
        {'agenda_pricing': 'foo', 'start_date': '2021-09-01'},
62
    ]
63
    for param in params:
64
        # missing user_external_id
65
        _param = param.copy()
66
        _param.update({'adult_external_id': 'adult:1'})
67
        resp = app.get(
68
            '/api/pricing/compute/',
69
            params=_param,
70
            status=400,
71
        )
72
        assert resp.json['err'] == 1
73
        assert resp.json['err_desc'] == 'invalid payload'
74
        assert resp.json['errors']['user_external_id'] == ['This field is required.']
75

  
76
        # missing adult_external_id
77
        _param = param.copy()
78
        _param.update({'user_external_id': 'user:1'})
79
        resp = app.get(
80
            '/api/pricing/compute/',
81
            params=_param,
82
            status=400,
83
        )
84
        assert resp.json['err'] == 1
85
        assert resp.json['err_desc'] == 'invalid payload'
86
        assert resp.json['errors']['adult_external_id'] == ['This field is required.']
45 87

  
46 88

  
47 89
def test_pricing_compute_slots(app, user):
......
100 142
    assert resp.json['errors']['slots'] == ['Unknown agendas: agenda, agenda2']
101 143

  
102 144

  
145
def test_pricing_compute_agenda(app, user):
146
    app.authorization = ('Basic', ('john.doe', 'password'))
147

  
148
    # unknown agenda
149
    resp = app.get(
150
        '/api/pricing/compute/',
151
        params={
152
            'agenda': 'agenda',
153
            'start_date': '2021-09-01',
154
            'user_external_id': 'user:1',
155
            'adult_external_id': 'adult:1',
156
        },
157
        status=400,
158
    )
159
    assert resp.json['err'] == 1
160
    assert resp.json['err_desc'] == 'invalid payload'
161
    assert resp.json['errors']['agenda'] == ['Unknown agenda: agenda']
162

  
163

  
164
def test_pricing_compute_agenda_pricing(app, user):
165
    app.authorization = ('Basic', ('john.doe', 'password'))
166

  
167
    def test():
168
        resp = app.get(
169
            '/api/pricing/compute/',
170
            params={
171
                'agenda_pricing': 'baz',
172
                'start_date': '2021-09-01',
173
                'user_external_id': 'user:1',
174
                'adult_external_id': 'adult:1',
175
            },
176
            status=400,
177
        )
178
        assert resp.json['err'] == 1
179
        assert resp.json['err_desc'] == 'invalid payload'
180
        assert resp.json['errors']['agenda_pricing'] == ['Unknown pricing: baz']
181

  
182
    # unknown agenda_pricing
183
    test()
184

  
185
    # bad dates
186
    pricing = Pricing.objects.create(label='Foo bar')
187
    agenda_pricing = AgendaPricing.objects.create(
188
        label='Baz',
189
        pricing=pricing,
190
        date_start=datetime.date(year=2021, month=8, day=1),
191
        date_end=datetime.date(year=2021, month=9, day=1),
192
        flat_fee_schedule=True,
193
        subscription_required=False,
194
    )
195
    test()
196
    agenda_pricing.date_start = datetime.date(year=2021, month=9, day=3)
197
    agenda_pricing.date_end = datetime.date(year=2021, month=10, day=1)
198
    agenda_pricing.save()
199
    test()
200

  
201
    # wrong flat_fee_schedule value
202
    agenda_pricing.flat_fee_schedule = False
203
    agenda_pricing.save()
204
    test()
205

  
206
    # wrong subscription_required value
207
    agenda_pricing.flat_fee_schedule = True
208
    agenda_pricing.subscription_required = True
209
    agenda_pricing.save()
210
    test()
211

  
212

  
103 213
@mock.patch('lingo.api.serializers.get_events')
104 214
def test_pricing_compute_events_error(mock_events, app, user):
105 215
    Agenda.objects.create(label='Agenda')
......
147 257
@mock.patch('lingo.api.serializers.get_events')
148 258
@mock.patch('lingo.api.serializers.get_subscriptions')
149 259
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
150
def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
260
def test_pricing_compute_for_event(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
151 261
    agenda = Agenda.objects.create(label='Agenda')
152 262
    agenda2 = Agenda.objects.create(label='Agenda2')
153 263
    app.authorization = ('Basic', ('john.doe', 'password'))
......
277 387
        },
278 388
    ]
279 389

  
280
    # ok
281 390
    pricing = Pricing.objects.create(label='Foo bar')
282 391
    agenda_pricing = AgendaPricing.objects.create(
283 392
        pricing=pricing,
284 393
        date_start=datetime.date(year=2021, month=9, day=1),
285 394
        date_end=datetime.date(year=2021, month=10, day=1),
395
        flat_fee_schedule=True,  # wrong config
286 396
    )
287 397
    agenda_pricing.agendas.add(agenda, agenda2)
288 398
    resp = app.get(
......
293 403
            'adult_external_id': 'adult:1',
294 404
        },
295 405
    )
406
    assert resp.json['data'] == [
407
        {
408
            'event': 'agenda2@event-baz-slug',
409
            'error': 'No agenda pricing found for event agenda2@event-baz-slug',
410
        },
411
        {
412
            'event': 'agenda@event-bar-slug',
413
            'error': 'No agenda pricing found for event agenda@event-bar-slug',
414
        },
415
    ]
416

  
417
    # ok
418
    agenda_pricing.flat_fee_schedule = False
419
    agenda_pricing.save()
420
    resp = app.get(
421
        '/api/pricing/compute/',
422
        params={
423
            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
424
            'user_external_id': 'user:1',
425
            'adult_external_id': 'adult:1',
426
        },
427
    )
296 428
    assert resp.json['data'] == [
297 429
        {'event': 'agenda2@event-baz-slug', 'pricing_data': {'foo': 'baz'}},
298 430
        {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
......
345 477
        {'event': 'agenda2@event-baz-slug', 'error': {'foo': 'error'}},
346 478
        {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
347 479
    ]
480

  
481

  
482
@mock.patch('lingo.api.serializers.get_subscriptions')
483
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
484
def test_pricing_compute_for_flat_fee_schedule_with_subscription(
485
    mock_pricing_data, mock_subscriptions, app, user
486
):
487
    agenda = Agenda.objects.create(label='Foo bar')
488
    pricing = Pricing.objects.create(label='Foo bar')
489
    agenda_pricing = AgendaPricing.objects.create(
490
        label='Foo bar pricing',
491
        pricing=pricing,
492
        date_start=datetime.date(year=2021, month=9, day=1),
493
        date_end=datetime.date(year=2021, month=10, day=1),
494
        flat_fee_schedule=True,
495
        subscription_required=True,
496
    )
497
    agenda_pricing.agendas.add(agenda)
498
    app.authorization = ('Basic', ('john.doe', 'password'))
499

  
500
    # no subscription
501
    mock_subscriptions.return_value = []
502
    resp = app.get(
503
        '/api/pricing/compute/',
504
        params={
505
            'agenda': 'foo-bar',
506
            'start_date': '2021-09-01',
507
            'user_external_id': 'user:1',
508
            'adult_external_id': 'adult:1',
509
        },
510
        status=400,
511
    )
512
    assert resp.json['err'] == 1
513
    assert resp.json['err_desc'] == 'invalid payload'
514
    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
515
    assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')]
516

  
517
    # no matching subscription
518
    mock_subscriptions.reset_mock()
519
    mock_subscriptions.return_value = [
520
        {
521
            'date_start': '2021-08-01',
522
            'date_end': '2021-09-01',
523
        },
524
        {
525
            'date_start': '2021-10-01',
526
            'date_end': '2021-11-01',
527
        },
528
    ]
529
    resp = app.get(
530
        '/api/pricing/compute/',
531
        params={
532
            'agenda': 'foo-bar',
533
            'start_date': '2021-09-02',
534
            'user_external_id': 'user:1',
535
            'adult_external_id': 'adult:1',
536
        },
537
        status=400,
538
    )
539
    assert resp.json['err'] == 1
540
    assert resp.json['err_desc'] == 'invalid payload'
541
    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
542
    assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')]
543

  
544
    # no agenda_pricing found
545
    agenda_pricing.delete()
546
    agenda_pricing = AgendaPricing.objects.create(
547
        pricing=pricing,
548
        # bad dates
549
        date_start=datetime.date(year=2021, month=8, day=1),
550
        date_end=datetime.date(year=2021, month=9, day=1),
551
    )
552
    agenda_pricing.agendas.add(agenda)
553
    agenda_pricing = AgendaPricing.objects.create(
554
        pricing=pricing,
555
        # bad dates
556
        date_start=datetime.date(year=2021, month=9, day=3),
557
        date_end=datetime.date(year=2021, month=10, day=1),
558
    )
559
    agenda_pricing.agendas.add(agenda)
560
    mock_subscriptions.return_value = [
561
        {
562
            'date_start': '2021-08-01',
563
            'date_end': '2021-09-01',
564
        },
565
        {
566
            'date_start': '2021-09-02',
567
            'date_end': '2021-09-03',
568
        },
569
        {
570
            'date_start': '2021-10-01',
571
            'date_end': '2021-11-01',
572
        },
573
    ]
574
    mock_pricing_data.return_value = {'foo': 'bar'}
575
    resp = app.get(
576
        '/api/pricing/compute/',
577
        params={
578
            'agenda': 'foo-bar',
579
            'start_date': '2021-09-02',
580
            'user_external_id': 'user:1',
581
            'adult_external_id': 'adult:1',
582
        },
583
    )
584
    assert resp.json['data'] == {
585
        'agenda': 'foo-bar',
586
        'error': 'No agenda pricing found for agenda foo-bar',
587
    }
588

  
589
    agenda_pricing.delete()
590
    agenda_pricing = AgendaPricing.objects.create(
591
        pricing=pricing,
592
        date_start=datetime.date(year=2021, month=9, day=1),
593
        date_end=datetime.date(year=2021, month=10, day=1),
594
        flat_fee_schedule=False,  # wrong config
595
        subscription_required=True,
596
    )
597
    agenda_pricing.agendas.add(agenda)
598
    resp = app.get(
599
        '/api/pricing/compute/',
600
        params={
601
            'agenda': 'foo-bar',
602
            'start_date': '2021-09-02',
603
            'user_external_id': 'user:1',
604
            'adult_external_id': 'adult:1',
605
        },
606
    )
607
    assert resp.json['data'] == {
608
        'agenda': 'foo-bar',
609
        'error': 'No agenda pricing found for agenda foo-bar',
610
    }
611

  
612
    agenda_pricing.flat_fee_schedule = True
613
    agenda_pricing.subscription_required = False  # wrong config
614
    agenda_pricing.save()
615
    resp = app.get(
616
        '/api/pricing/compute/',
617
        params={
618
            'agenda': 'foo-bar',
619
            'start_date': '2021-09-02',
620
            'user_external_id': 'user:1',
621
            'adult_external_id': 'adult:1',
622
        },
623
    )
624
    assert resp.json['data'] == {
625
        'agenda': 'foo-bar',
626
        'error': 'No agenda pricing found for agenda foo-bar',
627
    }
628

  
629
    # ok
630
    agenda_pricing.subscription_required = True
631
    agenda_pricing.save()
632
    resp = app.get(
633
        '/api/pricing/compute/',
634
        params={
635
            'agenda': 'foo-bar',
636
            'start_date': '2021-09-02',
637
            'user_external_id': 'user:1',
638
            'adult_external_id': 'adult:1',
639
        },
640
    )
641
    assert resp.json['data'] == {'agenda': 'foo-bar', 'pricing_data': {'foo': 'bar'}}
642
    assert mock_pricing_data.call_args_list == [
643
        mock.call(
644
            request=mock.ANY,
645
            pricing_date=datetime.date(2021, 9, 1),
646
            subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
647
            user_external_id='user:1',
648
            adult_external_id='adult:1',
649
        ),
650
    ]
651

  
652
    # get_pricing_data with error
653
    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
654
    resp = app.get(
655
        '/api/pricing/compute/',
656
        params={
657
            'agenda': 'foo-bar',
658
            'start_date': '2021-09-02',
659
            'user_external_id': 'user:1',
660
            'adult_external_id': 'adult:1',
661
        },
662
    )
663
    assert resp.json['data'] == {
664
        'agenda': 'foo-bar',
665
        'error': 'PricingError',
666
        'error_details': {'foo': 'error'},
667
    }
668

  
669
    # check with billing dates
670
    mock_pricing_data.return_value = {'foo': 'bar'}
671
    agenda_pricing.billingdates.create(
672
        date_start=datetime.date(2021, 9, 1),
673
        label='Foo 1',
674
    )
675
    agenda_pricing.billingdates.create(
676
        date_start=datetime.date(2021, 9, 15),
677
        label='Foo 2',
678
    )
679
    mock_subscriptions.return_value = [
680
        {
681
            'date_start': '2021-09-01',
682
            'date_end': '2021-09-15',
683
        },
684
    ]
685
    resp = app.get(
686
        '/api/pricing/compute/',
687
        params={
688
            'agenda': 'foo-bar',
689
            'start_date': '2021-09-16',
690
            'user_external_id': 'user:1',
691
            'adult_external_id': 'adult:1',
692
        },
693
        status=400,
694
    )
695
    assert resp.json['err'] == 1
696
    assert resp.json['err_desc'] == 'invalid payload'
697
    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
698
    mock_subscriptions.return_value = [
699
        {
700
            'date_start': '2021-09-15',
701
            'date_end': '2021-09-16',
702
        },
703
    ]
704
    mock_pricing_data.reset_mock()
705
    resp = app.get(
706
        '/api/pricing/compute/',
707
        params={
708
            'agenda': 'foo-bar',
709
            'start_date': '2021-09-16',
710
            'user_external_id': 'user:1',
711
            'adult_external_id': 'adult:1',
712
        },
713
    )
714
    assert mock_pricing_data.call_args_list == [
715
        mock.call(
716
            request=mock.ANY,
717
            pricing_date=datetime.date(2021, 9, 15),
718
            subscription={'date_start': '2021-09-15', 'date_end': '2021-09-16'},
719
            user_external_id='user:1',
720
            adult_external_id='adult:1',
721
        ),
722
    ]
723
    mock_subscriptions.return_value = [
724
        {
725
            'date_start': '2021-09-30',
726
            'date_end': '2021-10-01',
727
        },
728
    ]
729
    mock_pricing_data.reset_mock()
730
    resp = app.get(
731
        '/api/pricing/compute/',
732
        params={
733
            'agenda': 'foo-bar',
734
            'start_date': '2021-09-16',
735
            'user_external_id': 'user:1',
736
            'adult_external_id': 'adult:1',
737
        },
738
    )
739
    assert mock_pricing_data.call_args_list == [
740
        mock.call(
741
            request=mock.ANY,
742
            pricing_date=datetime.date(2021, 9, 15),
743
            subscription={'date_start': '2021-09-30', 'date_end': '2021-10-01'},
744
            user_external_id='user:1',
745
            adult_external_id='adult:1',
746
        ),
747
    ]
748

  
749

  
750
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
751
def test_pricing_compute_for_flat_fee_schedule_without_subscription(mock_pricing_data, app, user):
752
    pricing = Pricing.objects.create(label='Foo bar')
753
    agenda_pricing = AgendaPricing.objects.create(
754
        label='Foo bar pricing',
755
        pricing=pricing,
756
        date_start=datetime.date(year=2021, month=9, day=1),
757
        date_end=datetime.date(year=2021, month=10, day=1),
758
        flat_fee_schedule=True,
759
        subscription_required=False,
760
    )
761
    app.authorization = ('Basic', ('john.doe', 'password'))
762

  
763
    mock_pricing_data.return_value = {'foo': 'bar'}
764
    resp = app.get(
765
        '/api/pricing/compute/',
766
        params={
767
            'agenda_pricing': 'foo-bar-pricing',
768
            'start_date': '2021-09-02',
769
            'user_external_id': 'user:1',
770
            'adult_external_id': 'adult:1',
771
        },
772
    )
773
    assert resp.json['data'] == {'agenda_pricing': 'foo-bar-pricing', 'pricing_data': {'foo': 'bar'}}
774
    assert mock_pricing_data.call_args_list == [
775
        mock.call(
776
            request=mock.ANY,
777
            pricing_date=datetime.date(2021, 9, 1),
778
            subscription=None,
779
            user_external_id='user:1',
780
            adult_external_id='adult:1',
781
        ),
782
    ]
783

  
784
    # get_pricing_data with error
785
    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
786
    resp = app.get(
787
        '/api/pricing/compute/',
788
        params={
789
            'agenda_pricing': 'foo-bar-pricing',
790
            'start_date': '2021-09-02',
791
            'user_external_id': 'user:1',
792
            'adult_external_id': 'adult:1',
793
        },
794
    )
795
    assert resp.json['data'] == {
796
        'agenda_pricing': 'foo-bar-pricing',
797
        'error': 'PricingError',
798
        'error_details': {'foo': 'error'},
799
    }
800

  
801
    # check with billing dates
802
    mock_pricing_data.return_value = {'foo': 'bar'}
803
    agenda_pricing.billingdates.create(
804
        date_start=datetime.date(2021, 9, 1),
805
        label='Foo 1',
806
    )
807
    agenda_pricing.billingdates.create(
808
        date_start=datetime.date(2021, 9, 15),
809
        label='Foo 2',
810
    )
811
    mock_pricing_data.reset_mock()
812
    resp = app.get(
813
        '/api/pricing/compute/',
814
        params={
815
            'agenda_pricing': 'foo-bar-pricing',
816
            'start_date': '2021-09-16',
817
            'user_external_id': 'user:1',
818
            'adult_external_id': 'adult:1',
819
        },
820
    )
821
    assert mock_pricing_data.call_args_list == [
822
        mock.call(
823
            request=mock.ANY,
824
            pricing_date=datetime.date(2021, 9, 15),
825
            subscription=None,
826
            user_external_id='user:1',
827
            adult_external_id='adult:1',
828
        ),
829
    ]
348
-