mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Overhaul of our check-in features (#1647)
This commit is contained in:
@@ -125,7 +125,10 @@ TEST_LIST_RES = {
|
||||
"position_count": 0,
|
||||
"checkin_count": 0,
|
||||
"include_pending": False,
|
||||
"subevent": None
|
||||
"allow_multiple_entries": False,
|
||||
"allow_entry_after_exit": True,
|
||||
"subevent": None,
|
||||
"rules": {}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +190,8 @@ def test_list_create(token_client, organizer, event, item, item_on_wrong_event):
|
||||
"name": "VIP",
|
||||
"limit_products": [item.pk],
|
||||
"all_products": False,
|
||||
"subevent": None
|
||||
"subevent": None,
|
||||
"rules": {"==": [0, 1]}
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
@@ -197,6 +201,7 @@ def test_list_create(token_client, organizer, event, item, item_on_wrong_event):
|
||||
assert cl.name == "VIP"
|
||||
assert cl.limit_products.count() == 1
|
||||
assert not cl.all_products
|
||||
assert cl.rules == {"==": [0, 1]}
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug),
|
||||
@@ -275,8 +280,7 @@ def test_list_create_with_subevent(token_client, organizer, event, event3, item,
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Subevent cannot be null for event series."]}'
|
||||
assert resp.status_code == 201
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug),
|
||||
@@ -372,7 +376,8 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
||||
{
|
||||
'list': clist_all.pk,
|
||||
'datetime': c.datetime.isoformat().replace('+00:00', 'Z'),
|
||||
'auto_checked_in': False
|
||||
'auto_checked_in': False,
|
||||
'type': 'entry',
|
||||
}
|
||||
]
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?has_checkin=1'.format(
|
||||
@@ -410,7 +415,8 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
||||
{
|
||||
'list': clist_all.pk,
|
||||
'datetime': c.datetime.isoformat().replace('+00:00', 'Z'),
|
||||
'auto_checked_in': False
|
||||
'auto_checked_in': False,
|
||||
'type': 'entry',
|
||||
}
|
||||
]
|
||||
resp = token_client.get(
|
||||
@@ -450,6 +456,39 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
||||
assert [p2, p1] == resp.data['results']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_all_items_positions_by_subevent(token_client, organizer, event, clist, clist_all, item, other_item, order, subevent):
|
||||
with scopes_disabled():
|
||||
se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
pfirst = order.positions.first()
|
||||
pfirst.subevent = se2
|
||||
pfirst.save()
|
||||
p1 = dict(TEST_ORDERPOSITION1_RES)
|
||||
p1["id"] = pfirst.pk
|
||||
p1["subevent"] = se2.pk
|
||||
p1["item"] = item.pk
|
||||
plast = order.positions.last()
|
||||
plast.subevent = subevent
|
||||
plast.save()
|
||||
p2 = dict(TEST_ORDERPOSITION2_RES)
|
||||
p2["id"] = plast.pk
|
||||
p2["item"] = other_item.pk
|
||||
p2["subevent"] = subevent.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
|
||||
organizer.slug, event.slug, clist_all.pk
|
||||
))
|
||||
assert resp.status_code == 200
|
||||
assert [p1, p2] == resp.data['results']
|
||||
|
||||
clist_all.subevent = subevent
|
||||
clist_all.save()
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
|
||||
organizer.slug, event.slug, clist_all.pk
|
||||
))
|
||||
assert resp.status_code == 200
|
||||
assert [p2] == resp.data['results']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_limited_items_positions(token_client, organizer, event, clist, item, order):
|
||||
p1 = dict(TEST_ORDERPOSITION1_RES)
|
||||
@@ -607,6 +646,46 @@ def test_reupload_same_nonce(token_client, organizer, clist, event, order):
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_allow_multiple(token_client, organizer, clist, event, order):
|
||||
clist.allow_multiple_entries = True
|
||||
clist.save()
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {}, format='json')
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {}, format='json')
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
assert p.checkins.count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_allow_multiple_reupload_same_nonce(token_client, organizer, clist, event, order):
|
||||
clist.allow_multiple_entries = True
|
||||
clist.save()
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {'nonce': 'foobar'}, format='json')
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {'nonce': 'foobar'}, format='json')
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
assert p.checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_multiple_different_list(token_client, organizer, clist, clist_all, event, order):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -783,7 +783,7 @@ def test_orderposition_list(token_client, organizer, event, order, item, subeven
|
||||
with scopes_disabled():
|
||||
cl = event.checkin_lists.create(name="Default")
|
||||
op.checkins.create(datetime=datetime.datetime(2017, 12, 26, 10, 0, 0, tzinfo=UTC), list=cl)
|
||||
res['checkins'] = [{'datetime': '2017-12-26T10:00:00Z', 'list': cl.pk, 'auto_checked_in': False}]
|
||||
res['checkins'] = [{'datetime': '2017-12-26T10:00:00Z', 'list': cl.pk, 'auto_checked_in': False, 'type': 'entry'}]
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=true'.format(organizer.slug, event.slug))
|
||||
assert [res] == resp.data['results']
|
||||
|
||||
602
src/tests/base/test_checkin.py
Normal file
602
src/tests/base/test_checkin.py
Normal file
@@ -0,0 +1,602 @@
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
from freezegun import freeze_time
|
||||
|
||||
from pretix.base.models import Checkin, Event, Order, OrderPosition, Organizer
|
||||
from pretix.base.services.checkin import (
|
||||
CheckInError, RequiredQuestionsError, perform_checkin,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def event():
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(),
|
||||
plugins='pretix.plugins.banktransfer'
|
||||
)
|
||||
with scope(organizer=o):
|
||||
yield event
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clist(event):
|
||||
c = event.checkin_lists.create(name="Default", all_products=True)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def item(event):
|
||||
return event.items.create(name="Ticket", default_price=3, admission=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def position(event, item):
|
||||
order = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID, locale='en',
|
||||
datetime=now() - timedelta(days=4),
|
||||
expires=now() - timedelta(hours=4) + timedelta(days=10),
|
||||
total=Decimal('23.00'),
|
||||
)
|
||||
return OrderPosition.objects.create(
|
||||
order=order, item=item, variation=None,
|
||||
price=Decimal("23.00"), attendee_name_parts={"full_name": "Peter"}, positionid=1
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_valid(position, clist):
|
||||
perform_checkin(position, clist, {})
|
||||
assert position.checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_canceled_order(position, clist):
|
||||
o = position.order
|
||||
o.status = Order.STATUS_CANCELED
|
||||
o.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'unpaid'
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {}, canceled_supported=True)
|
||||
assert excinfo.value.code == 'canceled'
|
||||
assert position.checkins.count() == 0
|
||||
|
||||
o.status = Order.STATUS_EXPIRED
|
||||
o.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {}, canceled_supported=True)
|
||||
assert excinfo.value.code == 'canceled'
|
||||
assert position.checkins.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_canceled_position(position, clist):
|
||||
position.canceled = True
|
||||
position.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'unpaid'
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {}, canceled_supported=True)
|
||||
assert excinfo.value.code == 'canceled'
|
||||
assert position.checkins.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_invalid_product(position, clist):
|
||||
clist.all_products = False
|
||||
clist.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'product'
|
||||
clist.limit_products.add(position.item)
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_invalid_subevent(position, clist, event):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="Foo", date_from=event.date_from)
|
||||
se2 = event.subevents.create(name="Foo", date_from=event.date_from)
|
||||
position.subevent = se1
|
||||
position.save()
|
||||
clist.subevent = se2
|
||||
clist.save()
|
||||
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'product'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_checkin_all_subevents(position, clist, event):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="Foo", date_from=event.date_from)
|
||||
position.subevent = se1
|
||||
position.save()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unpaid(position, clist):
|
||||
o = position.order
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'unpaid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unpaid_include_pending_ignore(position, clist):
|
||||
o = position.order
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
clist.include_pending = True
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {}, ignore_unpaid=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unpaid_ignore_without_include_pendung(position, clist):
|
||||
o = position.order
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'unpaid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unpaid_force(position, clist):
|
||||
o = position.order
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
perform_checkin(position, clist, {}, force=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_required_question_missing(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
with pytest.raises(RequiredQuestionsError) as excinfo:
|
||||
perform_checkin(position, clist, {}, questions_supported=True)
|
||||
assert excinfo.value.code == 'incomplete'
|
||||
assert excinfo.value.questions == [q]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_required_question_missing_but_not_supported(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
perform_checkin(position, clist, {}, questions_supported=False)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_required_question_missing_but_forced(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
perform_checkin(position, clist, {}, questions_supported=True, force=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_optional_question_missing(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=False,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
with pytest.raises(RequiredQuestionsError) as excinfo:
|
||||
perform_checkin(position, clist, {}, questions_supported=True)
|
||||
assert excinfo.value.code == 'incomplete'
|
||||
assert excinfo.value.questions == [q]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_required_online_question_missing(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=False,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
perform_checkin(position, clist, {}, questions_supported=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_question_filled_previously(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
position.answers.create(question=q, answer='Foo')
|
||||
perform_checkin(position, clist, {}, questions_supported=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_question_filled(event, position, clist):
|
||||
q = event.questions.create(
|
||||
question="Quo vadis?",
|
||||
type="S",
|
||||
required=True,
|
||||
ask_during_checkin=True,
|
||||
)
|
||||
q.items.add(position.item)
|
||||
perform_checkin(position, clist, {q: 'Foo'}, questions_supported=True)
|
||||
a = position.answers.get()
|
||||
assert a.question == q
|
||||
assert a.answer == 'Foo'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry(position, clist):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'already_redeemed'
|
||||
|
||||
assert position.checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry_repeat_nonce(position, clist):
|
||||
perform_checkin(position, clist, {}, nonce='foo')
|
||||
perform_checkin(position, clist, {}, nonce='foo')
|
||||
|
||||
assert position.checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_multi_entry(position, clist):
|
||||
clist.allow_multiple_entries = True
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
assert position.checkins.count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_multi_entry_repeat_nonce(position, clist):
|
||||
clist.allow_multiple_entries = True
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {}, nonce='foo')
|
||||
perform_checkin(position, clist, {}, nonce='foo')
|
||||
|
||||
assert position.checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry_forced_reentry(position, clist):
|
||||
perform_checkin(position, clist, {}, force=True)
|
||||
|
||||
perform_checkin(position, clist, {}, force=True, nonce='bla')
|
||||
perform_checkin(position, clist, {}, force=True, nonce='bla')
|
||||
|
||||
assert position.checkins.count() == 2
|
||||
assert not position.checkins.last().forced
|
||||
assert position.checkins.first().forced
|
||||
assert position.order.all_logentries().count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_multi_exit(position, clist):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
|
||||
assert position.checkins.count() == 3
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry_after_exit_ordered_by_date(position, clist):
|
||||
dt1 = now() - timedelta(minutes=10)
|
||||
dt2 = now() - timedelta(minutes=5)
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT, datetime=dt2)
|
||||
time.sleep(1)
|
||||
perform_checkin(position, clist, {}, datetime=dt1)
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
assert position.checkins.count() == 3
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry_after_exit(position, clist):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
assert position.checkins.count() == 3
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_single_entry_after_exit_forbidden(position, clist):
|
||||
clist.allow_entry_after_exit = False
|
||||
clist.save()
|
||||
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'already_redeemed'
|
||||
|
||||
assert position.checkins.count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_simple(position, clist):
|
||||
clist.rules = {'and': [False, True]}
|
||||
clist.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type='exit')
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
clist.rules = {'and': [True, True]}
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_product(event, position, clist):
|
||||
i2 = event.items.create(name="Ticket", default_price=3, admission=True)
|
||||
clist.rules = {
|
||||
"inList": [
|
||||
{"var": "product"}, {
|
||||
"objectList": [
|
||||
{"lookup": ["product", str(i2.pk), "Ticket"]},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
clist.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
clist.rules = {
|
||||
"inList": [
|
||||
{"var": "product"}, {
|
||||
"objectList": [
|
||||
{"lookup": ["product", str(i2.pk), "Ticket"]},
|
||||
{"lookup": ["product", str(position.item.pk), "Ticket"]},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_variation(item, position, clist):
|
||||
v1 = item.variations.create(value="A")
|
||||
v2 = item.variations.create(value="B")
|
||||
position.variation = v2
|
||||
position.save()
|
||||
clist.rules = {
|
||||
"inList": [
|
||||
{"var": "variation"}, {
|
||||
"objectList": [
|
||||
{"lookup": ["variation", str(v1.pk), "Ticket – A"]},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
clist.save()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
clist.rules = {
|
||||
"inList": [
|
||||
{"var": "variation"}, {
|
||||
"objectList": [
|
||||
{"lookup": ["variation", str(v1.pk), "Ticket – A"]},
|
||||
{"lookup": ["variation", str(v2.pk), "Ticket – B"]},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_scan_number(position, clist):
|
||||
# Ticket is valid three times
|
||||
clist.allow_multiple_entries = True
|
||||
clist.rules = {"<": [{"var": "entries_number"}, 3]}
|
||||
clist.save()
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
perform_checkin(position, clist, {})
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_scan_today(event, position, clist):
|
||||
# Ticket is valid three times per day
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
clist.allow_multiple_entries = True
|
||||
clist.rules = {"<": [{"var": "entries_today"}, 3]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 10:00:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||
perform_checkin(position, clist, {})
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 22:50:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 23:10:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_scan_days(event, position, clist):
|
||||
# Ticket is valid unlimited times, but only on two arbitrary days
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
clist.allow_multiple_entries = True
|
||||
clist.rules = {"or": [{">": [{"var": "entries_today"}, 0]}, {"<": [{"var": "entries_days"}, 2]}]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 10:00:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
with freeze_time("2020-01-03 10:00:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
with freeze_time("2020-01-03 22:50:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
with freeze_time("2020-01-03 23:50:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_time_isafter_tolerance(event, position, clist):
|
||||
# Ticket is valid starting 10 minutes before admission time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
event.date_admission = event.timezone.localize(datetime(2020, 1, 1, 12, 0, 0))
|
||||
event.save()
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}, 10]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 10:45:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 10:51:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_time_isafter_no_tolerance(event, position, clist):
|
||||
# Ticket is valid only after admission time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
event.date_from = event.timezone.localize(datetime(2020, 1, 1, 12, 0, 0))
|
||||
# also tests that date_admission falls back to date_from
|
||||
event.save()
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 10:51:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 11:01:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_time_isbefore_with_tolerance(event, position, clist):
|
||||
# Ticket is valid until 10 minutes after end time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
event.date_to = event.timezone.localize(datetime(2020, 1, 1, 12, 0, 0))
|
||||
event.save()
|
||||
clist.rules = {"isBefore": [{"var": "now"}, {"buildTime": ["date_to"]}, 10]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 11:11:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 11:09:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_time_isafter_custom_time(event, position, clist):
|
||||
# Ticket is valid starting at a custom time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T22:00:00.000Z"]}, None]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 21:55:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 22:05:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_isafter_subevent(position, clist, event):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
se1 = event.subevents.create(name="Foo", date_from=event.timezone.localize(datetime(2020, 2, 1, 12, 0, 0)))
|
||||
position.subevent = se1
|
||||
position.save()
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}]}
|
||||
clist.save()
|
||||
with freeze_time("2020-02-01 10:51:00"):
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-02-01 11:01:00"):
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_position_queries(django_assert_num_queries, position, clist):
|
||||
with django_assert_num_queries(11) as captured:
|
||||
perform_checkin(position, clist, {})
|
||||
if 'sqlite' not in settings.DATABASES['default']['ENGINE']:
|
||||
assert any('FOR UPDATE' in s['sql'] for s in captured)
|
||||
@@ -1707,7 +1707,16 @@ class EventTest(TestCase):
|
||||
que1.items.add(i1)
|
||||
event1.settings.foo_setting = 23
|
||||
event1.settings.tax_rate_default = tr7
|
||||
cl1 = event1.checkin_lists.create(name="All", all_products=False)
|
||||
cl1 = event1.checkin_lists.create(
|
||||
name="All", all_products=False,
|
||||
rules={
|
||||
"and": [
|
||||
{"isBefore": [{"var": "now"}, {"buildTime": ["date_from"]}, None]},
|
||||
{"inList": [{"var": "product"}, {"objectList": [{"lookup": ["product", str(i1.pk), "Text"]}]}]},
|
||||
{"inList": [{"var": "variation"}, {"objectList": [{"lookup": ["variation", str(v1.pk), "Text"]}]}]}
|
||||
]
|
||||
}
|
||||
)
|
||||
cl1.limit_products.add(i1)
|
||||
|
||||
event2 = Event.objects.create(
|
||||
@@ -1739,7 +1748,15 @@ class EventTest(TestCase):
|
||||
assert event2.settings.foo_setting == '23'
|
||||
assert event2.settings.tax_rate_default == trnew
|
||||
assert event2.checkin_lists.count() == 1
|
||||
assert [i.pk for i in event2.checkin_lists.first().limit_products.all()] == [i1new.pk]
|
||||
clnew = event2.checkin_lists.first()
|
||||
assert [i.pk for i in clnew.limit_products.all()] == [i1new.pk]
|
||||
assert clnew.rules == {
|
||||
"and": [
|
||||
{"isBefore": [{"var": "now"}, {"buildTime": ["date_from"]}, None]},
|
||||
{"inList": [{"var": "product"}, {"objectList": [{"lookup": ["product", str(i1new.pk), "Text"]}]}]},
|
||||
{"inList": [{"var": "variation"}, {"objectList": [{"lookup": ["variation", str(i1new.variations.get().pk), "Text"]}]}]}
|
||||
]
|
||||
}
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_presale_has_ended(self):
|
||||
|
||||
519
src/tests/helpers/jsonlogic-tests.json
Normal file
519
src/tests/helpers/jsonlogic-tests.json
Normal file
@@ -0,0 +1,519 @@
|
||||
[
|
||||
"# Non-rules get passed through",
|
||||
[ true, {}, true ],
|
||||
[ false, {}, false ],
|
||||
[ 17, {}, 17 ],
|
||||
[ 3.14, {}, 3.14 ],
|
||||
[ "apple", {}, "apple" ],
|
||||
[ null, {}, null ],
|
||||
[ ["a","b"], {}, ["a","b"] ],
|
||||
|
||||
"# Single operator tests",
|
||||
[ {"==":[1,1]}, {}, true ],
|
||||
[ {"==":[1,"1"]}, {}, true ],
|
||||
[ {"==":[1,2]}, {}, false ],
|
||||
[ {"===":[1,1]}, {}, true ],
|
||||
[ {"===":[1,"1"]}, {}, false ],
|
||||
[ {"===":[1,2]}, {}, false ],
|
||||
[ {"!=":[1,2]}, {}, true ],
|
||||
[ {"!=":[1,1]}, {}, false ],
|
||||
[ {"!=":[1,"1"]}, {}, false ],
|
||||
[ {"!==":[1,2]}, {}, true ],
|
||||
[ {"!==":[1,1]}, {}, false ],
|
||||
[ {"!==":[1,"1"]}, {}, true ],
|
||||
[ {">":[2,1]}, {}, true ],
|
||||
[ {">":[1,1]}, {}, false ],
|
||||
[ {">":[1,2]}, {}, false ],
|
||||
[ {">":["2",1]}, {}, true ],
|
||||
[ {">=":[2,1]}, {}, true ],
|
||||
[ {">=":[1,1]}, {}, true ],
|
||||
[ {">=":[1,2]}, {}, false ],
|
||||
[ {">=":["2",1]}, {}, true ],
|
||||
[ {"<":[2,1]}, {}, false ],
|
||||
[ {"<":[1,1]}, {}, false ],
|
||||
[ {"<":[1,2]}, {}, true ],
|
||||
[ {"<":["1",2]}, {}, true ],
|
||||
[ {"<":[1,2,3]}, {}, true ],
|
||||
[ {"<":[1,1,3]}, {}, false ],
|
||||
[ {"<":[1,4,3]}, {}, false ],
|
||||
[ {"<=":[2,1]}, {}, false ],
|
||||
[ {"<=":[1,1]}, {}, true ],
|
||||
[ {"<=":[1,2]}, {}, true ],
|
||||
[ {"<=":["1",2]}, {}, true ],
|
||||
[ {"<=":[1,2,3]}, {}, true ],
|
||||
[ {"<=":[1,4,3]}, {}, false ],
|
||||
[ {"!":[false]}, {}, true ],
|
||||
[ {"!":false}, {}, true ],
|
||||
[ {"!":[true]}, {}, false ],
|
||||
[ {"!":true}, {}, false ],
|
||||
[ {"!":0}, {}, true ],
|
||||
[ {"!":1}, {}, false ],
|
||||
[ {"or":[true,true]}, {}, true ],
|
||||
[ {"or":[false,true]}, {}, true ],
|
||||
[ {"or":[true,false]}, {}, true ],
|
||||
[ {"or":[false,false]}, {}, false ],
|
||||
[ {"or":[false,false,true]}, {}, true ],
|
||||
[ {"or":[false,false,false]}, {}, false ],
|
||||
[ {"or":[false]}, {}, false ],
|
||||
[ {"or":[true]}, {}, true ],
|
||||
[ {"or":[1,3]}, {}, 1 ],
|
||||
[ {"or":[3,false]}, {}, 3 ],
|
||||
[ {"or":[false,3]}, {}, 3 ],
|
||||
[ {"and":[true,true]}, {}, true ],
|
||||
[ {"and":[false,true]}, {}, false ],
|
||||
[ {"and":[true,false]}, {}, false ],
|
||||
[ {"and":[false,false]}, {}, false ],
|
||||
[ {"and":[true,true,true]}, {}, true ],
|
||||
[ {"and":[true,true,false]}, {}, false ],
|
||||
[ {"and":[false]}, {}, false ],
|
||||
[ {"and":[true]}, {}, true ],
|
||||
[ {"and":[1,3]}, {}, 3 ],
|
||||
[ {"and":[3,false]}, {}, false ],
|
||||
[ {"and":[false,3]}, {}, false ],
|
||||
[ {"?:":[true,1,2]}, {}, 1 ],
|
||||
[ {"?:":[false,1,2]}, {}, 2 ],
|
||||
[ {"in":["Bart",["Bart","Homer","Lisa","Marge","Maggie"]]}, {}, true ],
|
||||
[ {"in":["Milhouse",["Bart","Homer","Lisa","Marge","Maggie"]]}, {}, false ],
|
||||
[ {"in":["Spring","Springfield"]}, {}, true ],
|
||||
[ {"in":["i","team"]}, {}, false ],
|
||||
[ {"cat":"ice"}, {}, "ice" ],
|
||||
[ {"cat":["ice"]}, {}, "ice" ],
|
||||
[ {"cat":["ice","cream"]}, {}, "icecream" ],
|
||||
[ {"cat":[1,2]}, {}, "12" ],
|
||||
[ {"cat":["Robocop",2]}, {}, "Robocop2" ],
|
||||
[ {"cat":["we all scream for ","ice","cream"]}, {}, "we all scream for icecream" ],
|
||||
[ {"%":[1,2]}, {}, 1 ],
|
||||
[ {"%":[2,2]}, {}, 0 ],
|
||||
[ {"%":[3,2]}, {}, 1 ],
|
||||
[ {"max":[1,2,3]}, {}, 3 ],
|
||||
[ {"max":[1,3,3]}, {}, 3 ],
|
||||
[ {"max":[3,2,1]}, {}, 3 ],
|
||||
[ {"max":[1]}, {}, 1 ],
|
||||
[ {"min":[1,2,3]}, {}, 1 ],
|
||||
[ {"min":[1,1,3]}, {}, 1 ],
|
||||
[ {"min":[3,2,1]}, {}, 1 ],
|
||||
[ {"min":[1]}, {}, 1 ],
|
||||
|
||||
[ {"+":[1,2]}, {}, 3 ],
|
||||
[ {"+":[2,2,2]}, {}, 6 ],
|
||||
[ {"+":[1]}, {}, 1 ],
|
||||
[ {"+":["1",1]}, {}, 2 ],
|
||||
[ {"*":[3,2]}, {}, 6 ],
|
||||
[ {"*":[2,2,2]}, {}, 8 ],
|
||||
[ {"*":[1]}, {}, 1 ],
|
||||
[ {"*":["1",1]}, {}, 1 ],
|
||||
[ {"-":[2,3]}, {}, -1 ],
|
||||
[ {"-":[3,2]}, {}, 1 ],
|
||||
[ {"-":[3]}, {}, -3 ],
|
||||
[ {"-":["1",1]}, {}, 0 ],
|
||||
[ {"/":[4,2]}, {}, 2 ],
|
||||
[ {"/":[2,4]}, {}, 0.5 ],
|
||||
[ {"/":["1",1]}, {}, 1 ],
|
||||
|
||||
"Substring",
|
||||
[{"substr":["jsonlogic", 4]}, null, "logic"],
|
||||
[{"substr":["jsonlogic", -5]}, null, "logic"],
|
||||
[{"substr":["jsonlogic", 0, 1]}, null, "j"],
|
||||
[{"substr":["jsonlogic", -1, 1]}, null, "c"],
|
||||
[{"substr":["jsonlogic", 4, 5]}, null, "logic"],
|
||||
[{"substr":["jsonlogic", -5, 5]}, null, "logic"],
|
||||
[{"substr":["jsonlogic", -5, -2]}, null, "log"],
|
||||
[{"substr":["jsonlogic", 1, -5]}, null, "son"],
|
||||
|
||||
"Merge arrays",
|
||||
[{"merge":[]}, null, []],
|
||||
[{"merge":[[1]]}, null, [1]],
|
||||
[{"merge":[[1],[]]}, null, [1]],
|
||||
[{"merge":[[1], [2]]}, null, [1,2]],
|
||||
[{"merge":[[1], [2], [3]]}, null, [1,2,3]],
|
||||
[{"merge":[[1, 2], [3]]}, null, [1,2,3]],
|
||||
[{"merge":[[1], [2, 3]]}, null, [1,2,3]],
|
||||
"Given non-array arguments, merge converts them to arrays",
|
||||
[{"merge":1}, null, [1]],
|
||||
[{"merge":[1,2]}, null, [1,2]],
|
||||
[{"merge":[1,[2]]}, null, [1,2]],
|
||||
|
||||
"Too few args",
|
||||
[{"if":[]}, null, null],
|
||||
[{"if":[true]}, null, true],
|
||||
[{"if":[false]}, null, false],
|
||||
[{"if":["apple"]}, null, "apple"],
|
||||
|
||||
"Simple if/then/else cases",
|
||||
[{"if":[true, "apple"]}, null, "apple"],
|
||||
[{"if":[false, "apple"]}, null, null],
|
||||
[{"if":[true, "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[false, "apple", "banana"]}, null, "banana"],
|
||||
|
||||
"Empty arrays are falsey",
|
||||
[{"if":[ [], "apple", "banana"]}, null, "banana"],
|
||||
[{"if":[ [1], "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[ [1,2,3,4], "apple", "banana"]}, null, "apple"],
|
||||
|
||||
"Empty strings are falsey, all other strings are truthy",
|
||||
[{"if":[ "", "apple", "banana"]}, null, "banana"],
|
||||
[{"if":[ "zucchini", "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[ "0", "apple", "banana"]}, null, "apple"],
|
||||
|
||||
"You can cast a string to numeric with a unary + ",
|
||||
[{"===":[0,"0"]}, null, false],
|
||||
[{"===":[0,{"+":"0"}]}, null, true],
|
||||
[{"if":[ {"+":"0"}, "apple", "banana"]}, null, "banana"],
|
||||
[{"if":[ {"+":"1"}, "apple", "banana"]}, null, "apple"],
|
||||
|
||||
"Zero is falsy, all other numbers are truthy",
|
||||
[{"if":[ 0, "apple", "banana"]}, null, "banana"],
|
||||
[{"if":[ 1, "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[ 3.1416, "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[ -1, "apple", "banana"]}, null, "apple"],
|
||||
|
||||
"Truthy and falsy definitions matter in Boolean operations",
|
||||
[{"!" : [ [] ]}, {}, true],
|
||||
[{"!!" : [ [] ]}, {}, false],
|
||||
[{"and" : [ [], true ]}, {}, [] ],
|
||||
[{"or" : [ [], true ]}, {}, true ],
|
||||
|
||||
[{"!" : [ 0 ]}, {}, true],
|
||||
[{"!!" : [ 0 ]}, {}, false],
|
||||
[{"and" : [ 0, true ]}, {}, 0 ],
|
||||
[{"or" : [ 0, true ]}, {}, true ],
|
||||
|
||||
[{"!" : [ "" ]}, {}, true],
|
||||
[{"!!" : [ "" ]}, {}, false],
|
||||
[{"and" : [ "", true ]}, {}, "" ],
|
||||
[{"or" : [ "", true ]}, {}, true ],
|
||||
|
||||
[{"!" : [ "0" ]}, {}, false],
|
||||
[{"!!" : [ "0" ]}, {}, true],
|
||||
[{"and" : [ "0", true ]}, {}, true ],
|
||||
[{"or" : [ "0", true ]}, {}, "0" ],
|
||||
|
||||
"If the conditional is logic, it gets evaluated",
|
||||
[{"if":[ {">":[2,1]}, "apple", "banana"]}, null, "apple"],
|
||||
[{"if":[ {">":[1,2]}, "apple", "banana"]}, null, "banana"],
|
||||
|
||||
"If the consequents are logic, they get evaluated",
|
||||
[{"if":[ true, {"cat":["ap","ple"]}, {"cat":["ba","na","na"]} ]}, null, "apple"],
|
||||
[{"if":[ false, {"cat":["ap","ple"]}, {"cat":["ba","na","na"]} ]}, null, "banana"],
|
||||
|
||||
"If/then/elseif/then cases",
|
||||
[{"if":[true, "apple", true, "banana"]}, null, "apple"],
|
||||
[{"if":[true, "apple", false, "banana"]}, null, "apple"],
|
||||
[{"if":[false, "apple", true, "banana"]}, null, "banana"],
|
||||
[{"if":[false, "apple", false, "banana"]}, null, null],
|
||||
|
||||
[{"if":[true, "apple", true, "banana", "carrot"]}, null, "apple"],
|
||||
[{"if":[true, "apple", false, "banana", "carrot"]}, null, "apple"],
|
||||
[{"if":[false, "apple", true, "banana", "carrot"]}, null, "banana"],
|
||||
[{"if":[false, "apple", false, "banana", "carrot"]}, null, "carrot"],
|
||||
|
||||
[{"if":[false, "apple", false, "banana", false, "carrot"]}, null, null],
|
||||
[{"if":[false, "apple", false, "banana", false, "carrot", "date"]}, null, "date"],
|
||||
[{"if":[false, "apple", false, "banana", true, "carrot", "date"]}, null, "carrot"],
|
||||
[{"if":[false, "apple", true, "banana", false, "carrot", "date"]}, null, "banana"],
|
||||
[{"if":[false, "apple", true, "banana", true, "carrot", "date"]}, null, "banana"],
|
||||
[{"if":[true, "apple", false, "banana", false, "carrot", "date"]}, null, "apple"],
|
||||
[{"if":[true, "apple", false, "banana", true, "carrot", "date"]}, null, "apple"],
|
||||
[{"if":[true, "apple", true, "banana", false, "carrot", "date"]}, null, "apple"],
|
||||
[{"if":[true, "apple", true, "banana", true, "carrot", "date"]}, null, "apple"],
|
||||
|
||||
"# Compound Tests",
|
||||
[ {"and":[{">":[3,1]},true]}, {}, true ],
|
||||
[ {"and":[{">":[3,1]},false]}, {}, false ],
|
||||
[ {"and":[{">":[3,1]},{"!":true}]}, {}, false ],
|
||||
[ {"and":[{">":[3,1]},{"<":[1,3]}]}, {}, true ],
|
||||
[ {"?:":[{">":[3,1]},"visible","hidden"]}, {}, "visible" ],
|
||||
|
||||
"# Data-Driven",
|
||||
[ {"var":["a"]},{"a":1},1 ],
|
||||
[ {"var":["b"]},{"a":1},null ],
|
||||
[ {"var":["a"]},null,null ],
|
||||
[ {"var":"a"},{"a":1},1 ],
|
||||
[ {"var":"b"},{"a":1},null ],
|
||||
[ {"var":"a"},null,null ],
|
||||
[ {"var":["a", 1]},null,1 ],
|
||||
[ {"var":["b", 2]},{"a":1},2 ],
|
||||
[ {"var":"a.b"},{"a":{"b":"c"}},"c" ],
|
||||
[ {"var":"a.q"},{"a":{"b":"c"}},null ],
|
||||
[ {"var":["a.q", 9]},{"a":{"b":"c"}},9 ],
|
||||
[ {"var":1}, ["apple","banana"], "banana" ],
|
||||
[ {"var":"1"}, ["apple","banana"], "banana" ],
|
||||
[ {"var":"1.1"}, ["apple",["banana","beer"]], "beer" ],
|
||||
[ {"and":[{"<":[{"var":"temp"},110]},{"==":[{"var":"pie.filling"},"apple"]}]},{"temp":100,"pie":{"filling":"apple"}},true ],
|
||||
[ {"var":[{"?:":[{"<":[{"var":"temp"},110]},"pie.filling","pie.eta"]}]},{"temp":100,"pie":{"filling":"apple","eta":"60s"}},"apple" ],
|
||||
[ {"in":[{"var":"filling"},["apple","cherry"]]},{"filling":"apple"},true ],
|
||||
[ {"var":"a.b.c"}, null, null ],
|
||||
[ {"var":"a.b.c"}, {"a":null}, null ],
|
||||
[ {"var":"a.b.c"}, {"a":{"b":null}}, null ],
|
||||
[ {"var":""}, 1, 1 ],
|
||||
[ {"var":null}, 1, 1 ],
|
||||
[ {"var":[]}, 1, 1 ],
|
||||
|
||||
"Missing",
|
||||
[{"missing":[]}, null, []],
|
||||
[{"missing":["a"]}, null, ["a"]],
|
||||
[{"missing":"a"}, null, ["a"]],
|
||||
[{"missing":"a"}, {"a":"apple"}, []],
|
||||
[{"missing":["a"]}, {"a":"apple"}, []],
|
||||
[{"missing":["a","b"]}, {"a":"apple"}, ["b"]],
|
||||
[{"missing":["a","b"]}, {"b":"banana"}, ["a"]],
|
||||
[{"missing":["a","b"]}, {"a":"apple", "b":"banana"}, []],
|
||||
[{"missing":["a","b"]}, {}, ["a","b"]],
|
||||
[{"missing":["a","b"]}, null, ["a","b"]],
|
||||
|
||||
[{"missing":["a.b"]}, null, ["a.b"]],
|
||||
[{"missing":["a.b"]}, {"a":"apple"}, ["a.b"]],
|
||||
[{"missing":["a.b"]}, {"a":{"c":"apple cake"}}, ["a.b"]],
|
||||
[{"missing":["a.b"]}, {"a":{"b":"apple brownie"}}, []],
|
||||
[{"missing":["a.b", "a.c"]}, {"a":{"b":"apple brownie"}}, ["a.c"]],
|
||||
|
||||
|
||||
"Missing some",
|
||||
[{"missing_some":[1, ["a", "b"]]}, {"a":"apple"}, [] ],
|
||||
[{"missing_some":[1, ["a", "b"]]}, {"b":"banana"}, [] ],
|
||||
[{"missing_some":[1, ["a", "b"]]}, {"a":"apple", "b":"banana"}, [] ],
|
||||
[{"missing_some":[1, ["a", "b"]]}, {"c":"carrot"}, ["a", "b"]],
|
||||
|
||||
[{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "b":"banana"}, [] ],
|
||||
[{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "c":"carrot"}, [] ],
|
||||
[{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "b":"banana", "c":"carrot"}, [] ],
|
||||
[{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "d":"durian"}, ["b", "c"] ],
|
||||
[{"missing_some":[2, ["a", "b", "c"]]}, {"d":"durian", "e":"eggplant"}, ["a", "b", "c"] ],
|
||||
|
||||
|
||||
"Missing and If are friends, because empty arrays are falsey in JsonLogic",
|
||||
[{"if":[ {"missing":"a"}, "missed it", "found it" ]}, {"a":"apple"}, "found it"],
|
||||
[{"if":[ {"missing":"a"}, "missed it", "found it" ]}, {"b":"banana"}, "missed it"],
|
||||
|
||||
"Missing, Merge, and If are friends. VIN is always required, APR is only required if financing is true.",
|
||||
[
|
||||
{"missing":{"merge":[ "vin", {"if": [{"var":"financing"}, ["apr"], [] ]} ]} },
|
||||
{"financing":true},
|
||||
["vin","apr"]
|
||||
],
|
||||
|
||||
[
|
||||
{"missing":{"merge":[ "vin", {"if": [{"var":"financing"}, ["apr"], [] ]} ]} },
|
||||
{"financing":false},
|
||||
["vin"]
|
||||
],
|
||||
|
||||
"Filter, map, all, none, and some",
|
||||
[
|
||||
{"filter":[{"var":"integers"}, true]},
|
||||
{"integers":[1,2,3]},
|
||||
[1,2,3]
|
||||
],
|
||||
[
|
||||
{"filter":[{"var":"integers"}, false]},
|
||||
{"integers":[1,2,3]},
|
||||
[]
|
||||
],
|
||||
[
|
||||
{"filter":[{"var":"integers"}, {">=":[{"var":""},2]}]},
|
||||
{"integers":[1,2,3]},
|
||||
[2,3]
|
||||
],
|
||||
[
|
||||
{"filter":[{"var":"integers"}, {"%":[{"var":""},2]}]},
|
||||
{"integers":[1,2,3]},
|
||||
[1,3]
|
||||
],
|
||||
|
||||
[
|
||||
{"map":[{"var":"integers"}, {"*":[{"var":""},2]}]},
|
||||
{"integers":[1,2,3]},
|
||||
[2,4,6]
|
||||
],
|
||||
[
|
||||
{"map":[{"var":"integers"}, {"*":[{"var":""},2]}]},
|
||||
null,
|
||||
[]
|
||||
],
|
||||
[
|
||||
{"map":[{"var":"desserts"}, {"var":"qty"}]},
|
||||
{"desserts":[
|
||||
{"name":"apple","qty":1},
|
||||
{"name":"brownie","qty":2},
|
||||
{"name":"cupcake","qty":3}
|
||||
]},
|
||||
[1,2,3]
|
||||
],
|
||||
|
||||
[
|
||||
{"reduce":[
|
||||
{"var":"integers"},
|
||||
{"+":[{"var":"current"}, {"var":"accumulator"}]},
|
||||
0
|
||||
]},
|
||||
{"integers":[1,2,3,4]},
|
||||
10
|
||||
],
|
||||
[
|
||||
{"reduce":[
|
||||
{"var":"integers"},
|
||||
{"+":[{"var":"current"}, {"var":"accumulator"}]},
|
||||
0
|
||||
]},
|
||||
null,
|
||||
0
|
||||
],
|
||||
[
|
||||
{"reduce":[
|
||||
{"var":"integers"},
|
||||
{"*":[{"var":"current"}, {"var":"accumulator"}]},
|
||||
1
|
||||
]},
|
||||
{"integers":[1,2,3,4]},
|
||||
24
|
||||
],
|
||||
[
|
||||
{"reduce":[
|
||||
{"var":"integers"},
|
||||
{"*":[{"var":"current"}, {"var":"accumulator"}]},
|
||||
0
|
||||
]},
|
||||
{"integers":[1,2,3,4]},
|
||||
0
|
||||
],
|
||||
[
|
||||
{"reduce": [
|
||||
{"var":"desserts"},
|
||||
{"+":[ {"var":"accumulator"}, {"var":"current.qty"}]},
|
||||
0
|
||||
]},
|
||||
{"desserts":[
|
||||
{"name":"apple","qty":1},
|
||||
{"name":"brownie","qty":2},
|
||||
{"name":"cupcake","qty":3}
|
||||
]},
|
||||
6
|
||||
],
|
||||
|
||||
|
||||
[
|
||||
{"all":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"all":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"all":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"all":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"all":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"all":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"all":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"all":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[]},
|
||||
false
|
||||
],
|
||||
|
||||
|
||||
[
|
||||
{"none":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"none":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"none":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"none":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"none":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"none":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"none":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"none":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[]},
|
||||
true
|
||||
],
|
||||
|
||||
[
|
||||
{"some":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"some":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"some":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[1,2,3]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"some":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
|
||||
{"integers":[]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"some":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"some":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
true
|
||||
],
|
||||
[
|
||||
{"some":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
|
||||
{"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
|
||||
false
|
||||
],
|
||||
[
|
||||
{"some":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
|
||||
{"items":[]},
|
||||
false
|
||||
],
|
||||
|
||||
"EOF"
|
||||
]
|
||||
34
src/tests/helpers/test_jsonlogic.py
Normal file
34
src/tests/helpers/test_jsonlogic.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pretix.helpers.jsonlogic import Logic
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'jsonlogic-tests.json'), 'r') as f:
|
||||
data = json.load(f)
|
||||
params = [r for r in data if isinstance(r, list)]
|
||||
|
||||
params += [
|
||||
({"==": [True, True]}, {}, True),
|
||||
({"==": [True, False]}, {}, False),
|
||||
({"<": [0, "foo"]}, {}, False),
|
||||
({"+": [3.4, "0.1"]}, {}, 3.5),
|
||||
({"missing_some": [0, {'var': ''}]}, {}, []),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("logic,data,expected", params)
|
||||
def test_shared_tests(logic, data, expected):
|
||||
assert Logic().apply(logic, data) == expected
|
||||
|
||||
|
||||
def test_unknown_operator():
|
||||
with pytest.raises(ValueError):
|
||||
assert Logic().apply({'unknownOp': []}, {})
|
||||
|
||||
|
||||
def test_custom_operation():
|
||||
logic = Logic()
|
||||
logic.add_operation('double', lambda a: a * 2)
|
||||
assert logic.apply({'double': [{'var': 'value'}]}, {'value': 3}) == 6
|
||||
Reference in New Issue
Block a user