invoicing: move payer_data calculation in Payer model (#78015)

This commit is contained in:
Lauréline Guérin 2023-06-12 16:40:57 +02:00
parent abddf1175f
commit 9d57534786
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
8 changed files with 277 additions and 273 deletions

View File

@ -26,6 +26,7 @@ from django.contrib.auth.models import Group
from django.core import validators
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connection, models, transaction
from django.template import RequestContext, Template
from django.template.loader import get_template
from django.utils.formats import date_format
from django.utils.text import slugify
@ -47,6 +48,20 @@ class PoolPromotionError(Exception):
self.msg = msg
class InvoicingError(Exception):
def __init__(self, details=None):
self.details = details or {}
super().__init__()
class PayerError(InvoicingError):
pass
class PayerDataError(InvoicingError):
pass
class Payer(models.Model):
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
@ -156,6 +171,44 @@ class Payer(models.Model):
result.append((label, value))
return result
def get_payer_data(self, request, payer_external_id):
if not self.carddef_reference:
raise PayerError(details={'reason': 'missing-card-model'})
result = {}
context = RequestContext(request)
payer_external_raw_id = None
if ':' in payer_external_id:
payer_external_raw_id = payer_external_id.split(':')[1]
context.push({'payer_external_id': payer_external_raw_id or payer_external_id})
bool_keys = ['demat', 'direct_debit']
for key, dummy in self.user_variables:
if not self.user_fields_mapping.get(key):
if key not in bool_keys:
raise PayerDataError(details={'key': key, 'reason': 'not-defined'})
tplt = 'False'
else:
tplt = (
'{{ cards|objects:"%s"|filter_by_internal_id:payer_external_id|include_fields|first|get:"fields"|get:"%s"|default:"" }}'
% (
self.carddef_reference.split(':')[1],
self.user_fields_mapping[key],
)
)
value = Template(tplt).render(context)
if not value:
if key not in bool_keys:
raise PayerDataError(details={'key': key, 'reason': 'empty-result'})
value = False
if key in bool_keys:
if value in ('True', 'true', '1'):
value = True
elif value in ('False', 'false', '0'):
value = False
else:
raise PayerDataError(details={'key': key, 'reason': 'not-a-boolean'})
result[key] = value
return result
class Regie(models.Model):
label = models.CharField(_('Label'), max_length=150)
@ -248,6 +301,11 @@ class Regie(models.Model):
regie_id=self.pk,
)
def get_payer_data(self, request, payer_external_id):
if not self.payer:
raise PayerError(details={'reason': 'missing-payer'})
return self.payer.get_payer_data(request, payer_external_id)
class Campaign(models.Model):
label = models.CharField(_('Label'), max_length=150)

View File

