Projet

Général

Profil

0002-api-endpoint-to-get-pricing-data-for-a-list-of-event.patch

Lauréline Guérin, 25 juillet 2022 09:35

Télécharger (25,6 ko)

Voir les différences:

Subject: [PATCH 2/2] api: endpoint to get pricing data for a list of events
 (#66354)

 lingo/agendas/chrono.py      |  23 ++-
 lingo/api/serializers.py     | 146 ++++++++++++++++
 lingo/api/urls.py            |   5 +
 lingo/api/views.py           |  20 ++-
 lingo/settings.py            |   1 +
 tests/agendas/test_chrono.py |  61 ++++++-
 tests/api/conftest.py        |  14 ++
 tests/api/test_pricing.py    | 314 +++++++++++++++++++++++++++++++++++
 8 files changed, 576 insertions(+), 8 deletions(-)
 create mode 100644 lingo/api/serializers.py
 create mode 100644 tests/api/conftest.py
 create mode 100644 tests/api/test_pricing.py
lingo/agendas/chrono.py
38 38
    return list(settings.KNOWN_SERVICES.get('chrono').values())[0]
39 39

  
40 40

  
41
def get_chrono_json(path, log_errors=True):
41
def get_chrono_json(path, params=None, log_errors=True):
42 42
    chrono_site = get_chrono_service()
43 43
    if chrono_site is None:
44 44
        return
45 45
    try:
46 46
        response = requests.get(
47 47
            path,
48
            params=params or {},
48 49
            remote_service=chrono_site,
49 50
            without_user=True,
50 51
            headers={'accept': 'application/json'},
......
111 112

  
112 113

  
113 114
def get_event(event_slug):
114
    result = get_chrono_json('api/agendas/events/?slots=%s' % event_slug)
115
    return get_events(
116
        [event_slug],
117
        error_message=_('Unable to get event details'),
118
        error_message_with_details=_('Unable to get event details (%s)'),
119
    )[0]
120

  
121

  
122
def get_events(event_slugs, error_message=None, error_message_with_details=None):
123
    error_message = error_message or _('Unable to get events details')
124
    error_message_with_details = error_message_with_details or _('Unable to get events details (%s)')
125
    result = get_chrono_json('api/agendas/events/', params={'slots': event_slugs})
115 126
    if not result:
116
        raise ChronoError(_('Unable to get event details'))
127
        raise ChronoError(error_message)
117 128
    if result.get('err'):
118
        raise ChronoError(_('Unable to get event details (%s)') % result['err_desc'])
129
        raise ChronoError(error_message_with_details % result['err_desc'])
119 130
    if not result.get('data'):
120
        raise ChronoError(_('Unable to get event details'))
121
    return result['data'][0]
131
        raise ChronoError(error_message)
132
    return result['data']
122 133

  
123 134

  
124 135
def get_subscriptions(agenda_slug, user_external_id):
lingo/api/serializers.py
1
# lingo - payment and billing system
2
# Copyright (C) 2022  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import datetime
18

  
19
from django.utils.translation import ugettext_lazy as _
20
from rest_framework import serializers
21
from rest_framework.exceptions import ValidationError
22

  
23
from lingo.agendas.chrono import ChronoError, get_events, get_subscriptions
24
from lingo.agendas.models import Agenda
25
from lingo.pricing.models import AgendaPricing, PricingError
26

  
27

  
28
class CommaSeparatedStringField(serializers.ListField):
29
    def get_value(self, dictionary):
30
        return super(serializers.ListField, self).get_value(dictionary)
31

  
32
    def to_internal_value(self, data):
33
        data = [s.strip() for s in data.split(',') if s.strip()]
34
        return super().to_internal_value(data)
35

  
36

  
37
class PricingComputeSerializer(serializers.Serializer):
38
    slots = CommaSeparatedStringField(
39
        required=True, child=serializers.CharField(max_length=160, allow_blank=False)
40
    )
41
    user_external_id = serializers.CharField(required=True, max_length=250)
42
    adult_external_id = serializers.CharField(required=True, max_length=250)
43

  
44
    agenda_slugs = []
45
    agendas = {}
46
    serialized_events = {}
47
    event_subscriptions = {}
48

  
49
    def validate_slots(self, value):
50
        self.agendas = {a.slug: a for a in Agenda.objects.all()}
51
        allowed_agenda_slugs = self.agendas.keys()
52
        agenda_slugs = set()
53
        for slot in value:
54
            try:
55
                agenda_slug, event_slug = slot.split('@')
56
            except ValueError:
57
                raise ValidationError(_('Invalid format for slot %s') % slot)
58
            if not agenda_slug:
59
                raise ValidationError(_('Missing agenda slug in slot %s') % slot)
60
            if not event_slug:
61
                raise ValidationError(_('Missing event slug in slot %s') % slot)
62
            agenda_slugs.add(agenda_slug)
63
        extra_agendas = agenda_slugs - set(allowed_agenda_slugs)
64
        if extra_agendas:
65
            extra_agendas = ', '.join(sorted(extra_agendas))
66
            raise ValidationError(_('Unknown agendas: %s') % extra_agendas)
67
        self.agenda_slugs = sorted(agenda_slugs)
68

  
69
        try:
70
            serialized_events = get_events(value)
71
        except ChronoError as e:
72
            raise ValidationError(e)
73
        else:
74
            for serialized_event in serialized_events:
75
                event_slug = '%s@%s' % (serialized_event['agenda'], serialized_event['slug'])
76
                self.serialized_events[event_slug] = serialized_event
77

  
78
        return value
79

  
80
    def get_subscriptions(self, user_external_id):
81
        agenda_subscriptions = {}
82
        for agenda_slug in self.agenda_slugs:
83
            try:
84
                agenda_subscriptions[agenda_slug] = get_subscriptions(agenda_slug, user_external_id)
85
            except ChronoError as e:
86
                raise ValidationError({'user_external_id': e})
87

  
88
        self.event_subscriptions = {}
89
        for event_slug, serialized_event in self.serialized_events.items():
90
            start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
91
            end_date = start_date + datetime.timedelta(days=1)
92
            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
103
            if event_subscription is None:
104
                raise ValidationError(
105
                    {'user_external_id': _('No subscription found for event %s') % event_slug}
106
                )
107
            self.event_subscriptions[event_slug] = event_subscription
108

  
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
114

  
115
    def compute(self, request):
116
        if not self.serialized_events or not self.event_subscriptions:
117
            return
118
        result = []
119
        event_slugs = sorted(self.serialized_events.keys())
120
        for event_slug in event_slugs:
121
            serialized_event = self.serialized_events[event_slug]
122
            try:
123
                pricing_data = AgendaPricing.get_pricing_data(
124
                    request=request,
125
                    agenda=self.agendas[serialized_event['agenda']],
126
                    event=serialized_event,
127
                    subscription=self.event_subscriptions[event_slug],
128
                    check_status={
129
                        'status': 'presence',
130
                        'check_type': None,
131
                    },
132
                    booking={},
133
                    user_external_id=self.validated_data['user_external_id'],
134
                    adult_external_id=self.validated_data['adult_external_id'],
135
                )
136
                result.append(
137
                    {
138
                        'event': event_slug,
139
                        'pricing_data': pricing_data,
140
                    }
141
                )
142
            except PricingError as e:
143
                result.append({'event': event_slug, 'error': e.details})
144

  
145
        result = sorted(result, key=lambda d: d['event'])
146
        return result
lingo/api/urls.py
24 24
        views.agenda_check_type_list,
25 25
        name='api-agenda-check-types',
26 26
    ),
27
    url(
28
        r'^pricing/compute/$',
29
        views.pricing_compute,
30
        name='api-pricing-compute',
31
    ),
27 32
]
lingo/api/views.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django.shortcuts import get_object_or_404
18
from django.utils.translation import gettext_noop as N_
19
from rest_framework import permissions
18 20
from rest_framework.views import APIView
19 21

  
20 22
from lingo.agendas.models import Agenda
21
from lingo.api.utils import Response
23
from lingo.api import serializers
24
from lingo.api.utils import APIErrorBadRequest, Response
22 25

  
23 26

  
24 27
class AgendaCheckTypeList(APIView):
......
37 40

  
38 41

  
39 42
agenda_check_type_list = AgendaCheckTypeList.as_view()
43

  
44

  
45
class PricingCompute(APIView):
46
    permission_classes = (permissions.IsAuthenticated,)
