From 41eda72b2eb08336714d38ade5d3d9b101e8f030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Tue, 5 Jul 2022 19:58:13 +0200 Subject: [PATCH] pricing: test tool for agenda pricing (#66892) --- lingo/agendas/chrono.py | 29 ++ lingo/manager/static/css/style.scss | 5 + lingo/pricing/forms.py | 111 +++++++- lingo/pricing/models.py | 12 +- .../manager_agenda_pricing_detail.html | 25 +- lingo/pricing/views.py | 10 + tests/agendas/test_chrono.py | 125 ++++++++- tests/pricing/manager/test_agenda.py | 252 +++++++++++++++++- 8 files changed, 560 insertions(+), 9 deletions(-) diff --git a/lingo/agendas/chrono.py b/lingo/agendas/chrono.py index 3dc8929..c2ed78b 100644 --- a/lingo/agendas/chrono.py +++ b/lingo/agendas/chrono.py @@ -17,12 +17,17 @@ import json from django.conf import settings +from django.utils.translation import ugettext_lazy as _ from requests.exceptions import RequestException from lingo.agendas.models import Agenda from lingo.utils import requests +class ChronoError(Exception): + pass + + def is_chrono_enabled(): return hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get('chrono') @@ -103,3 +108,27 @@ def refresh_agendas(): for slug, agenda in existing_agendas.items(): if slug not in seen_agendas: agenda.delete() + + +def get_event(event_slug): + result = get_chrono_json('api/agendas/events/?slots=%s' % event_slug) + if not result: + raise ChronoError(_('Unable to get event details')) + if result.get('err'): + raise ChronoError(_('Unable to get event details (%s)') % result['err_desc']) + if not result.get('data'): + raise ChronoError(_('Unable to get event details')) + return result['data'][0] + + +def get_subscriptions(agenda_slug, user_external_id): + result = get_chrono_json( + 'api/agenda/%s/subscription/?user_external_id=%s' % (agenda_slug, user_external_id) + ) + if not result or not result.get('data'): + raise ChronoError(_('Unable to get subscription details')) + if result.get('err'): + raise ChronoError(_('Unable to get subscription details (%s)') % result['err_desc']) + if not result.get('data'): + raise ChronoError(_('Unable to get subscription details')) + return result['data'] diff --git a/lingo/manager/static/css/style.scss b/lingo/manager/static/css/style.scss index 7cfd025..1fb43fc 100644 --- a/lingo/manager/static/css/style.scss +++ b/lingo/manager/static/css/style.scss @@ -47,3 +47,8 @@ ul.objects-list.sortable { ul.objects-list.single-links li a.link::before { content: "\f08e"; /* fa-external-link */ } + +div.test-tool-result .infonotice h3 { + margin-top: 0; + font-weight: normal; +} diff --git a/lingo/pricing/forms.py b/lingo/pricing/forms.py index 4795993..c35e5b7 100644 --- a/lingo/pricing/forms.py +++ b/lingo/pricing/forms.py @@ -14,14 +14,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import datetime + from django import forms from django.forms import ValidationError from django.template import Template, TemplateSyntaxError from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ +from lingo.agendas.chrono import ChronoError, get_event, get_subscriptions from lingo.agendas.models import CheckType -from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory +from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, PricingError class ExportForm(forms.Form): @@ -162,6 +165,112 @@ class PricingMatrixForm(forms.Form): self.fields['crit_%i' % i] = forms.DecimalField(required=True, max_digits=5, decimal_places=2) +class PricingTestToolForm(forms.Form): + event_slug = forms.CharField(label=_('Event identifier')) + user_external_id = forms.CharField(label=_('User external identifier')) + adult_external_id = forms.CharField(label=_('Adult external identifier')) + booking_status = forms.ChoiceField(label=_('Booking status'), choices=[]) + + def __init__(self, *args, **kwargs): + self.request = kwargs.pop('request') + self.agenda_pricing = kwargs.pop('agenda_pricing') + self.agenda = self.agenda_pricing.agenda + self.serialized_event = None + self.serialized_subscription = None + self.check_type_slug = None + self.booking_status = None + super().__init__(*args, **kwargs) + presence_check_types = ( + self.agenda.check_type_group.check_types.presences() if self.agenda.check_type_group else [] + ) + absence_check_types = ( + self.agenda.check_type_group.check_types.absences() if self.agenda.check_type_group else [] + ) + status_choices = [ + ('presence', _('Presence')), + ] + status_choices += [ + ('presence::%s' % ct.slug, _('Presence (%s)') % ct.label) for ct in presence_check_types + ] + status_choices += [('absence', _('Absence'))] + status_choices += [ + ('absence::%s' % ct.slug, _('Absence (%s)') % ct.label) for ct in absence_check_types + ] + self.fields['booking_status'].choices = status_choices + + def clean_event_slug(self): + original_event_slug = self.cleaned_data['event_slug'] + event_slug = original_event_slug + if '@' not in event_slug: + # chrono's endpoint takes event ids like '@' + event_slug = '%s@%s' % (self.agenda.slug, event_slug) + elif event_slug.split('@')[0] != self.agenda.slug: + raise ValidationError(_('The agenda identifier is wrong (%s)') % event_slug.split('@')[0]) + try: + self.serialized_event = get_event(event_slug) + except ChronoError as e: + raise forms.ValidationError(str(e).replace(event_slug, original_event_slug)) + + event_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date() + if event_date < self.agenda_pricing.date_start or event_date >= self.agenda_pricing.date_end: + raise ValidationError(_('This event takes place outside the period covered by this pricing')) + + return original_event_slug + + def clean_booking_status(self): + original_booking_status = self.cleaned_data['booking_status'] + self.booking_status = original_booking_status + if '::' in original_booking_status: + # split value to get booking status and selected check_type + self.booking_status, self.check_type_slug = original_booking_status.split('::') + return original_booking_status + + def get_subscription(self, user_external_id): + start_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date() + end_date = start_date + datetime.timedelta(days=1) + try: + subscriptions = get_subscriptions(self.agenda.slug, user_external_id) + except ChronoError as e: + self.add_error('user_external_id', str(e)) + return + for subscription in subscriptions: + sub_start_date = datetime.date.fromisoformat(subscription['date_start']) + sub_end_date = datetime.date.fromisoformat(subscription['date_end']) + if sub_start_date >= end_date: + continue + if sub_end_date <= start_date: + continue + return subscription + self.add_error('user_external_id', _('No subscription found for this event')) + + def clean(self): + super().clean() + if self.cleaned_data.get('user_external_id') and self.serialized_event: + user_external_id = self.cleaned_data['user_external_id'] + self.serialized_subscription = self.get_subscription(user_external_id) + + def compute(self): + if not self.serialized_event or not self.serialized_subscription: + return + try: + return AgendaPricing.get_pricing_data( + request=self.request, + agenda=self.agenda, + agenda_pricing=self.agenda_pricing, + event=self.serialized_event, + subscription=self.serialized_subscription, + check_status={ + 'status': self.booking_status, + 'check_type': self.check_type_slug, + }, + booking={}, + user_external_id=self.cleaned_data['user_external_id'], + adult_external_id=self.cleaned_data['adult_external_id'], + ) + except PricingError as e: + return {'error': e.details} + + class NewCheckTypeForm(forms.ModelForm): class Meta: model = CheckType diff --git a/lingo/pricing/models.py b/lingo/pricing/models.py index 3329668..8e42436 100644 --- a/lingo/pricing/models.py +++ b/lingo/pricing/models.py @@ -363,9 +363,17 @@ class AgendaPricing(models.Model): @staticmethod def get_pricing_data( - request, agenda, event, subscription, check_status, booking, user_external_id, adult_external_id + request, + agenda, + event, + subscription, + check_status, + booking, + user_external_id, + adult_external_id, + agenda_pricing=None, ): - agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) + agenda_pricing = agenda_pricing or AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) data = { 'event': event, 'subscription': subscription, diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html index 866f63c..fe6afd5 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html @@ -20,8 +20,31 @@ {% endblock %} {% block content %} -{% for matrix in object.iter_pricing_matrix %}
+

{% trans "Test tool" %}

+
+
+ {{ test_tool_form.as_p }} +
+ +
+
+ {% if request.GET and test_tool_form.is_valid %} + {% with test_tool_form.compute as pricing_data %} +
+
+

{% trans "Computed pricing data" %}

+ {% if pricing_data.pricing is not None %}

{% trans "Pricing:" %} {{ pricing_data.pricing|stringformat:".2f" }}

{% endif %} +
{{ pricing_data|pprint }}
+
+
+ {% endwith %} + {% endif %} +
+
+ +{% for matrix in object.iter_pricing_matrix %} +
{% if matrix.criteria %}

{{ matrix.criteria.label }}

{% endif %}
diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py index 366843a..242984f 100644 --- a/lingo/pricing/views.py +++ b/lingo/pricing/views.py @@ -54,6 +54,7 @@ from lingo.pricing.forms import ( PricingCriteriaCategoryEditForm, PricingDuplicateForm, PricingMatrixForm, + PricingTestToolForm, PricingVariableFormSet, ) from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory @@ -717,6 +718,15 @@ class AgendaPricingDetailView(AgendaMixin, DetailView): 'pricing__criterias__category' ) + def get_context_data(self, **kwargs): + form = PricingTestToolForm( + agenda_pricing=self.object, request=self.request, data=self.request.GET or None + ) + if self.request.GET: + form.is_valid() + kwargs['test_tool_form'] = form + return super().get_context_data(**kwargs) + agenda_pricing_detail = AgendaPricingDetailView.as_view() diff --git a/tests/agendas/test_chrono.py b/tests/agendas/test_chrono.py index 5d06579..33cb47e 100644 --- a/tests/agendas/test_chrono.py +++ b/tests/agendas/test_chrono.py @@ -5,7 +5,13 @@ import pytest from requests.exceptions import ConnectionError from requests.models import Response -from lingo.agendas.chrono import collect_agenda_data, refresh_agendas +from lingo.agendas.chrono import ( + ChronoError, + collect_agenda_data, + get_event, + get_subscriptions, + refresh_agendas, +) from lingo.agendas.models import Agenda pytestmark = pytest.mark.django_db @@ -151,3 +157,120 @@ def test_refresh_agendas(mock_collect): mock_collect.return_value = [] refresh_agendas() assert Agenda.objects.count() == 0 + + +def test_get_event_no_service(settings): + settings.KNOWN_SERVICES = {} + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + settings.KNOWN_SERVICES = {'other': []} + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + +def test_get_event(): + with mock.patch('requests.Session.get') as requests_get: + requests_get.side_effect = ConnectionError() + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + with mock.patch('requests.Session.get') as requests_get: + mock_resp = Response() + mock_resp.status_code = 500 + requests_get.return_value = mock_resp + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + with mock.patch('requests.Session.get') as requests_get: + mock_resp = Response() + mock_resp.status_code = 404 + requests_get.return_value = mock_resp + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'})) + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + + data = {'data': []} + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps(data)) + with pytest.raises(ChronoError) as e: + get_event('foo') + assert str(e.value) == 'Unable to get event details' + assert requests_get.call_args_list[0][0] == ('api/agendas/events/?slots=foo',) + assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org' + + data = {'data': ['foo']} + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps(data)) + assert get_event('foo') == 'foo' + + data = {'data': ['foo', 'bar']} # should not happen + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps(data)) + assert get_event('foo') == 'foo' + + +def test_get_subscriptions_no_service(settings): + settings.KNOWN_SERVICES = {} + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + settings.KNOWN_SERVICES = {'other': []} + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + +def test_get_subscriptions(): + with mock.patch('requests.Session.get') as requests_get: + requests_get.side_effect = ConnectionError() + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + with mock.patch('requests.Session.get') as requests_get: + mock_resp = Response() + mock_resp.status_code = 500 + requests_get.return_value = mock_resp + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + with mock.patch('requests.Session.get') as requests_get: + mock_resp = Response() + mock_resp.status_code = 404 + requests_get.return_value = mock_resp + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'})) + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + + data = {'data': []} + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps(data)) + with pytest.raises(ChronoError) as e: + get_subscriptions('foo', 'user:1') + assert str(e.value) == 'Unable to get subscription details' + assert requests_get.call_args_list[0][0] == ('api/agenda/foo/subscription/?user_external_id=user:1',) + assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org' + + data = {'data': ['foo', 'bar']} + with mock.patch('requests.Session.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps(data)) + assert get_subscriptions('foo', 'user:1') == ['foo', 'bar'] diff --git a/tests/pricing/manager/test_agenda.py b/tests/pricing/manager/test_agenda.py index 571e518..106fe1d 100644 --- a/tests/pricing/manager/test_agenda.py +++ b/tests/pricing/manager/test_agenda.py @@ -1,11 +1,13 @@ import datetime +from decimal import Decimal from unittest import mock import pytest from django.utils.timezone import now -from lingo.agendas.models import Agenda, CheckTypeGroup -from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing +from lingo.agendas.chrono import ChronoError +from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup +from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingError from tests.utils import login pytestmark = pytest.mark.django_db @@ -353,7 +355,7 @@ def test_detail_agenda_pricing_2_categories(app, admin_user): app = login(app) resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) - assert '