@ -76,7 +76,7 @@ def get_invoice_lines_for_user(
if payer_external_id not in payer_data_cache:
if agenda_pricing:
# will raise a PricingError if payer_data can not be computed
payer_data_cache[payer_external_id] = agenda_pricing.get_payer_data(
payer_data_cache[payer_external_id] = pool.campaign.regie.get_payer_data(
request, payer_external_id
)
elif payer_data:
@ -143,10 +143,10 @@ def get_invoice_lines_for_user(
user_first_name=user_first_name,
user_last_name=user_last_name,
payer_external_id=payer_external_id,
payer_first_name=payer_data.get('payer_first_name') or '',
payer_last_name=payer_data.get('payer_last_name') or '',
payer_demat=payer_data.get('payer_demat') or False,
payer_direct_debit=payer_data.get('payer_direct_debit') or False,
payer_first_name=payer_data.get('first_name') or '',
payer_last_name=payer_data.get('last_name') or '',
payer_demat=payer_data.get('demat') or False,
payer_direct_debit=payer_data.get('direct_debit') or False,
event=serialized_event,
pricing_data=pricing_error,
status='warning' if isinstance(e, AgendaPricingNotFound) else 'error',
@ -167,10 +167,10 @@ def get_invoice_lines_for_user(
user_first_name=user_first_name,
user_last_name=user_last_name,
payer_external_id=payer_external_id,
payer_first_name=payer_data['payer_first_name'],
payer_last_name=payer_data['payer_last_name'],
payer_demat=payer_data['payer_demat'],
payer_direct_debit=payer_data['payer_direct_debit'],
payer_first_name=payer_data['first_name'],
payer_last_name=payer_data['last_name'],
payer_demat=payer_data['demat'],
payer_direct_debit=payer_data['direct_debit'],
event=serialized_event,
pricing_data=pricing_data,
status='success',
@ -206,10 +206,10 @@ def get_invoice_lines_for_user(
for injected_line in injected_lines:
payer_external_id = injected_line.payer_external_id
payer_data = {
'payer_first_name': injected_line.payer_first_name,
'payer_last_name': injected_line.payer_last_name,
'payer_demat': injected_line.payer_demat,
'payer_direct_debit': injected_line.payer_direct_debit,
'first_name': injected_line.payer_first_name,
'last_name': injected_line.payer_last_name,
'demat': injected_line.payer_demat,
'direct_debit': injected_line.payer_direct_debit,
}
payer_data = get_cached_payer_data(request, payer_external_id, payer_data=payer_data)
lines.append(
@ -224,10 +224,10 @@ def get_invoice_lines_for_user(
user_first_name=user_first_name,
user_last_name=user_last_name,
payer_external_id=payer_external_id,
payer_first_name=payer_data['payer_first_name'],
payer_last_name=payer_data['payer_last_name'],
payer_demat=payer_data['payer_demat'],
payer_direct_debit=payer_data['payer_direct_debit'],
payer_first_name=payer_data['first_name'],
payer_last_name=payer_data['last_name'],
payer_demat=payer_data['demat'],
payer_direct_debit=payer_data['direct_debit'],
status='success',
pool=pool,
from_injected_line=injected_line,

View File

@ -254,45 +254,6 @@ class Pricing(models.Model):
def get_payer_external_id_from_nameid(self, request, original_context):
return self.get_payer_external_id(request, original_context, key='payer_external_id_from_nameid')
def get_payer_data(self, request, original_context):
result = {}
context = RequestContext(request)
context.push(original_context)
bool_keys = ['payer_demat', 'payer_direct_debit']
for key in self.get_payer_variables_keys():
if key in ['payer_external_id', 'payer_external_id_from_nameid']:
continue
tplt = self.payer_variables.get(key) or ''
if not tplt:
if key not in bool_keys:
raise PayerDataError(
details={'key': key.removeprefix('payer_'), 'reason': 'empty-template'}
)
tplt = 'False'
try:
value = Template(tplt).render(context)
if not value:
if key not in bool_keys:
raise PayerDataError(
details={'key': key.removeprefix('payer_'), 'reason': 'empty-result'}
)
value = False
if key in bool_keys:
if value in ('True', 'true', '1'):
value = True
elif value in ('False', 'false', '0'):
value = False
else:
raise PayerDataError(
details={'key': key.removeprefix('payer_'), 'reason': 'not-a-boolean'}
)
result[key] = value
except TemplateSyntaxError:
raise PayerDataError(details={'key': key.removeprefix('payer_'), 'reason': 'syntax-error'})
except VariableDoesNotExist:
raise PayerDataError(details={'key': key.removeprefix('payer_'), 'reason': 'variable-error'})
return result
@classmethod
def import_json(cls, data):
data = data.copy()
@ -478,12 +439,6 @@ class AgendaPricing(models.Model):
context = {'nameid': nameid}
return self.pricing.get_payer_external_id_from_nameid(request, context)
def get_payer_data(self, request, payer_external_id):
context = {'payer_external_id': payer_external_id}
if ':' in payer_external_id:
context['payer_external_raw_id'] = payer_external_id.split(':')[1]
return self.pricing.get_payer_data(request, context)
def get_pricing_data(self, request, pricing_date, user_external_id, payer_external_id):
# compute pricing for flat_fee_schedule mode
data = {

View File

@ -1,60 +1,14 @@
import copy
import json
import re
import urllib.parse
from unittest import mock
import pytest
from lingo.invoicing.models import Payer, Regie
from tests.invoicing.utils import mocked_requests_send
from tests.utils import login
pytestmark = pytest.mark.django_db
WCS_CARDDEFS_DATA = [
{'title': 'Card Model 1', 'slug': 'card_model_1', 'custom_views': [{'id': 'foo', 'text': 'bar'}]},
{'title': 'Card Model 2', 'slug': 'card_model_2'},
{'title': 'Card Model 3', 'slug': 'card_model_3'},
]
WCS_CARDDEF_SCHEMAS = {
'card_model_1': {
'name': 'Card Model 1',
'fields': [
{'label': 'Field A', 'varname': 'fielda', 'type': 'string'},
{'label': 'Field B', 'varname': 'fieldb', 'type': 'string'},
],
}
}
class MockedRequestResponse(mock.Mock):
status_code = 200
def json(self):
return json.loads(self.content)
def get_data_from_url(url):
if '/api/cards/@list' in url:
return WCS_CARDDEFS_DATA
m_schema = re.match(r'/api/cards/([a-z0-9_]+)/@schema', url)
if m_schema:
return WCS_CARDDEF_SCHEMAS.get(m_schema.group(1)) or {}
return []
def mocked_requests_send(request, **kwargs):
request_url = urllib.parse.urlparse(request.url)
data = copy.deepcopy(get_data_from_url(request_url.path))
if not isinstance(data, list):
return MockedRequestResponse(content=json.dumps(data))
return MockedRequestResponse(content=json.dumps({'data': data}))
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
def test_add_payer(mock_send, app, admin_user):
app = login(app)

View File

@ -526,10 +526,10 @@ def test_get_invoice_lines_for_user_check_status(
pool=pool,
payer_data_cache={
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
},
)
@ -844,7 +844,7 @@ def test_get_invoice_lines_for_user_get_payer_id_error(mock_payer, mock_status):
@mock.patch('lingo.invoicing.utils.get_check_status')
@mock.patch('lingo.pricing.models.AgendaPricing.get_payer_external_id')
@mock.patch('lingo.pricing.models.AgendaPricing.get_payer_data')
@mock.patch('lingo.invoicing.models.Regie.get_payer_data')
def test_get_invoice_lines_for_user_get_payer_data_error(mock_payer_data, mock_payer, mock_status):
regie = Regie.objects.create(label='Regie')
agenda = Agenda.objects.create(label='Agenda')
@ -971,21 +971,21 @@ def test_get_invoice_lines_for_user_get_payer_id_and_data(mock_status):
def get_payer_data(ap, r, payer_external_id):
return {
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
},
'payer:2': {
'payer_first_name': 'First2',
'payer_last_name': 'Last2',
'payer_demat': False,
'payer_direct_debit': True,
'first_name': 'First2',
'last_name': 'Last2',
'demat': False,
'direct_debit': True,
},
}.get(payer_external_id)
payer_patch = mock.patch.object(AgendaPricing, 'get_payer_external_id', autospec=True)
payer_data_patch = mock.patch.object(AgendaPricing, 'get_payer_data', autospec=True)
payer_data_patch = mock.patch.object(Regie, 'get_payer_data', autospec=True)
with payer_patch as mock_payer, payer_data_patch as mock_payer_data:
mock_payer.side_effect = lambda *args: get_payer(*args)
mock_payer_data.side_effect = lambda *args: get_payer_data(*args)
@ -1033,16 +1033,16 @@ def test_get_invoice_lines_for_user_get_payer_id_and_data(mock_status):
# cache is populated
assert payer_data_cache == {
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
},
'payer:2': {
'payer_first_name': 'First2',
'payer_last_name': 'Last2',
'payer_demat': False,
'payer_direct_debit': True,
'first_name': 'First2',
'last_name': 'Last2',
'demat': False,
'direct_debit': True,
},
}
assert mock_payer.call_args_list == [
@ -1051,8 +1051,8 @@ def test_get_invoice_lines_for_user_get_payer_id_and_data(mock_status):
mock.call(agenda_pricing, mock.ANY, 'user:1'),
]
assert mock_payer_data.call_args_list == [
mock.call(agenda_pricing, mock.ANY, 'payer:1'),
mock.call(agenda_pricing, mock.ANY, 'payer:2'),
mock.call(regie, mock.ANY, 'payer:1'),
mock.call(regie, mock.ANY, 'payer:2'),
# only 2 calls, payer:1 is cached after first call
]
@ -1113,20 +1113,20 @@ def test_get_invoice_lines_for_user_get_payer_id_and_data(mock_status):
# cache is populated
assert payer_data_cache == {
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
},
}
# but take in cache if present
payer_data_cache = {
'payer:1': {
'payer_first_name': 'First1 IN CACHE',
'payer_last_name': 'Last1 IN CACHE',
'payer_demat': False,
'payer_direct_debit': True,
'first_name': 'First1 IN CACHE',
'last_name': 'Last1 IN CACHE',
'demat': False,
'direct_debit': True,
},
}
@ -1218,10 +1218,10 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_payer
pool=pool,
payer_data_cache={
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
},
)
@ -1253,10 +1253,10 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_payer
pool=pool,
payer_data_cache={
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
},
)
@ -1287,10 +1287,10 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_payer
pool=pool,
payer_data_cache={
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
},
)
@ -1399,10 +1399,10 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(
pool=pool,
payer_data_cache={
'payer:1': {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
},
)
@ -1570,7 +1570,7 @@ def test_get_all_invoice_lines(mock_user_lines):
@mock.patch('lingo.invoicing.utils.get_check_status')
@mock.patch('lingo.pricing.models.AgendaPricing.get_payer_external_id')
@mock.patch('lingo.pricing.models.AgendaPricing.get_payer_data')
@mock.patch('lingo.invoicing.models.Regie.get_payer_data')
def test_get_all_invoice_lines_queryset(mock_payer_data, mock_payer, mock_status):
# don't mock get_pricing_data_for_event to check all querysets
category1 = CriteriaCategory.objects.create(label='Foo1', slug='foo1')
@ -1675,10 +1675,10 @@ def test_get_all_invoice_lines_queryset(mock_payer_data, mock_payer, mock_status
]
mock_payer.return_value = 'payer:1'
mock_payer_data.return_value = {
'payer_first_name': 'First1',
'payer_last_name': 'Last1',
'payer_demat': True,
'payer_direct_debit': False,
'first_name': 'First1',
'last_name': 'Last1',
'demat': True,
'direct_debit': False,
}
with CaptureQueriesContext(connection) as ctx:

View File

@ -1,8 +1,12 @@
import datetime
from unittest import mock
import pytest
from django.db import transaction
from django.db.utils import InternalError
from django.template import Context
from django.test.client import RequestFactory
from publik_django_templatetags.wcs.context_processors import Cards
from lingo.invoicing.models import (
Campaign,
@ -12,14 +16,28 @@ from lingo.invoicing.models import (
Invoice,
InvoiceLine,
InvoicePayment,
Payer,
PayerDataError,
PayerError,
Payment,
Pool,
Regie,
)
from tests.invoicing.utils import mocked_requests_send
pytestmark = pytest.mark.django_db
@pytest.fixture
def context():
return Context(
{
'cards': Cards(),
'request': RequestFactory().get('/'),
}
)
@pytest.mark.parametrize('draft', [True, False])
@pytest.mark.parametrize('orphan', [True, False])
def test_invoice_total_amount(draft, orphan):
@ -416,3 +434,67 @@ def test_regie_format_number():
regie.save()
assert regie.format_number(datetime.date(2023, 2, 15), 42) == 'Ffoobar-2023-00000042'
assert regie.format_number(datetime.date(2024, 12, 15), 42000000) == 'Ffoobar-2024-42000000'
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
def test_get_payer_data(mock_send, context, nocache):
payer = Payer.objects.create(label='Payer')
regie = Regie.objects.create(label='Regie')
with pytest.raises(PayerError) as e:
regie.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'reason': 'missing-payer'}
regie.payer = payer
regie.save()
with pytest.raises(PayerError) as e:
regie.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'reason': 'missing-card-model'}
payer.carddef_reference = 'default:card_model_1'
payer.save()
original_variables = {
'first_name': 'fielda',
'last_name': 'fielda',
'demat': 'fieldb',
'direct_debit': 'fieldb',
}
payer_data = {
'first_name': 'foo',
'last_name': 'foo',
'demat': True,
'direct_debit': True,
}
payer.user_fields_mapping = original_variables.copy()
payer.save()
assert regie.get_payer_data(request=context['request'], payer_external_id='payer:42') == payer_data
assert '/api/cards/card_model_1/list?' in mock_send.call_args_list[-1][0][0].url
assert (
'&filter-internal-id=42&filter-internal-id-operator=eq&include-fields=on'
in mock_send.call_args_list[-1][0][0].url
)
for key in ['first_name', 'last_name']:
payer.user_fields_mapping = original_variables.copy()
payer.user_fields_mapping[key] = ''
payer.save()
with pytest.raises(PayerDataError) as e:
regie.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'key': key, 'reason': 'not-defined'}
for key in ['demat', 'direct_debit']:
payer.user_fields_mapping = original_variables.copy()
payer.user_fields_mapping[key] = ''
payer.save()
data_result = payer_data.copy()
data_result[key] = False
assert regie.get_payer_data(request=context['request'], payer_external_id='payer:42') == data_result
payer.user_fields_mapping = original_variables.copy()
payer.user_fields_mapping[key] = 'fielda'
payer.save()
with pytest.raises(PayerDataError) as e:
regie.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'key': key, 'reason': 'not-a-boolean'}