47
    serializer_class = serializers.PricingComputeSerializer
48

  
49
    def get(self, request, format=None):
50
        serializer = self.serializer_class(data=request.query_params)
51
        if not serializer.is_valid():
52
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
53

  
54
        return Response({'data': serializer.compute(self.request)})
55

  
56

  
57
pricing_compute = PricingCompute.as_view()
lingo/settings.py
54 54
    'django.contrib.humanize',
55 55
    'eopayment',
56 56
    'gadjo',
57
    'rest_framework',
57 58
    'lingo.agendas',
58 59
    'lingo.api',
59 60
    'lingo.manager',
tests/agendas/test_chrono.py
9 9
    ChronoError,
10 10
    collect_agenda_data,
11 11
    get_event,
12
    get_events,
12 13
    get_subscriptions,
13 14
    refresh_agendas,
14 15
)
......
206 207
        with pytest.raises(ChronoError) as e:
207 208
            get_event('foo')
208 209
        assert str(e.value) == 'Unable to get event details'
209
        assert requests_get.call_args_list[0][0] == ('api/agendas/events/?slots=foo',)
210
        assert requests_get.call_args_list[0][0] == ('api/agendas/events/',)
211
        assert requests_get.call_args_list[0][1]['params']['slots'] == ['foo']
210 212
        assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org'