' not in resp + assert len(resp.pyquery.find('div.section.prixing-matrix h3')) == 0 ths = resp.pyquery.find('table thead th') assert len(ths) == 4 assert ths[0].text is None @@ -400,7 +402,7 @@ def test_detail_agenda_pricing_1_category(app, admin_user): app = login(app) resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) - assert '

' not in resp + assert len(resp.pyquery.find('div.section.prixing-matrix h3')) == 0 ths = resp.pyquery.find('table thead') assert len(ths) == 0 assert resp.pyquery.find('table tr.pricing-row-crit-3-1 th')[0].text == 'Crit 3-1' @@ -413,6 +415,248 @@ def test_detail_agenda_pricing_1_category(app, admin_user): assert resp.pyquery.find('table tr.pricing-row-crit-3-4 td')[0].text == '114.00' +@mock.patch('lingo.pricing.forms.get_event') +@mock.patch('lingo.pricing.forms.get_subscriptions') +@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data') +def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, mock_event, app, admin_user): + agenda = Agenda.objects.create(label='Foo bar') + pricing = Pricing.objects.create(label='Foo bar') + agenda_pricing = AgendaPricing.objects.create( + agenda=agenda, + pricing=pricing, + date_start=datetime.date(year=2021, month=9, day=1), + date_end=datetime.date(year=2021, month=10, day=1), + ) + + app = login(app) + resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) + assert 'Computed pricing data' not in resp + + # check event date + mock_event.return_value = {'start_datetime': '2021-08-31T12:00:00+02:00'} + resp.form['event_slug'] = 'foo' + resp.form['user_external_id'] = 'user:1' + resp.form['adult_external_id'] = 'adult:1' + resp.form['booking_status'] = 'presence' + resp = resp.form.submit() + assert 'Computed pricing data' not in resp + assert resp.context['test_tool_form'].errors['event_slug'] == [ + 'This event takes place outside the period covered by this pricing' + ] + assert mock_event.call_args_list == [mock.call('foo-bar@foo')] + assert mock_pricing_data.call_args_list == [] + mock_event.return_value = {'start_datetime': '2021-10-01T12:00:00+02:00'} + resp = resp.form.submit() + assert 'Computed pricing data' not in resp + assert resp.context['test_tool_form'].errors['event_slug'] == [ + 'This event takes place outside the period covered by this pricing' + ] + + mock_event.return_value = {'start_datetime': '2021-09-01T12:00:00+02:00'} + + # check event_slug & agenda + resp.form['event_slug'] = 'foo@foo' + resp = resp.form.submit() + assert resp.context['test_tool_form'].errors['event_slug'] == ['The agenda identifier is wrong (foo)'] + + # check subscriptions dates + mock_subscriptions.return_value = [] + mock_event.reset_mock() + resp.form['event_slug'] = 'foo-bar@foo' + resp = resp.form.submit() + assert mock_event.call_args_list == [mock.call('foo-bar@foo')] + assert mock_pricing_data.call_args_list == [] + assert 'Computed pricing data' not in resp + assert resp.context['test_tool_form'].errors['user_external_id'] == [ + 'No subscription found for this event' + ] + + mock_subscriptions.return_value = [ + { + 'date_start': '2021-08-01', + 'date_end': '2021-09-01', + }, + { + 'date_start': '2021-09-02', + 'date_end': '2021-09-03', + }, + ] + resp = resp.form.submit() + assert 'Computed pricing data' not in resp + assert resp.context['test_tool_form'].errors['user_external_id'] == [ + 'No subscription found for this event' + ] + + mock_subscriptions.return_value = [ + { + 'date_start': '2021-08-01', + 'date_end': '2021-09-01', + }, + { + 'date_start': '2021-09-01', + 'date_end': '2021-09-02', + }, + { + 'date_start': '2021-09-02', + 'date_end': '2021-09-03', + }, + ] + mock_pricing_data.return_value = {'foo': 'bar', 'pricing': Decimal('42')} + resp = resp.form.submit() + assert 'Computed pricing data' in resp + assert mock_pricing_data.call_args_list == [ + mock.call( + request=mock.ANY, + agenda=agenda, + agenda_pricing=agenda_pricing, + event={'start_datetime': '2021-09-01T12:00:00+02:00'}, + subscription={'date_start': '2021-09-01', 'date_end': '2021-09-02'}, + check_status={'status': 'presence', 'check_type': None}, + booking={}, + user_external_id='user:1', + adult_external_id='adult:1', + ) + ] + assert '