66
tests/invoicing/utils.py Normal file
View File

@ -0,0 +1,66 @@
import copy
import json
import re
import urllib.parse
from unittest import mock
WCS_CARDDEFS_DATA = [
{'title': 'Card Model 1', 'slug': 'card_model_1', 'custom_views': [{'id': 'foo', 'text': 'bar'}]},
{'title': 'Card Model 2', 'slug': 'card_model_2'},
{'title': 'Card Model 3', 'slug': 'card_model_3'},
]
WCS_CARDDEF_SCHEMAS = {
'card_model_1': {
'name': 'Card Model 1',
'fields': [
{'label': 'Field A', 'varname': 'fielda', 'type': 'string'},
{'label': 'Field B', 'varname': 'fieldb', 'type': 'bool'},
],
}
}
WCS_CARDS_DATA = {
'card_model_1': [
{
'id': 42,
'display_id': '10-42',
'display_name': 'Card Model 1 - n°10-42',
'digest': 'a a a',
'text': 'aa',
'fields': {
'fielda': 'foo',
'fieldb': True,
},
},
]
}
class MockedRequestResponse(mock.Mock):
status_code = 200
def json(self):
return json.loads(self.content)
def get_data_from_url(url):
if '/api/cards/@list' in url:
return WCS_CARDDEFS_DATA
m_schema = re.match(r'/api/cards/([a-z0-9_]+)/@schema', url)
if m_schema:
return WCS_CARDDEF_SCHEMAS.get(m_schema.group(1)) or {}
m_list = re.match(r'/api/cards/([a-z0-9_]+)/list', url)
if m_list:
return WCS_CARDS_DATA.get(m_list.group(1)) or []
return []
def mocked_requests_send(request, **kwargs):
request_url = urllib.parse.urlparse(request.url)
data = copy.deepcopy(get_data_from_url(request_url.path))
if not isinstance(data, list):
return MockedRequestResponse(content=json.dumps(data))
return MockedRequestResponse(content=json.dumps({'data': data}))