211 213

  
212 214
    data = {'data': ['foo']}
......
220 222
        assert get_event('foo') == 'foo'
221 223

  
222 224

  
225
def test_get_events_no_service(settings):
226
    settings.KNOWN_SERVICES = {}
227
    with pytest.raises(ChronoError) as e:
228
        get_events(['foo', 'bar'])
229
    assert str(e.value) == 'Unable to get events details'
230

  
231
    settings.KNOWN_SERVICES = {'other': []}
232
    with pytest.raises(ChronoError) as e:
233
        get_events(['foo', 'bar'])
234
    assert str(e.value) == 'Unable to get events details'
235

  
236

  
237
def test_get_events():
238
    with mock.patch('requests.Session.get') as requests_get:
239
        requests_get.side_effect = ConnectionError()
240
        with pytest.raises(ChronoError) as e:
241
            get_events(['foo', 'bar'])
242
        assert str(e.value) == 'Unable to get events details'
243

  
244
    with mock.patch('requests.Session.get') as requests_get:
245
        mock_resp = Response()
246
        mock_resp.status_code = 500
247
        requests_get.return_value = mock_resp
248
        with pytest.raises(ChronoError) as e:
249
            get_events(['foo', 'bar'])
250
        assert str(e.value) == 'Unable to get events details'
251

  
252
    with mock.patch('requests.Session.get') as requests_get:
253
        mock_resp = Response()
254
        mock_resp.status_code = 404
255
        requests_get.return_value = mock_resp
256
        with pytest.raises(ChronoError) as e:
257
            get_events(['foo', 'bar'])
258
        assert str(e.value) == 'Unable to get events details'
259

  
260
    with mock.patch('requests.Session.get') as requests_get:
261
        requests_get.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'}))
262
        with pytest.raises(ChronoError) as e:
263
            get_events(['foo', 'bar'])
264
        assert str(e.value) == 'Unable to get events details'
265

  
266
    data = {'data': []}
267
    with mock.patch('requests.Session.get') as requests_get:
268
        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
269
        with pytest.raises(ChronoError) as e:
270
            get_events(['foo', 'bar'])
271
        assert str(e.value) == 'Unable to get events details'
272
        assert requests_get.call_args_list[0][0] == ('api/agendas/events/',)
273
        assert requests_get.call_args_list[0][1]['params']['slots'] == ['foo', 'bar']