Pricing: 42.00

' in resp + assert '
{'foo': 'bar', 'pricing': Decimal('42')}
' in resp + + mock_pricing_data.side_effect = PricingError(details={'foo': 'error'}) + resp = resp.form.submit() + assert 'Computed pricing data' in resp + assert '
{'error': {'foo': 'error'}}
' in resp + + +@mock.patch('lingo.pricing.forms.get_event') +@mock.patch('lingo.pricing.forms.get_subscriptions') +def test_detail_agenda_pricing_test_tool_event_error(mock_subscriptions, mock_event, app, admin_user): + agenda = Agenda.objects.create(label='Foo bar') + pricing = Pricing.objects.create(label='Foo bar') + agenda_pricing = AgendaPricing.objects.create( + agenda=agenda, + pricing=pricing, + date_start=datetime.date(year=2021, month=9, day=1), + date_end=datetime.date(year=2021, month=10, day=1), + ) + + mock_event.side_effect = ChronoError('foo bar foo-bar@foo-event') + + app = login(app) + resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) + resp.form['event_slug'] = 'foo-event' + resp.form['user_external_id'] = 'user:1' + resp.form['adult_external_id'] = 'adult:1' + resp.form['booking_status'] = 'presence' + + # agenda slug is removed from error message + resp = resp.form.submit() + assert resp.context['test_tool_form'].errors['event_slug'] == ['foo bar foo-event'] + + # except it was included in event_slug + resp.form['event_slug'] = 'foo-bar@foo-event' + resp = resp.form.submit() + assert resp.context['test_tool_form'].errors['event_slug'] == ['foo bar foo-bar@foo-event'] + + +@mock.patch('lingo.pricing.forms.get_event') +@mock.patch('lingo.pricing.forms.get_subscriptions') +def test_detail_agenda_pricing_test_tool_subscription_error(mock_subscriptions, mock_event, app, admin_user): + agenda = Agenda.objects.create(label='Foo bar') + pricing = Pricing.objects.create(label='Foo bar') + agenda_pricing = AgendaPricing.objects.create( + agenda=agenda, + pricing=pricing, + date_start=datetime.date(year=2021, month=9, day=1), + date_end=datetime.date(year=2021, month=10, day=1), + ) + + mock_event.return_value = {'start_datetime': '2021-09-01T12:00:00+02:00'} + mock_subscriptions.side_effect = ChronoError('foo bar') + + app = login(app) + resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) + resp.form['event_slug'] = 'foo-event' + resp.form['user_external_id'] = 'user:1' + resp.form['adult_external_id'] = 'adult:1' + resp.form['booking_status'] = 'presence' + resp = resp.form.submit() + assert resp.context['test_tool_form'].errors['user_external_id'] == ['foo bar'] + + +@mock.patch('lingo.pricing.forms.get_event') +@mock.patch('lingo.pricing.forms.get_subscriptions') +@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data') +def test_detail_agenda_pricing_test_tool_booking_status( + mock_pricing_data, mock_subscriptions, mock_event, app, admin_user +): + agenda = Agenda.objects.create(label='Foo bar') + pricing = Pricing.objects.create(label='Foo bar') + agenda_pricing = AgendaPricing.objects.create( + agenda=agenda, + pricing=pricing, + date_start=datetime.date(year=2021, month=9, day=1), + date_end=datetime.date(year=2021, month=10, day=1), + ) + + mock_event.return_value = {'start_datetime': '2021-09-01T12:00:00+02:00'} + mock_subscriptions.return_value = [ + { + 'date_start': '2021-09-01', + 'date_end': '2021-09-02', + }, + ] + + app = login(app) + resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) + assert resp.form['booking_status'].options == [ + ('presence', False, 'Presence'), + ('absence', False, 'Absence'), + ] + + group = CheckTypeGroup.objects.create(label='Foo bar') + CheckType.objects.create(label='Foo presence reason', group=group, kind='presence') + CheckType.objects.create(label='Foo absence reason', group=group, kind='absence') + agenda.check_type_group = group + agenda.save() + + resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) + assert resp.form['booking_status'].options == [ + ('presence', False, 'Presence'), + ('presence::foo-presence-reason', False, 'Presence (Foo presence reason)'), + ('absence', False, 'Absence'), + ('absence::foo-absence-reason', False, 'Absence (Foo absence reason)'), + ] + resp.form['event_slug'] = 'foo' + resp.form['user_external_id'] = 'user:1' + resp.form['adult_external_id'] = 'adult:1' + resp.form['booking_status'] = 'presence' + resp = resp.form.submit() + assert mock_pricing_data.call_args_list[0][1]['check_status'] == { + 'check_type': None, + 'status': 'presence', + } + + mock_pricing_data.reset_mock() + resp.form['booking_status'] = 'presence::foo-presence-reason' + resp = resp.form.submit() + assert mock_pricing_data.call_args_list[0][1]['check_status'] == { + 'check_type': 'foo-presence-reason', + 'status': 'presence', + } + + mock_pricing_data.reset_mock() + resp.form['booking_status'] = 'absence' + resp = resp.form.submit() + assert mock_pricing_data.call_args_list[0][1]['check_status'] == {'check_type': None, 'status': 'absence'} + + mock_pricing_data.reset_mock() + resp.form['booking_status'] = 'absence::foo-absence-reason' + resp = resp.form.submit() + assert mock_pricing_data.call_args_list[0][1]['check_status'] == { + 'check_type': 'foo-absence-reason', + 'status': 'absence', + } + + def test_edit_agenda_pricing_matrix_3_categories(app, admin_user): category1 = CriteriaCategory.objects.create(label='Cat 1') criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1)