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)