274
        assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org'
275

  
276
    data = {'data': ['foo', 'bar']}
277
    with mock.patch('requests.Session.get') as requests_get:
278
        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
279
        assert get_events(['foo', 'bar']) == ['foo', 'bar']
280

  
281

  
223 282
def test_get_subscriptions_no_service(settings):
224 283
    settings.KNOWN_SERVICES = {}
225 284
    with pytest.raises(ChronoError) as e:
tests/api/conftest.py
1
import pytest
2
from django.contrib.auth import get_user_model
3

  
4
User = get_user_model()
5

  
6

  
7
@pytest.fixture
8
def user():
9
    user = User.objects.create(
10
        username='john.doe', first_name='John', last_name='Doe', email='john.doe@example.net'
11
    )
12
    user.set_password('password')
13
    user.save()
14
    return user
tests/api/test_pricing.py
1
from unittest import mock
2

  
3
import pytest
4

  
5
from lingo.agendas.chrono import ChronoError
6
from lingo.agendas.models import Agenda
7
from lingo.pricing.models import PricingError
8

  
9
pytestmark = pytest.mark.django_db
10

  
11

  
12
def test_pricing_compute_params(app, user):
13
    app.authorization = ('Basic', ('john.doe', 'password'))
14

  
15
    # missing slots
16
    resp = app.get(
17
        '/api/pricing/compute/',
18
        params={'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
19
        status=400,
20
    )
21
    assert resp.json['err'] == 1
22
    assert resp.json['err_desc'] == 'invalid payload'
23
    assert resp.json['errors']['slots'] == ['This field is required.']
24

  
25
    # missing user_external_id
26
    resp = app.get(
27
        '/api/pricing/compute/',
28
        params={'slots': 'foo@foo', 'adult_external_id': 'adult:1'},
29
        status=400,
30
    )
31
    assert resp.json['err'] == 1
32
    assert resp.json['err_desc'] == 'invalid payload'
33
    assert resp.json['errors']['user_external_id'] == ['This field is required.']
34

  
35
    # missing adult_external_id
36
    resp = app.get(
37
        '/api/pricing/compute/',
38
        params={'slots': 'foo@foo', 'user_external_id': 'user:1'},
39
        status=400,
40
    )
41
    assert resp.json['err'] == 1
42
    assert resp.json['err_desc'] == 'invalid payload'
43
    assert resp.json['errors']['adult_external_id'] == ['This field is required.']
44

  
45

  
46
def test_pricing_compute_slots(app, user):
47
    app.authorization = ('Basic', ('john.doe', 'password'))
48

  
49
    # bad slot format
50
    resp = app.get(
51
        '/api/pricing/compute/',
52
        params={
53
            'slots': 'event-bar-slug',
54
            'user_external_id': 'user:1',
55
            'adult_external_id': 'adult:1',
56
        },
57
        status=400,
58
    )
59
    assert resp.json['err'] == 1
60
    assert resp.json['err_desc'] == 'invalid payload'
61
    assert resp.json['errors']['slots'] == ['Invalid format for slot event-bar-slug']
62
    resp = app.get(
63
        '/api/pricing/compute/',
64
        params={
65
            'slots': '@event-bar-slug',
66
            'user_external_id': 'user:1',
67
            'adult_external_id': 'adult:1',
68
        },
69
        status=400,
70
    )
71
    assert resp.json['err'] == 1
72
    assert resp.json['err_desc'] == 'invalid payload'
73
    assert resp.json['errors']['slots'] == ['Missing agenda slug in slot @event-bar-slug']
74
    resp = app.get(
75
        '/api/pricing/compute/',
76
        params={
77
            'slots': 'agenda@',
78
            'user_external_id': 'user:1',
79
            'adult_external_id': 'adult:1',
80
        },
81
        status=400,
82
    )
83
    assert resp.json['err'] == 1
84
    assert resp.json['err_desc'] == 'invalid payload'
85
    assert resp.json['errors']['slots'] == ['Missing event slug in slot agenda@']
86

  
87
    # unknown agenda
88
    resp = app.get(
89
        '/api/pricing/compute/',
90
        params={
91
            'slots': 'agenda@event-bar-slug, agenda2@event-bar-slug',
92
            'user_external_id': 'user:1',
93
            'adult_external_id': 'adult:1',
94
        },
95
        status=400,
96
    )
97
    assert resp.json['err'] == 1
98
    assert resp.json['err_desc'] == 'invalid payload'
99
    assert resp.json['errors']['slots'] == ['Unknown agendas: agenda, agenda2']
100

  
101

  
102
@mock.patch('lingo.api.serializers.get_events')
103
def test_pricing_compute_events_error(mock_events, app, user):
104
    Agenda.objects.create(label='Agenda')
105
    app.authorization = ('Basic', ('john.doe', 'password'))
106

  
107
    mock_events.side_effect = ChronoError('foo bar')
108
    resp = app.get(
109
        '/api/pricing/compute/',
110
        params={
111
            'slots': 'agenda@event-bar-slug',
112
            'user_external_id': 'user:1',
113
            'adult_external_id': 'adult:1',
114
        },
115
        status=400,
116
    )
117
    assert resp.json['err'] == 1
118
    assert resp.json['err_desc'] == 'invalid payload'
119
    assert resp.json['errors']['slots'] == ['foo bar']
120

  
121

  
122
@mock.patch('lingo.api.serializers.get_events')
123
@mock.patch('lingo.api.serializers.get_subscriptions')
124
def test_pricing_compute_subscriptions_error(mock_subscriptions, mock_events, app, user):
125
    Agenda.objects.create(label='Agenda')
126
    app.authorization = ('Basic', ('john.doe', 'password'))
127

  
128
    mock_events.return_value = [
129
        {'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'}
130
    ]
131
    mock_subscriptions.side_effect = ChronoError('foo baz')
132
    resp = app.get(
133
        '/api/pricing/compute/',
134
        params={
135
            'slots': 'agenda@event-bar-slug',
136
            'user_external_id': 'user:1',
137
            'adult_external_id': 'adult:1',
138
        },
139
        status=400,
140
    )
141
    assert resp.json['err'] == 1
142
    assert resp.json['err_desc'] == 'invalid payload'
143
    assert resp.json['errors']['user_external_id'] == ['foo baz']
144

  
145

  
146
@mock.patch('lingo.api.serializers.get_events')
147
@mock.patch('lingo.api.serializers.get_subscriptions')
148
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
149
def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app, user):
150
    agenda = Agenda.objects.create(label='Agenda')
