0002-api-endpoint-to-get-pricing-data-for-a-list-of-event.patch
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.IsAuthenticatedOrReadOnly,) |
|
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() |
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 |
- |