View File

@ -1792,114 +1792,3 @@ def test_get_payer_external_id_from_nameid(mock_send, context, nocache):
mock_send.reset_mock()
agenda_pricing.get_payer_external_id_from_nameid(request=context['request'], nameid='foobar')
assert 'filter-user-uuid=foobar&' in mock_send.call_args_list[0][0][0].url
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
def test_get_payer_data(mock_send, context, nocache):
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
original_variables = {
'payer_first_name': 'First',
'payer_last_name': 'Last',
'payer_demat': 'False',
'payer_direct_debit': 'True',
}
payer_data = {
'payer_first_name': 'First',
'payer_last_name': 'Last',
'payer_demat': False,
'payer_direct_debit': True,
}
for key in ['payer_first_name', 'payer_last_name']:
values = [
('bar', 'bar'),
('{{ 40|add:2 }}', '42'),
('{{ cards|objects:"foo"|first|get:"id" }}', '1'),
]
for value, result in values:
pricing.payer_variables = original_variables.copy()
pricing.payer_variables[key] = value
pricing.save()
data_result = payer_data.copy()
data_result[key] = result
assert (
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='payer:42')
== data_result
)
values = [
('', 'empty-template'),
('{{ "" }}', 'empty-result'),
('{% for %}', 'syntax-error'),
('{{ "foo"|add:user.email }}', 'variable-error'),
]
for value, error in values:
pricing.payer_variables = original_variables.copy()
pricing.payer_variables[key] = value
pricing.save()
with pytest.raises(PayerDataError) as e:
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'key': key.removeprefix('payer_'), 'reason': error}
for key in ['payer_demat', 'payer_direct_debit']:
values = [
('', False),
('True', True),
('true', True),
('1', True),
('False', False),
('false', False),
('0', False),
('{{ cards|objects:"foo"|first|get:"fields"|get:"bar" }}', False),
('{{ cards|objects:"foo"|last|get:"fields"|get:"bar" }}', True),
]
for value, result in values:
pricing.payer_variables = original_variables.copy()
pricing.payer_variables[key] = value
pricing.save()
data_result = payer_data.copy()
data_result[key] = result
assert (
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='payer:42')
== data_result
)
values = [
('{% for %}', 'syntax-error'),
('{{ "foo"|add:user.email }}', 'variable-error'),
('{{ cards|objects:"foo"|last|get:"fields"|get:"foo" }}', 'not-a-boolean'),
]
for value, error in values:
pricing.payer_variables = original_variables.copy()
pricing.payer_variables[key] = value
pricing.save()
with pytest.raises(PayerDataError) as e:
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='payer:42')
assert e.value.details == {'key': key.removeprefix('payer_'), 'reason': error}
# payer_external_id can be used in variables
pricing.payer_variables = original_variables.copy()
pricing.payer_variables.update(
{
'payer_first_name': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:payer_external_id|first|get:"id" }}',
}
)
pricing.save()
mock_send.reset_mock()
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='child:42')
assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
pricing.payer_variables.update(
{
'payer_first_name': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:payer_external_raw_id|first|get:"id" }}',
}
)
pricing.save()
mock_send.reset_mock()
agenda_pricing.get_payer_data(request=context['request'], payer_external_id='child:42')
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url