151
    agenda2 = Agenda.objects.create(label='Agenda2')
152
    app.authorization = ('Basic', ('john.doe', 'password'))
153

  
154
    mock_events.return_value = [
155
        {'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'}
156
    ]
157
    mock_subscriptions.return_value = []
158
    resp = app.get(
159
        '/api/pricing/compute/',
160
        params={
161
            'slots': 'agenda@event-bar-slug',
162
            'user_external_id': 'user:1',
163
            'adult_external_id': 'adult:1',
164
        },
165
        status=400,
166
    )
167
    assert resp.json['err'] == 1
168
    assert resp.json['err_desc'] == 'invalid payload'
169
    assert resp.json['errors']['user_external_id'] == [
170
        'No subscription found for event agenda@event-bar-slug'
171
    ]
172
    assert mock_pricing_data.call_args_list == []
173
    assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
174

  
175
    mock_subscriptions.reset_mock()
176
    mock_subscriptions.return_value = [
177
        {
178
            'date_start': '2021-08-01',
179
            'date_end': '2021-09-01',
180
        },
181
        {
182
            'date_start': '2021-09-02',
183
            'date_end': '2021-09-03',
184
        },
185
    ]
186
    resp = app.get(
187
        '/api/pricing/compute/',
188
        params={
189
            'slots': 'agenda@event-bar-slug',
190
            'user_external_id': 'user:1',
191
            'adult_external_id': 'adult:1',
192
        },
193
        status=400,
194
    )
195
    assert resp.json['err'] == 1
196
    assert resp.json['err_desc'] == 'invalid payload'
197
    assert resp.json['errors']['user_external_id'] == [
198
        'No subscription found for event agenda@event-bar-slug'
199
    ]
200
    assert mock_pricing_data.call_args_list == []
201
    assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
202

  
203
    mock_events.return_value = [
204
        {'start_datetime': '2021-09-02T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'},
205
        {'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda2', 'slug': 'event-baz-slug'},
206
    ]
207
    mock_subscriptions.reset_mock()
208
    mock_subscriptions.return_value = [
209
        {
210
            'date_start': '2021-08-01',
211
            'date_end': '2021-09-01',
212
        },
213
        {
214
            'date_start': '2021-09-02',
215
            'date_end': '2021-09-03',
216
        },
217
    ]
218
    resp = app.get(
219
        '/api/pricing/compute/',
220
        params={
221
            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
222
            'user_external_id': 'user:1',
223
            'adult_external_id': 'adult:1',
224
        },
225
        status=400,
226
    )
227
    assert resp.json['err'] == 1
228
    assert resp.json['err_desc'] == 'invalid payload'
229
    assert resp.json['errors']['user_external_id'] == [
230
        'No subscription found for event agenda2@event-baz-slug'
231
    ]
232
    assert mock_pricing_data.call_args_list == []
233
    assert mock_subscriptions.call_args_list == [
234
        mock.call('agenda', 'user:1'),
235
        mock.call('agenda2', 'user:1'),
236
    ]
237

  
238
    mock_subscriptions.return_value = [
239
        {
240
            'date_start': '2021-08-01',
241
            'date_end': '2021-09-01',
242
        },
243
        {
244
            'date_start': '2021-09-01',
245
            'date_end': '2021-09-02',
246
        },
247
        {
248
            'date_start': '2021-09-02',
249
            'date_end': '2021-09-03',
250
        },
251
    ]
252
    mock_pricing_data.side_effect = [
253
        {'foo': 'baz'},
254
        {'foo': 'bar'},
255
    ]
256
    resp = app.get(
257
        '/api/pricing/compute/',
258
        params={
259
            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
260
            'user_external_id': 'user:1',
261
            'adult_external_id': 'adult:1',
262
        },
263
    )
264
    assert resp.json['data'] == [
265
        {'event': 'agenda2@event-baz-slug', 'pricing_data': {'foo': 'baz'}},
266
        {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
267
    ]
268
    assert mock_pricing_data.call_args_list == [
269
        mock.call(
270
            request=mock.ANY,
271
            agenda=agenda2,
272
            event={
273
                'start_datetime': '2021-09-01T12:00:00+02:00',
274
                'agenda': 'agenda2',
275
                'slug': 'event-baz-slug',
276
            },
277
            subscription={'date_start': '2021-09-01', 'date_end': '2021-09-02'},
278
            check_status={'status': 'presence', 'check_type': None},
279
            booking={},
280
            user_external_id='user:1',
281
            adult_external_id='adult:1',
282
        ),
283
        mock.call(
284
            request=mock.ANY,
285
            agenda=agenda,
286
            event={
287
                'start_datetime': '2021-09-02T12:00:00+02:00',
288
                'agenda': 'agenda',
289
                'slug': 'event-bar-slug',
290
            },
291
            subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
292
            check_status={'status': 'presence', 'check_type': None},
293
            booking={},
294
            user_external_id='user:1',
295
            adult_external_id='adult:1',
296
        ),
297
    ]
298

  
299
    mock_pricing_data.side_effect = [
300
        PricingError(details={'foo': 'error'}),
301
        {'foo': 'bar'},
302
    ]
303
    resp = app.get(
304
        '/api/pricing/compute/',
305
        params={
306
            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
307
            'user_external_id': 'user:1',
308
            'adult_external_id': 'adult:1',
309
        },
310
    )
311
    assert resp.json['data'] == [
312
        {'event': 'agenda2@event-baz-slug', 'error': {'foo': 'error'}},
313
        {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
314
    ]
0
-