forked from CGM_Public/pretix_original
Merge branch 'master' into self-service-storno
This commit is contained in:
@@ -212,4 +212,17 @@ def membership_type(organizer):
|
||||
return organizer.membership_types.create(name='foo')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clist(event, item):
|
||||
c = event.checkin_lists.create(name="Default", all_products=False)
|
||||
c.limit_products.add(item)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clist_all(event, item):
|
||||
c = event.checkin_lists.create(name="Default", all_products=True)
|
||||
return c
|
||||
|
||||
|
||||
utils.setup_databases = scopes_disabled()(utils.setup_databases)
|
||||
|
||||
@@ -252,19 +252,6 @@ TEST_HISTORY_RES = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clist(event, item):
|
||||
c = event.checkin_lists.create(name="Default", all_products=False)
|
||||
c.limit_products.add(item)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clist_all(event, item):
|
||||
c = event.checkin_lists.create(name="Default", all_products=True)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_list(token_client, organizer, event, clist, item, subevent, django_assert_num_queries):
|
||||
res = dict(TEST_LIST_RES)
|
||||
@@ -1111,6 +1098,27 @@ def test_question_upload(token_client, organizer, clist, event, order, question)
|
||||
assert order.positions.first().answers.get(question=question[0]).file
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_question_upload_optional(token_client, organizer, clist, event, order, question):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
question[0].type = 'F'
|
||||
question[0].required = False
|
||||
question[0].save()
|
||||
|
||||
resp = _redeem(token_client, organizer, clist, p.pk, {})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'incomplete'
|
||||
with scopes_disabled():
|
||||
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
|
||||
|
||||
resp = _redeem(token_client, organizer, clist, p.pk, {'answers': {question[0].pk: ""}})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
assert not order.positions.first().answers.filter(question=question[0]).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_store_failed(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -34,7 +34,7 @@ from tests.const import SAMPLE_PNG
|
||||
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.base.models import (
|
||||
Checkin, InvoiceAddress, Order, OrderPosition, ReusableMedium,
|
||||
Checkin, InvoiceAddress, Item, Order, OrderPosition, ReusableMedium,
|
||||
)
|
||||
|
||||
# Lots of this code is overlapping with test_checkin.py, and some of it is arguably redundant since it's triggering
|
||||
@@ -286,12 +286,12 @@ def test_by_secret_special_chars(token_client, organizer, clist, event, order):
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
ReusableMedium.objects.create(
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
linked_orderposition=order.positions.first(),
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
@@ -301,6 +301,71 @@ def test_by_medium(token_client, organizer, clist, event, order):
|
||||
assert ci.raw_source_type == "barcode"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium_multiple_orderpositions(token_client, organizer, clist_all, event, order):
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
)
|
||||
op_item_first = order.positions.first()
|
||||
rm.linked_orderpositions.add(op_item_first)
|
||||
op_item_other = order.positions.all()[1]
|
||||
rm.linked_orderpositions.add(op_item_other)
|
||||
|
||||
# multiple tickets are valid => no check-in
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'ambiguous'
|
||||
|
||||
with scopes_disabled():
|
||||
op_item_other.valid_from = datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=event.timezone)
|
||||
op_item_other.valid_until = datetime.datetime(2020, 1, 1, 15, 0, 0, tzinfo=event.timezone)
|
||||
op_item_other.save()
|
||||
|
||||
with freeze_time("2020-01-01 13:45:00"):
|
||||
# multiple tickets are valid => no check-in
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'ambiguous'
|
||||
|
||||
with freeze_time("2020-01-01 10:45:00"):
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
with freeze_time("2020-01-01 15:45:00"):
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'already_redeemed'
|
||||
|
||||
with scopes_disabled():
|
||||
op_item_first.valid_from = datetime.datetime(2020, 1, 1, 10, 0, 0, tzinfo=event.timezone)
|
||||
op_item_first.valid_until = datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=event.timezone)
|
||||
op_item_first.save()
|
||||
|
||||
with freeze_time("2020-01-01 15:45:00"):
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'invalid_time'
|
||||
|
||||
with scopes_disabled():
|
||||
op_item_first.canceled = True
|
||||
op_item_first.save()
|
||||
op_item_other.canceled = True
|
||||
op_item_other.save()
|
||||
|
||||
resp = _redeem(token_client, organizer, clist_all, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'canceled'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium_not_connected(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
@@ -318,12 +383,12 @@ def test_by_medium_not_connected(token_client, organizer, clist, event, order):
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium_wrong_event(token_client, organizer, clist, event, order2):
|
||||
with scopes_disabled():
|
||||
ReusableMedium.objects.create(
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
linked_orderposition=order2.positions.first(),
|
||||
)
|
||||
rm.linked_orderpositions.add(order2.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 404
|
||||
assert resp.data['status'] == 'error'
|
||||
@@ -337,12 +402,12 @@ def test_by_medium_wrong_event(token_client, organizer, clist, event, order2):
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium_wrong_type(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
ReusableMedium.objects.create(
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
linked_orderposition=order.positions.first(),
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 404
|
||||
assert resp.data['status'] == 'error'
|
||||
@@ -355,13 +420,13 @@ def test_by_medium_wrong_type(token_client, organizer, clist, event, order):
|
||||
@pytest.mark.django_db
|
||||
def test_by_medium_inactive(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
ReusableMedium.objects.create(
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
active=False,
|
||||
linked_orderposition=order.positions.first(),
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {"source_type": "barcode"})
|
||||
assert resp.status_code == 404
|
||||
assert resp.data['status'] == 'error'
|
||||
@@ -1188,3 +1253,489 @@ def test_annul_failures(device_client, team, organizer, clist, clist_event2, eve
|
||||
with scopes_disabled():
|
||||
ci = p.all_checkins.get()
|
||||
assert ci.successful
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_incomplete_body(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid"
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {
|
||||
'non_field_errors': ['If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.']
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_medium_for_medium(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "barcode",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'error'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_unknown_media_type(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "unknown",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {"exchange_medium_type": ["\"unknown\" is not a valid choice."]}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_disabled_media_type(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'error'
|
||||
assert resp.data['reason_explanation'] == 'Medium type is not enabled for organizer.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_mismatch_media_type(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "barcode"
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'product'
|
||||
assert resp.data['reason_explanation'] == 'Incorrect medium type for product.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_no_item_policy(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'product'
|
||||
assert resp.data['reason_explanation'] == 'Product does not support medium exchange.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_new(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_reuse_replace(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.count() == 2
|
||||
assert rm.linked_orderpositions.filter(secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_exists_append(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.count() == 2
|
||||
assert rm.linked_orderpositions.filter(secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_expired(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
expires=now() - datetime.timedelta(hours=2),
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_not_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_new_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
"exchange_link_action": "append",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_exists'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_new_not_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
"exchange_link_action": "replace",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_required(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'exchange'
|
||||
assert resp.data['media_policy'] == 'new'
|
||||
assert resp.data['media_type'] == 'nfc_uid'
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
# Force works
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"force": True,
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_original_barcode_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_original_barcode_not_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'already_exchanged'
|
||||
# Force works
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"force": True,
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_scan_medium_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "12345678", {
|
||||
"source_type": "nfc_uid",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_double_exchange(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = False
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "87654321",
|
||||
"exchange_link_action": "replace",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'already_exchanged'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy,media_type",
|
||||
[
|
||||
(Item.MEDIA_POLICY_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_REUSE_OR_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_APPEND_OR_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_NEW, "barcode"),
|
||||
(Item.MEDIA_POLICY_REUSE_OR_NEW, "barcode"),
|
||||
(Item.MEDIA_POLICY_APPEND_OR_NEW, "barcode"),
|
||||
]
|
||||
)
|
||||
def test_exchange_unsupported_media_type_for_new(token_client, organizer, clist, event, order, item, media_policy, media_type):
|
||||
organizer.settings.set(f'reusable_media_type_{media_type}', True)
|
||||
# Shouldn't be configurable, but test that the logic is solid anyway
|
||||
item.media_type = media_type
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": media_type,
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy",
|
||||
[
|
||||
Item.MEDIA_POLICY_NEW,
|
||||
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||
]
|
||||
)
|
||||
def test_exchange_rejected_media_identifier(token_client, organizer, clist, event, order, item, media_policy):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "08RANDOM",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy",
|
||||
[
|
||||
Item.MEDIA_POLICY_NEW,
|
||||
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||
]
|
||||
)
|
||||
def test_exchange_create_gift_card(token_client, organizer, clist, event, order, item, media_policy):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard = True
|
||||
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard_currency = "EUR"
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "0412345",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(identifier="0412345")
|
||||
assert rm.linked_giftcard.currency == "EUR"
|
||||
|
||||
@@ -1079,3 +1079,18 @@ def test_event_edit_restrictions(client, event, organizer, user, team):
|
||||
assert _get_and_patch_event_export(user2_client, s2)
|
||||
assert _get_and_patch_event_export(team1_client, s2)
|
||||
assert _get_and_patch_event_export(user1_client, s2)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_checkinlist_patch(user_client, organizer, event, user, event_scheduled_export, clist):
|
||||
event_scheduled_export.export_identifier = "checkinlistpdf"
|
||||
event_scheduled_export.save()
|
||||
|
||||
resp = user_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format(organizer.slug, event.slug, event_scheduled_export.id),
|
||||
data={
|
||||
"export_form_data": {"list": clist.pk},
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
@@ -171,6 +171,35 @@ def test_giftcard_detail_expand(token_client, organizer, event, giftcard):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_giftcard_detail_expand_without_permissions(team, token_client, organizer, event, giftcard):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
res = dict(TEST_GC_RES)
|
||||
res["id"] = giftcard.pk
|
||||
res["issuance"] = giftcard.issuance.isoformat().replace('+00:00', 'Z')
|
||||
resp = token_client.get('/api/v1/organizers/{}/giftcards/{}/?expand=owner_ticket'.format(organizer.slug, giftcard.pk))
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["owner_ticket"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
|
||||
TEST_GIFTCARD_CREATE_PAYLOAD = {
|
||||
"secret": "DEFABC",
|
||||
"value": "12.00",
|
||||
|
||||
@@ -530,6 +530,7 @@ def test_item_detail_program_times(token_client, organizer, event, team, item, c
|
||||
res["program_times"] = [{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
"location": None
|
||||
}]
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
@@ -1972,32 +1973,54 @@ def program_time2(item, category):
|
||||
end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def program_time3(item, category):
|
||||
return item.program_times.create(start=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 31, 0, 0, 0, tzinfo=timezone.utc),
|
||||
location='Testlocation')
|
||||
|
||||
|
||||
TEST_PROGRAM_TIMES_RES = {
|
||||
0: {
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
"location": None,
|
||||
},
|
||||
1: {
|
||||
"start": "2017-12-29T00:00:00Z",
|
||||
"end": "2017-12-30T00:00:00Z",
|
||||
"location": None,
|
||||
},
|
||||
2: {
|
||||
"start": "2017-12-30T00:00:00Z",
|
||||
"end": "2017-12-31T00:00:00Z",
|
||||
"location": {"en": "Testlocation"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2):
|
||||
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2, program_time3):
|
||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||
res[0]["id"] = program_time.pk
|
||||
res[1]["id"] = program_time2.pk
|
||||
res[2]["id"] = program_time3.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res[0]['start'] == resp.data['results'][0]['start']
|
||||
assert res[0]['end'] == resp.data['results'][0]['end']
|
||||
assert res[0]['id'] == resp.data['results'][0]['id']
|
||||
assert res[0] == resp.data['results'][0]
|
||||
assert res[1]['start'] == resp.data['results'][1]['start']
|
||||
assert res[1]['end'] == resp.data['results'][1]['end']
|
||||
assert res[1]['id'] == resp.data['results'][1]['id']
|
||||
assert res[1] == resp.data['results'][1]
|
||||
assert res[2]['start'] == resp.data['results'][2]['start']
|
||||
assert res[2]['end'] == resp.data['results'][2]['end']
|
||||
assert res[2]['location'] == resp.data['results'][2]['location']
|
||||
assert res[2]['id'] == resp.data['results'][2]['id']
|
||||
assert res[2] == resp.data['results'][2]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -2039,6 +2062,59 @@ def test_program_times_create(token_client, organizer, event, item):
|
||||
assert resp.content.decode() == '{"non_field_errors":["The program end must not be before the program start."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_create_location(token_client, organizer, event, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
"location": {
|
||||
"en": "Testlocation",
|
||||
"de": "Testort"
|
||||
}
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert "Testlocation" == program_time.location.localize("en")
|
||||
assert "Testort" == program_time.location.localize("de")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_create_without_location(token_client, organizer, event, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['location'] is None
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert str(program_time.location) == ""
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
"location": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['location'] is None
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert str(program_time.location) == ""
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_update(token_client, organizer, event, item, program_time):
|
||||
resp = token_client.patch(
|
||||
|
||||
@@ -3121,9 +3121,68 @@ def test_order_create_use_medium(token_client, organizer, event, item, quota, qu
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
medium.refresh_from_db()
|
||||
assert o.positions.first() == medium.linked_orderposition
|
||||
assert o.positions.first() == medium.linked_orderpositions.first()
|
||||
assert resp.data['positions'][0]['pdf_data']['medium_identifier'] == medium.identifier
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
medium.refresh_from_db()
|
||||
assert medium.linked_orderpositions.count() == 1
|
||||
assert o.positions.first() == medium.linked_orderpositions.first()
|
||||
assert resp.data['positions'][0]['pdf_data']['medium_identifier'] == medium.identifier
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_create_add_to_medium(token_client, organizer, event, item, quota, question, medium):
|
||||
item.media_type = medium.type
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
|
||||
res['positions'][0]['item'] = item.pk
|
||||
res['positions'][0]['use_reusable_medium'] = medium.pk
|
||||
res['positions'][0]['answers'][0]['question'] = question.pk
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
medium.refresh_from_db()
|
||||
assert medium.linked_orderpositions.count() == 1
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
medium.refresh_from_db()
|
||||
assert medium.linked_orderpositions.count() == 2
|
||||
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
res['positions'][0]['use_reusable_medium'] = medium.pk
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
medium.refresh_from_db()
|
||||
assert medium.linked_orderpositions.count() == 1
|
||||
assert o.positions.first() == medium.linked_orderpositions.first()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_create_use_medium_other_organizer(token_client, organizer, event, item, quota, question, medium2):
|
||||
@@ -3168,7 +3227,7 @@ def test_order_create_create_medium(token_client, organizer, event, item, quota,
|
||||
i = resp.data['positions'][0]['pdf_data']['medium_identifier']
|
||||
assert i
|
||||
m = organizer.reusable_media.get(identifier=i)
|
||||
assert m.linked_orderposition == o.positions.first()
|
||||
assert m.linked_orderpositions.first() == o.positions.first()
|
||||
assert m.type == "barcode"
|
||||
|
||||
|
||||
|
||||
@@ -89,10 +89,13 @@ TEST_MEDIUM_RES = {
|
||||
"organizer": "dummy",
|
||||
"identifier": "ABCDEFGH",
|
||||
"type": "barcode",
|
||||
"claim_token": None,
|
||||
"label": None,
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderposition": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
"info": {},
|
||||
@@ -170,7 +173,7 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
medium.linked_orderposition = op
|
||||
medium.linked_orderpositions.add(op)
|
||||
medium.linked_giftcard = giftcard
|
||||
medium.customer = customer
|
||||
medium.save()
|
||||
@@ -252,6 +255,76 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_detail_event_permission_missing(token_client, organizer, event, medium, giftcard, customer, team):
|
||||
team.all_organizer_permissions = False
|
||||
team.limit_organizer_permissions = {
|
||||
"organizer.reusablemedia:read": True,
|
||||
"organizer.customers:read": True,
|
||||
"organizer.giftcards:read": True,
|
||||
}
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
medium.linked_orderpositions.add(op)
|
||||
medium.linked_giftcard = giftcard
|
||||
medium.customer = customer
|
||||
medium.save()
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard&expand='
|
||||
'linked_giftcard.owner_ticket&expand=linked_orderposition&expand=customer'.format(
|
||||
organizer.slug, medium.pk
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["linked_orderposition"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
assert resp.data["linked_giftcard"] == {
|
||||
"id": giftcard.pk,
|
||||
"secret": "ABCDEF",
|
||||
"issuance": giftcard.issuance.isoformat().replace("+00:00", "Z"),
|
||||
"value": "23.00",
|
||||
"currency": "EUR",
|
||||
"testmode": False,
|
||||
"expires": None,
|
||||
"conditions": None,
|
||||
"owner_ticket": {"id": op.pk},
|
||||
"issuer": "dummy",
|
||||
}
|
||||
|
||||
assert resp.data["customer"] == {
|
||||
"identifier": customer.identifier,
|
||||
"external_identifier": None,
|
||||
"email": "foo@example.org",
|
||||
"phone": None,
|
||||
"name": "Foo",
|
||||
"name_parts": {"_legacy": "Foo"},
|
||||
"is_active": True,
|
||||
"is_verified": False,
|
||||
"last_login": None,
|
||||
"date_joined": customer.date_joined.isoformat().replace("+00:00", "Z"),
|
||||
"locale": "en",
|
||||
"last_modified": customer.last_modified.isoformat().replace("+00:00", "Z"),
|
||||
"notes": None
|
||||
}
|
||||
|
||||
|
||||
TEST_MEDIUM_CREATE_PAYLOAD = {
|
||||
"type": "barcode",
|
||||
"identifier": "FOOBAR",
|
||||
@@ -282,6 +355,110 @@ def test_medium_create(token_client, organizer, giftcard):
|
||||
assert m.updated > now() - timedelta(minutes=10)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_create_linked_orderposition(token_client, organizer, event, org2_event, medium):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
op2 = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
|
||||
org2_o = Order.objects.create(
|
||||
code='FOO', event=org2_event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=org2_event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
org2_ticket = org2_event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
org2_op = org2_o.positions.create(item=org2_ticket, price=Decimal("14"))
|
||||
|
||||
payload = dict(TEST_MEDIUM_CREATE_PAYLOAD)
|
||||
|
||||
# wrong orderposition for organizer
|
||||
payload['linked_orderposition'] = org2_op.pk
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
# unkown orderposition
|
||||
payload['linked_orderposition'] = "unknown"
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
# create with linked_orderposition
|
||||
payload['linked_orderposition'] = op.pk
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
m = ReusableMedium.objects.get(pk=resp.data['id'])
|
||||
assert list(m.linked_orderpositions.values_list('pk', flat=True)) == [op.pk]
|
||||
|
||||
# double-check API-response for fallback-values
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, resp.data['id'])
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['linked_orderposition'] == op.pk
|
||||
assert resp.data['linked_orderpositions'] == [op.pk]
|
||||
|
||||
# create with linked_orderposition and linked_orderpositions (not allowed)
|
||||
payload['identifier'] = "FOOBAZ"
|
||||
payload['linked_orderpositions'] = [op.pk, org2_op.pk]
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
# multiple linked_orderpositions, but from different organizers
|
||||
del payload['linked_orderposition']
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
# multiple linked_orderpositions from same organizer
|
||||
payload['linked_orderpositions'] = [op.pk, op2.pk]
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/reusablemedia/'.format(organizer.slug),
|
||||
payload,
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
m = ReusableMedium.objects.get(pk=resp.data['id'])
|
||||
assert list(m.linked_orderpositions.values_list('pk', flat=True)) == [op.pk, op2.pk]
|
||||
|
||||
# double-check API-response for fallback-values
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, resp.data['id'])
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['linked_orderposition'] is None
|
||||
assert resp.data['linked_orderpositions'] == [op.pk, op2.pk]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_foreignkeyval(token_client, organizer, giftcard2):
|
||||
payload = dict(TEST_MEDIUM_CREATE_PAYLOAD)
|
||||
@@ -328,6 +505,68 @@ def test_medium_patch(token_client, organizer, event, medium, giftcard, customer
|
||||
assert medium.info == {'test': 2}
|
||||
assert medium.identifier == "ABCDEFGH"
|
||||
|
||||
# test patch with linked_orderpositions
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
op2 = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, medium.pk),
|
||||
{
|
||||
'linked_orderposition': op.pk,
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
medium.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert list(medium.linked_orderpositions.values_list('pk', flat=True)) == [op.pk]
|
||||
assert medium.all_logentries().count() == 2
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, medium.pk),
|
||||
{
|
||||
'linked_orderpositions': [op.pk, op2.pk],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
medium.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert list(medium.linked_orderpositions.values_list('pk', flat=True)) == [op.pk, op2.pk]
|
||||
assert medium.all_logentries().count() == 3
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, medium.pk),
|
||||
{
|
||||
'linked_orderpositions': [op2.pk],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
medium.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert list(medium.linked_orderpositions.values_list('pk', flat=True)) == [op2.pk]
|
||||
assert medium.all_logentries().count() == 4
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/'.format(organizer.slug, medium.pk),
|
||||
{
|
||||
'linked_orderposition': op.pk,
|
||||
'linked_orderpositions': [op.pk, op2.pk],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_no_deletion(token_client, organizer, event, medium):
|
||||
@@ -468,7 +707,7 @@ def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2
|
||||
ticket = org2_event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
medium2.linked_orderposition = op
|
||||
medium2.linked_orderpositions.add(op)
|
||||
medium2.linked_giftcard = giftcard2
|
||||
medium2.save()
|
||||
|
||||
|
||||
@@ -1186,7 +1186,7 @@ def test_rules_reasoning_prefer_number_over_date(event, position, clist):
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_position_queries(django_assert_max_num_queries, position, clist):
|
||||
with django_assert_max_num_queries(13) as captured:
|
||||
with django_assert_max_num_queries(12) 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)
|
||||
|
||||
@@ -82,7 +82,11 @@ def test_full_clone_same_organizer():
|
||||
assert item1.meta_data
|
||||
ItemProgramTime.objects.create(item=item1,
|
||||
start=datetime.datetime(2017, 12, 27, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0, tzinfo=datetime.timezone.utc))
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
location={
|
||||
"en": "Testlocation",
|
||||
"de": "Testort"
|
||||
})
|
||||
assert item1.program_times
|
||||
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15,
|
||||
hidden_if_item_available=item1)
|
||||
@@ -169,6 +173,7 @@ def test_full_clone_same_organizer():
|
||||
assert copied_item1.meta_data == item1.meta_data
|
||||
assert copied_item1.program_times.first().start == item1.program_times.first().start
|
||||
assert copied_item1.program_times.first().end == item1.program_times.first().end
|
||||
assert copied_item1.program_times.first().location == item1.program_times.first().location
|
||||
assert copied_item2.variations.get().meta_data == item2v.meta_data
|
||||
assert copied_item1.hidden_if_available == copied_q2
|
||||
assert copied_item1.grant_membership_type == membership_type
|
||||
@@ -238,6 +243,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
sc1_c = organizer.sales_channels.create(identifier="c")
|
||||
sc2_a = organizer2.sales_channels.get(identifier="web")
|
||||
sc2_c = organizer2.sales_channels.create(identifier="c")
|
||||
o1_meta_prop_a = organizer.meta_properties.create(name="Prop to copy")
|
||||
o1_meta_prop_b = organizer.meta_properties.create(name="Prop to find")
|
||||
o2_meta_prop_b = organizer2.meta_properties.create(name="Prop to find")
|
||||
|
||||
event = Event.objects.create(
|
||||
organizer=organizer, name='Dummy', slug='dummy',
|
||||
@@ -262,6 +270,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
event.settings.payment_giftcard__enabled = True
|
||||
event.settings.payment_giftcard__restrict_to_sales_channels = ['web', 'b', 'c']
|
||||
|
||||
event.meta_values.create(property=o1_meta_prop_a, value='a')
|
||||
event.meta_values.create(property=o1_meta_prop_b, value='b')
|
||||
|
||||
copied_event = Event.objects.create(
|
||||
organizer=organizer2, name='Dummy2', slug='dummy2',
|
||||
date_from=datetime.datetime(2022, 4, 15, 9, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
@@ -284,3 +295,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
|
||||
assert event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'b', 'c']
|
||||
assert copied_event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'c']
|
||||
|
||||
assert event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).value == 'a'
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer2
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).value == 'b'
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).property.organizer == organizer2
|
||||
|
||||
@@ -123,6 +123,8 @@ def env():
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='eu:ecb:eurofxref-daily', source_currency='EUR', other_currency=currency, rate=rate)
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='cz:cnb:rate-fixing-daily', source_currency='EUR',
|
||||
other_currency='CZK', rate=Decimal('25.0000'))
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='pl:nbp:table-a', source_currency='EUR',
|
||||
other_currency='PLN', rate=Decimal('4.2355'))
|
||||
yield event, o
|
||||
|
||||
|
||||
@@ -347,6 +349,23 @@ def test_invoice_indirect_currency_conversion(env):
|
||||
assert inv.foreign_currency_source == 'eu:ecb:eurofxref-daily'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_pln_currency_conversion(env):
|
||||
event, order = env
|
||||
event.settings.invoice_eu_currencies = 'PLN'
|
||||
|
||||
event.settings.set('invoice_language', 'en')
|
||||
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', zipcode='12345', city='Warsaw',
|
||||
country=Country('PL'), vat_id='PL123456780', vat_id_validated=True, order=order,
|
||||
is_business=True)
|
||||
|
||||
inv = generate_invoice(order)
|
||||
assert inv.foreign_currency_display == "PLN"
|
||||
assert inv.foreign_currency_rate == Decimal("4.2355")
|
||||
assert inv.foreign_currency_rate_date == date.today()
|
||||
assert inv.foreign_currency_source == 'pl:nbp:table-a'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_czk_currency_conversion(env):
|
||||
event, order = env
|
||||
|
||||
@@ -602,10 +602,13 @@ PRIVATE_IPS_RES = [
|
||||
[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('127.1.1.1', 443))],
|
||||
[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('192.168.5.3', 443))],
|
||||
[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('224.0.0.1', 443))],
|
||||
[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('100.64.0.1', 443))],
|
||||
[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('100.100.100.100', 443))],
|
||||
[(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::1', 443, 0, 0))],
|
||||
[(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('fe80::1', 443, 0, 0))],
|
||||
[(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('ff00::1', 443, 0, 0))],
|
||||
[(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('fc00::1', 443, 0, 0))],
|
||||
[(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::ffff:100.64.0.1', 443, 0, 0))],
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -126,3 +126,81 @@ class LocaleDeterminationTest(TestCase):
|
||||
response = c.get('/dummy/dummy/')
|
||||
language = response['Content-Language']
|
||||
self.assertEqual(language, 'en')
|
||||
|
||||
|
||||
def test_render_csp():
|
||||
from pretix.base.middleware import _render_csp
|
||||
|
||||
assert _render_csp({}) == ""
|
||||
assert _render_csp({'default-src': ["'self'"]}) == "default-src 'self'"
|
||||
|
||||
h = {
|
||||
'default-src': ["'self'"],
|
||||
'script-src': ["'self'"],
|
||||
'object-src': ["'none'"],
|
||||
'frame-src': ["'self'"],
|
||||
'style-src': ["'self'", "'self'"],
|
||||
'connect-src': ["'self'", "'self'"],
|
||||
'img-src': ["'self'", "'self'", "data:"],
|
||||
'font-src': ["'self'"],
|
||||
'media-src': ["'self'", "data:"],
|
||||
'form-action': ["'self'", "https:"],
|
||||
}
|
||||
assert _render_csp(h) == (
|
||||
"default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'self'; style-src 'self' 'self'; "
|
||||
"connect-src 'self' 'self'; img-src 'self' 'self' data:; font-src 'self'; media-src 'self' data:; form-action 'self' https:"
|
||||
)
|
||||
|
||||
|
||||
def test_merge_csp():
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
|
||||
h = {
|
||||
'default-src': ["'self'"],
|
||||
'script-src': ["'self'"],
|
||||
'style-src': ["'self'", "'self'"],
|
||||
'form-action': ["'self'", "https:"],
|
||||
'connect-src': ["'self'", "'self'"],
|
||||
}
|
||||
assert _render_csp(h) == (
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'self'; form-action 'self' https:; connect-src 'self' 'self'"
|
||||
)
|
||||
|
||||
_merge_csp(h, _parse_csp("style-src 'unsafe-inline'; connect-src https://example.com"))
|
||||
assert _render_csp(h) == (
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'self' 'unsafe-inline'; form-action 'self' "
|
||||
"https:; connect-src 'self' 'self' https://example.com"
|
||||
)
|
||||
|
||||
|
||||
def test_roundtrip_csp():
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
|
||||
prod_csp = ("default-src 'self' https://pretix.eu https://static.pretix.cloud; script-src 'self' "
|
||||
"'sha256-+tmFggeXIPOAC2UgcQ3LW/gPHTkwyWg3/D6FOJ5BHGo=' 'unsafe-eval' https://matomo.rami.io "
|
||||
"https://pretix.eu https://static.pretix.cloud https://support.rami.io; object-src 'none'; "
|
||||
"frame-src 'self' https://matomo.rami.io https://pretix.eu https://static.pretix.cloud "
|
||||
"https://support.rami.io https://www.youtube-nocookie.com; style-src 'self' 'unsafe-inline' "
|
||||
"data: https://cdn.pretix.cloud https://pretix.eu https://static.pretix…rt.rami.io; connect-src "
|
||||
"'self' https://cdn.pretix.cloud https://matomo.rami.io https://pretix.eu https://static.pretix.cloud "
|
||||
"https://support.rami.io ws://support.rami.io; img-src 'self' data: https://cdn.pretix.cloud "
|
||||
"https://matomo.rami.io https://pretix.eu https://static.pretix.cloud https://support.rami.io; "
|
||||
"font-src 'self' https://pretix.eu https://static.pretix.cloud; media-src 'self' data: "
|
||||
"https://cdn.pretix.cloud https://pretix.eu https://static.pretix.cloud; form-action 'self' "
|
||||
"https: https://pretix.eu")
|
||||
h = _parse_csp(prod_csp)
|
||||
_merge_csp(h, _parse_csp(prod_csp))
|
||||
assert _render_csp(h) == prod_csp
|
||||
|
||||
|
||||
def test_sanitize_csp():
|
||||
from pretix.base.middleware import _render_csp
|
||||
|
||||
h = {
|
||||
'style-src': ["'self'", "https://example.com", "https://example.org https://attack.example.net", "https://\fexample.org",
|
||||
"https://\texample.org", "https://example.org;script-src https://example.org", ],
|
||||
'script-src': ["'self'"],
|
||||
}
|
||||
assert _render_csp(h) == (
|
||||
"style-src 'self' https://example.com; script-src 'self'"
|
||||
)
|
||||
|
||||
@@ -4123,8 +4123,8 @@ def test_giftcard_multiple(event):
|
||||
for p in order.payments.all():
|
||||
p.payment_provider.execute_payment(None, p)
|
||||
|
||||
assert order.payments.get(info__icontains=gc1.pk).amount == Decimal('12.00')
|
||||
assert order.payments.get(info__icontains=gc2.pk).amount == Decimal('11.00')
|
||||
assert order.payments.get(amount=Decimal("12.00")).info_data["gift_card"] == gc1.pk
|
||||
assert order.payments.get(amount=Decimal("11.00")).info_data["gift_card"] == gc2.pk
|
||||
gc1 = GiftCard.objects.get(pk=gc1.pk)
|
||||
assert gc1.value == 0
|
||||
gc2 = GiftCard.objects.get(pk=gc2.pk)
|
||||
|
||||
@@ -119,7 +119,7 @@ def test_linkify_abs(link):
|
||||
assert markdown_compile_email(input) == f"<p>{output}</p>"
|
||||
|
||||
|
||||
signer = signing.Signer(salt='safe-redirect')
|
||||
signer = signing.Signer(salt='safelink-url')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -27,7 +27,7 @@ from django.core.cache import cache
|
||||
from django.test import override_settings
|
||||
from django.utils import translation
|
||||
from django_scopes import scopes_disabled
|
||||
from fakeredis import FakeConnection
|
||||
from fakeredis import FakeRedisConnection
|
||||
from xdist.dsession import DSession
|
||||
|
||||
from pretix.testutils.mock import get_redis_connection
|
||||
@@ -97,21 +97,21 @@ def fakeredis_client(monkeypatch):
|
||||
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
||||
'LOCATION': f'redis://127.0.0.1:{redis_port}',
|
||||
'OPTIONS': {
|
||||
'connection_class': FakeConnection
|
||||
'connection_class': FakeRedisConnection
|
||||
}
|
||||
},
|
||||
'redis_session': {
|
||||
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
||||
'LOCATION': f'redis://127.0.0.1:{redis_port}',
|
||||
'OPTIONS': {
|
||||
'connection_class': FakeConnection
|
||||
'connection_class': FakeRedisConnection
|
||||
}
|
||||
},
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
||||
'LOCATION': f'redis://127.0.0.1:{redis_port}',
|
||||
'OPTIONS': {
|
||||
'connection_class': FakeConnection
|
||||
'connection_class': FakeRedisConnection
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -692,7 +692,8 @@ class ItemsTest(ItemFormTest):
|
||||
self.item2.program_times.create(start=datetime.datetime(2017, 12, 27, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc))
|
||||
tzinfo=datetime.timezone.utc),
|
||||
location={"en": "Testlocation", "de": "Testort"})
|
||||
|
||||
doc = self.get_doc('/control/event/%s/%s/items/add?copy_from=%d' % (self.orga1.slug, self.event1.slug, self.item2.pk))
|
||||
data = extract_form_fields(doc.select("form")[0])
|
||||
@@ -723,6 +724,7 @@ class ItemsTest(ItemFormTest):
|
||||
assert set([str(v.value) for v in i_new.variations.all()]) == set([str(v.value) for v in i_old.variations.all()])
|
||||
assert i_old.program_times.first().start == i_new.program_times.first().start
|
||||
assert i_old.program_times.first().end == i_new.program_times.first().end
|
||||
assert i_old.program_times.first().location == i_new.program_times.first().location
|
||||
|
||||
def test_add_to_existing_quota(self):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -137,6 +137,7 @@ event_urls = [
|
||||
"subevents/select2",
|
||||
"subevents/add",
|
||||
"subevents/2/delete",
|
||||
"subevents/2/edit",
|
||||
"subevents/2/",
|
||||
"quotas/",
|
||||
"quotas/2/delete",
|
||||
@@ -360,8 +361,9 @@ event_permission_urls = [
|
||||
("event.items:write", "discounts/reorder", 400, HTTP_POST),
|
||||
("event.items:write", "discounts/add", 200, HTTP_GET),
|
||||
(None, "subevents/", 200, HTTP_GET),
|
||||
("event.subevents:write", "subevents/2/", 404, HTTP_GET),
|
||||
("event.subevents:write", "subevents/2/", 404, HTTP_POST),
|
||||
(None, "subevents/2/", 404, HTTP_GET),
|
||||
("event.subevents:write", "subevents/2/edit", 404, HTTP_GET),
|
||||
("event.subevents:write", "subevents/2/edit", 404, HTTP_POST),
|
||||
("event.subevents:write", "subevents/2/delete", 404, HTTP_GET),
|
||||
("event.subevents:write", "subevents/add", 200, HTTP_GET),
|
||||
("event.subevents:write", "subevents/bulk_add", 200, HTTP_GET),
|
||||
|
||||
@@ -110,9 +110,9 @@ class SubEventsTest(SoupTest):
|
||||
assert se.checkinlist_set.count() == 1
|
||||
|
||||
def test_modify(self):
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk)
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/edit' % self.subevent1.pk)
|
||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk, {
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/edit' % self.subevent1.pk, {
|
||||
'name_0': 'SE2',
|
||||
'active': 'on',
|
||||
'date_from_0': '2017-07-01',
|
||||
@@ -747,6 +747,92 @@ class SubEventsTest(SoupTest):
|
||||
assert ses[1].date_from.isoformat() == "2018-04-12T11:29:31+00:00"
|
||||
assert ses[-1].date_from.isoformat() == "2019-03-28T12:29:31+00:00"
|
||||
|
||||
def test_create_bulk_skip_existing(self):
|
||||
with scopes_disabled():
|
||||
self.event1.subevents.all().delete()
|
||||
# SubEvent ends at rrule start time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 4, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 4, 10, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent overlaps rrule start
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 5, 9, 30, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 5, 10, 30, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent times are same as rrule
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 6, 10, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 6, 11, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent starts at rrule end time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 7, 11, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 7, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent overlaps entire rrule time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 8, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 8, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent has before rrule time and no end
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 9, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
existing_events = list(self.event1.subevents.values_list('pk', flat=True))
|
||||
|
||||
self.event1.settings.timezone = 'Europe/Berlin'
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
||||
'rruleformset-TOTAL_FORMS': '1',
|
||||
'rruleformset-INITIAL_FORMS': '0',
|
||||
'rruleformset-MIN_NUM_FORMS': '0',
|
||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
||||
'rruleformset-0-end': 'count',
|
||||
'rruleformset-0-count': '10',
|
||||
'rruleformset-0-interval': '1',
|
||||
'rruleformset-0-freq': 'weekly',
|
||||
'rruleformset-0-dtstart': '2018-04-03',
|
||||
'rruleformset-0-weekly_byweekday': ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'],
|
||||
'rruleformset-0-yearly_same': 'on',
|
||||
'rruleformset-0-monthly_same': 'on',
|
||||
'timeformset-TOTAL_FORMS': '1',
|
||||
'timeformset-INITIAL_FORMS': '0',
|
||||
'timeformset-MIN_NUM_FORMS': '1',
|
||||
'timeformset-MAX_NUM_FORMS': '1000',
|
||||
'timeformset-0-time_from': '12:00:00',
|
||||
'timeformset-0-time_to': '13:00:00',
|
||||
'rruleformset-0-until': '2019-04-03',
|
||||
'skip_if_overlap': 'on',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'frontpage_text_0': '',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'quotas-0-name': 'Q1',
|
||||
'quotas-0-size': '50',
|
||||
'quotas-0-itemvars': str(self.ticket.pk),
|
||||
'checkinlist_set-TOTAL_FORMS': '0',
|
||||
'checkinlist_set-INITIAL_FORMS': '0',
|
||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
with scopes_disabled():
|
||||
ses = list(self.event1.subevents.exclude(pk__in=existing_events).order_by('date_from'))
|
||||
|
||||
assert len(ses) == 7
|
||||
assert [s.date_from.date().isoformat() for s in ses] == [
|
||||
'2018-04-03',
|
||||
'2018-04-04',
|
||||
'2018-04-07',
|
||||
'2018-04-09',
|
||||
'2018-04-10',
|
||||
'2018-04-11',
|
||||
'2018-04-12'
|
||||
]
|
||||
|
||||
def test_delete_bulk(self):
|
||||
self.subevent2.active = True
|
||||
self.subevent2.save()
|
||||
|
||||
@@ -44,7 +44,7 @@ from tests.base import SoupTestMixin, extract_form_fields
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer, Quota, Team,
|
||||
User, Voucher,
|
||||
User, Voucher, WaitingListEntry,
|
||||
)
|
||||
|
||||
|
||||
@@ -771,3 +771,38 @@ class VoucherFormTest(SoupTestMixin, TransactionTestCase):
|
||||
|
||||
assert len(doc.select('.alert-warning ul li')) == 1 # Check that there's exactly 1 item in the warning list
|
||||
assert doc.text.count('Order DEDUP') == 1 # Check that the order is listed exactly once
|
||||
|
||||
def test_tag_typeahead(self):
|
||||
with scopes_disabled():
|
||||
self.event.vouchers.create(item=self.ticket, code='AAAAAAA', tag='early-bird')
|
||||
self.event.vouchers.create(item=self.ticket, code='BBBBBBB', tag='early-vip')
|
||||
self.event.vouchers.create(item=self.ticket, code='CCCCCCC', tag='sponsor')
|
||||
|
||||
url = '/control/event/%s/%s/vouchers/tags/typeahead' % (self.orga.slug, self.event.slug)
|
||||
resp = self.client.get(url + '?q=early')
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
names = [r['name'] for r in data['results']]
|
||||
|
||||
assert 'early-bird' in names
|
||||
assert 'early-vip' in names
|
||||
assert 'sponsor' not in names
|
||||
|
||||
def test_tag_typeahead_excludes_waiting_list(self):
|
||||
with scopes_disabled():
|
||||
v = self.event.vouchers.create(item=self.ticket, code='AAAAAAA', tag='waiting-list')
|
||||
WaitingListEntry.objects.create(
|
||||
event=self.event, item=self.ticket, email='foo@example.com', voucher=v
|
||||
)
|
||||
self.event.vouchers.create(item=self.ticket, code='BBBBBBB', tag='walk-ins')
|
||||
|
||||
url = '/control/event/%s/%s/vouchers/tags/typeahead' % (self.orga.slug, self.event.slug)
|
||||
resp = self.client.get(url + '?q=wa')
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
names = [r['name'] for r in data['results']]
|
||||
|
||||
assert 'walk-ins' in names
|
||||
assert 'waiting-list' not in names
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestButtonJourney:
|
||||
"""
|
||||
Complete purchase journeys for a regular event.
|
||||
Tests item visibility, variation visibility, adding items and variations to cart, and checkout up until showing the iframe.
|
||||
"""
|
||||
|
||||
def test_no_predefined_items_journey(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
|
||||
widget_page.goto_button_test_page(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
button = page.locator('.pretix-button')
|
||||
button.click()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
expect(iframe.locator('#btn-add-to-cart')).to_be_visible(timeout=15000)
|
||||
|
||||
def test_predefined_items_journey(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
item_str = ','.join(f'item_{item.pk}=1' for item in items)
|
||||
|
||||
widget_page.goto_button_test_page(
|
||||
live_server_url, organizer.slug, event.slug, items=item_str)
|
||||
|
||||
button = page.locator('.pretix-button')
|
||||
button.click()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(iframe.locator('text=/200\\.00/')).to_be_visible(timeout=15000)
|
||||
|
||||
def test_subevent_button_journey(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
event, subevents = event_series
|
||||
subevent = subevents[2]
|
||||
|
||||
widget_page.goto_button_test_page(
|
||||
live_server_url, organizer.slug, event.slug,
|
||||
subevent=str(subevent.pk))
|
||||
|
||||
button = page.locator('.pretix-button')
|
||||
button.click()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
expect(iframe.locator('#btn-add-to-cart')).to_be_visible(timeout=15000)
|
||||
page.pause()
|
||||
expect(iframe.get_by_role('heading', name='Concert Night 3')).to_be_visible(timeout=15000)
|
||||
@@ -0,0 +1,159 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestEventSeriesJourney:
|
||||
"""
|
||||
Complete purchase journeys for an event series.
|
||||
Tests the different views (calendar, list, week) and adds multiple items to the cart.
|
||||
"""
|
||||
|
||||
def test_multi_subevent_journey_calendar_view(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
event, _subevents = event_series
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
# **{'list-type': 'list'}
|
||||
)
|
||||
page.locator('.pretix-widget-event-calendar-event').first.click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 2)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
expect(
|
||||
iframe.locator('text=/45\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
|
||||
widget_page.close_iframe()
|
||||
page.locator('a[rel="back"]').click()
|
||||
|
||||
widget_page.wait_for_view('.pretix-widget-event-calendar-next-month')
|
||||
|
||||
page.locator('.pretix-widget-event-calendar-next-month').click()
|
||||
widget_page.wait_for_loading_indicator()
|
||||
page.locator('.pretix-widget-event-calendar-event').first.click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 1)
|
||||
widget_page.click_buy_button()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(
|
||||
iframe.locator('text=/90\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
|
||||
def test_multi_subevent_journey_list_view(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
event, _subevents = event_series
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'list'}
|
||||
)
|
||||
|
||||
page.locator('.pretix-widget-event-list-entry').first.click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 2)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
expect(
|
||||
iframe.locator('text=/45\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
|
||||
widget_page.close_iframe()
|
||||
page.locator('a[rel="back"]').click()
|
||||
|
||||
widget_page.wait_for_view('.pretix-widget-event-list-entry')
|
||||
|
||||
page.locator('.pretix-widget-event-list-entry').nth(1).click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 1)
|
||||
widget_page.click_buy_button()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(
|
||||
iframe.locator('text=/90\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
|
||||
def test_multi_subevent_journey_week_view(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
event, _subevents = event_series
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'week'}
|
||||
)
|
||||
page.locator('.pretix-widget-event-calendar-event').first.click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 2)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
expect(
|
||||
iframe.locator('text=/45\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
|
||||
widget_page.close_iframe()
|
||||
page.locator('a[rel="back"]').click()
|
||||
|
||||
widget_page.wait_for_view('.pretix-widget-event-calendar-event')
|
||||
|
||||
page.locator('.pretix-widget-event-calendar-next-month').click()
|
||||
widget_page.wait_for_loading_indicator()
|
||||
page.locator('.pretix-widget-event-calendar-event').first.click()
|
||||
|
||||
widget_page.select_item_quantity('Concert Ticket', 1)
|
||||
widget_page.click_buy_button()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(
|
||||
iframe.locator('text=/90\\.00/').first
|
||||
).to_be_visible(timeout=15000)
|
||||
@@ -0,0 +1,93 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSingleEventJourney:
|
||||
"""
|
||||
Complete purchase journeys for a regular event.
|
||||
Tests item visibility, variation visibility, adding items and variations to cart, and checkout up until showing the iframe.
|
||||
"""
|
||||
|
||||
def test_full_purchase_journey(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
ga_item = page.locator('.pretix-widget-item:has-text("General Admission")')
|
||||
expect(ga_item).to_be_visible()
|
||||
expect(ga_item.locator('text=/50\\.00/')).to_be_visible()
|
||||
|
||||
vip_item = page.locator('.pretix-widget-item:has-text("VIP Ticket")')
|
||||
expect(vip_item).to_be_visible()
|
||||
expect(vip_item.locator('text=/150\\.00/')).to_be_visible()
|
||||
|
||||
widget_page.select_item_quantity('General Admission', 2)
|
||||
widget_page.select_item_quantity('VIP Ticket', 1)
|
||||
|
||||
widget_page.click_buy_button()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(iframe.locator('text=/250\\.00/')).to_be_visible(timeout=15000)
|
||||
|
||||
def test_journey_with_variations(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
item, _variations = item_with_variations
|
||||
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
widget_page.expand_variations(item.name)
|
||||
large_var = page.locator('.pretix-widget-variation:has(strong:text-is("Large"))')
|
||||
expect(large_var).to_be_visible()
|
||||
expect(large_var.locator('text=/25\\.00/')).to_be_visible()
|
||||
|
||||
widget_page.select_item_quantity('General Admission', 1)
|
||||
widget_page.select_variation_quantity(item.name, 'Large', 1)
|
||||
|
||||
widget_page.click_buy_button()
|
||||
iframe = widget_page.wait_for_iframe_checkout()
|
||||
|
||||
# TODO a bit janky selector
|
||||
expect(iframe.locator('text=/75\\.00/')).to_be_visible(timeout=15000)
|
||||
@@ -23,6 +23,8 @@ from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import SafeData
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
@@ -77,6 +79,31 @@ TESTVERANST-12345
|
||||
'&bic=BYLADEM1MIL&amount=123%2C00&reason=TESTVERANST-12345¤cy=EUR')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_qr_codes_euro_keeps_allowed_apostrophe_unescaped(env):
|
||||
o, event = env
|
||||
codes = generate_payment_qr_codes(
|
||||
event=event,
|
||||
code='TESTVERANST-12345',
|
||||
amount=Decimal('123.00'),
|
||||
bank_details_sepa_bic='BYLADEM1MIL',
|
||||
bank_details_sepa_iban='DE37796500000069799047',
|
||||
bank_details_sepa_name='Bits\'n"Bugs',
|
||||
)
|
||||
|
||||
qr_data = codes[0]['qr_data']
|
||||
assert '\nBits\'nBugs\n' in qr_data
|
||||
assert not isinstance(qr_data, SafeData)
|
||||
|
||||
html = render_to_string(
|
||||
'pretixpresale/event/payment_qr_codes.html',
|
||||
{'payment_qr_codes': codes},
|
||||
)
|
||||
assert 'type="application/json"' in html
|
||||
assert "\\nBits'nBugs\\n" in html
|
||||
assert ''' not in html
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_qr_codes_swiss(env):
|
||||
o, event = env
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pytest
|
||||
from django.core.exceptions import SuspiciousFileOperation
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
|
||||
def test_http_access_disabled(monkeypatch):
|
||||
def guard(*args, **kwargs):
|
||||
pytest.fail("No internet wanted!")
|
||||
|
||||
monkeypatch.setattr('socket.socket', guard)
|
||||
|
||||
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||
Paragraph(
|
||||
'<img src="https://static.pretix.cloud/static/pretixeu/img/opengraph.png"/>',
|
||||
)
|
||||
|
||||
|
||||
def test_file_access_disabled_scheme(monkeypatch):
|
||||
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||
Paragraph(
|
||||
'<img src="file:///etc/passwd" />',
|
||||
)
|
||||
|
||||
|
||||
def test_file_access_disabled_direct(monkeypatch):
|
||||
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||
Paragraph(
|
||||
'<img src="/etc/passwd" />',
|
||||
)
|
||||
@@ -41,6 +41,10 @@ def test_private_ip_blocked():
|
||||
requests.get("http://10.0.0.1", timeout=0.1)
|
||||
with pytest.raises(HTTPError, match="Request to private address.*"):
|
||||
requests.get("https://10.0.0.1", timeout=0.1)
|
||||
with pytest.raises(HTTPError, match="Request to RFC 6598 address.*"):
|
||||
requests.get("https://100.100.100.100", timeout=0.1)
|
||||
with pytest.raises(HTTPError, match="Request to RFC 6598 address.*"):
|
||||
requests.get("https://[::ffff:100.64.0.1]", timeout=0.1)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -50,17 +54,20 @@ def test_private_ip_blocked():
|
||||
[(AF_INET, SOCK_STREAM, 6, '', ('127.1.1.1', 443))],
|
||||
[(AF_INET, SOCK_STREAM, 6, '', ('192.168.5.3', 443))],
|
||||
[(AF_INET, SOCK_STREAM, 6, '', ('224.0.0.1', 443))],
|
||||
[(AF_INET, SOCK_STREAM, 6, '', ('100.64.0.1', 443))],
|
||||
[(AF_INET, SOCK_STREAM, 6, '', ('100.100.100.100', 443))],
|
||||
[(AF_INET6, SOCK_STREAM, 6, '', ('::1', 443, 0, 0))],
|
||||
[(AF_INET6, SOCK_STREAM, 6, '', ('fe80::1', 443, 0, 0))],
|
||||
[(AF_INET6, SOCK_STREAM, 6, '', ('ff00::1', 443, 0, 0))],
|
||||
[(AF_INET6, SOCK_STREAM, 6, '', ('fc00::1', 443, 0, 0))],
|
||||
[(AF_INET6, SOCK_STREAM, 6, "", ("::ffff:100.64.0.1", 443, 0, 0))],
|
||||
])
|
||||
def test_dns_resolving_to_local_blocked(res):
|
||||
with mock.patch('socket.getaddrinfo') as mock_addr:
|
||||
mock_addr.return_value = res
|
||||
with pytest.raises(HTTPError, match="Request to (multicast|private|local) address.*"):
|
||||
with pytest.raises(HTTPError, match="Request to (multicast|private|local|RFC 6598) address.*"):
|
||||
requests.get("https://example.org", timeout=0.1)
|
||||
with pytest.raises(HTTPError, match="Request to (multicast|private|local) address.*"):
|
||||
with pytest.raises(HTTPError, match="Request to (multicast|private|local|RFC 6598) address.*"):
|
||||
requests.get("http://example.org", timeout=0.1)
|
||||
|
||||
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
from django import urls
|
||||
from django.test import override_settings
|
||||
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
def test_site_url_domain():
|
||||
with override_settings(SITE_URL='https://example.com'):
|
||||
assert build_absolute_uri('control:auth.login') == 'https://example.com/control/login'
|
||||
assert mainreverse_absolute('control:auth.login') == 'https://example.com/control/login'
|
||||
|
||||
|
||||
def test_site_url_subpath():
|
||||
with override_settings(SITE_URL='https://example.com/presale'):
|
||||
old_prefix = urls.get_script_prefix()
|
||||
urls.set_script_prefix('/presale/')
|
||||
assert build_absolute_uri('control:auth.login') == 'https://example.com/presale/control/login'
|
||||
assert mainreverse_absolute('control:auth.login') == 'https://example.com/presale/control/login'
|
||||
urls.set_script_prefix(old_prefix)
|
||||
|
||||
@@ -26,7 +26,7 @@ from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.testutils.queries import assert_num_queries
|
||||
|
||||
|
||||
@@ -248,20 +248,20 @@ def test_event_custom_domain_cache_clear(env):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_main_domain_absolute(env):
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://example.com/mrmcd/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://example.com/mrmcd/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://barfoo/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://barfoo/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_org_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://foobar/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -269,4 +269,4 @@ def test_event_org_alt_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
|
||||
@@ -43,7 +43,7 @@ RES_RULE = {
|
||||
"limit_products": [],
|
||||
"limit_variations": [],
|
||||
"all_payment_methods": True,
|
||||
"limit_payment_methods": set(),
|
||||
"limit_payment_methods": [],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -834,3 +834,56 @@ def test_ambigious_date_with_region(env, job):
|
||||
|
||||
with scopes_disabled():
|
||||
assert env[2].payments.last().info_data["date"] == "2016-05-03"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_name_prefix_contains_dash(env, orga_job):
|
||||
event, user, o1, o2 = env
|
||||
slugs = ['dummy-2', 'dummy2345']
|
||||
for slug in slugs:
|
||||
event = Event.objects.create(
|
||||
organizer=event.organizer, name=slug.upper(), slug=slug,
|
||||
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'
|
||||
)
|
||||
with scopes_disabled():
|
||||
o1.event = Event.objects.get(slug="dummy2345")
|
||||
o1.save()
|
||||
process_banktransfers(orga_job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': f'DUMMY2345-{o1.code}',
|
||||
'date': '2016-01-26',
|
||||
'amount': '23.00'
|
||||
}])
|
||||
with scopes_disabled():
|
||||
job = BankImportJob.objects.last()
|
||||
t = job.transactions.last()
|
||||
assert t.state == BankTransaction.STATE_VALID
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.django_db
|
||||
def test_event_name_prefix_multiple_dashes(env, orga_job):
|
||||
# We decided to ignore the case of multiple events where the event slugs only differ in dashes
|
||||
# for now. If we change that, switch this test case from xfail to regular test.
|
||||
|
||||
event, user, o1, o2 = env
|
||||
slugs = ['dummy-2', 'dummy--2', 'dummy2345']
|
||||
for slug in slugs:
|
||||
event = Event.objects.create(
|
||||
organizer=event.organizer, name=slug.upper(), slug=slug,
|
||||
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'
|
||||
)
|
||||
|
||||
with scopes_disabled():
|
||||
o1.event = Event.objects.get(slug="dummy-2")
|
||||
o1.save()
|
||||
process_banktransfers(orga_job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': f'DUMMY2-{o1.code}',
|
||||
'date': '2016-01-26',
|
||||
'amount': '23.00'
|
||||
}])
|
||||
with scopes_disabled():
|
||||
job = BankImportJob.objects.last()
|
||||
t = job.transactions.last()
|
||||
assert t.state == BankTransaction.STATE_VALID
|
||||
|
||||
@@ -25,10 +25,11 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
from django.core import mail as djmail
|
||||
from django.db.models import F
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import InvoiceAddress, Order
|
||||
from pretix.base.models import Event, InvoiceAddress, Order
|
||||
from pretix.base.services.checkin import perform_checkin
|
||||
from pretix.plugins.sendmail.models import Rule, ScheduledMail
|
||||
from pretix.plugins.sendmail.signals import sendmail_run_rules
|
||||
@@ -687,3 +688,41 @@ def test_sendmail_context_localization(event, order, pos):
|
||||
|
||||
sendmail_run_rules(None)
|
||||
assert "Hallo Herr Mustermann" in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_event_clone_ignores_rules_with_subevent(event, order, pos):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="subevent 1", date_from=dt_now)
|
||||
|
||||
event.sendmail_rules.create(date_is_absolute=False, send_offset_days=1, send_offset_time=datetime.time(hour=12),
|
||||
subject='Mail To Subevent', template='TestBody', subevent=se1)
|
||||
event.sendmail_rules.create(date_is_absolute=False, send_offset_days=1, send_offset_time=datetime.time(hour=12),
|
||||
subject='Mail To All', template='TestBody')
|
||||
|
||||
assert event.sendmail_rules.count() == 2
|
||||
assert event.scheduledmail_set.count() == 2
|
||||
for rule in event.sendmail_rules.all():
|
||||
assert rule.scheduledmail_set.count() == 1
|
||||
|
||||
copied_event = Event.objects.create(
|
||||
organizer=event.organizer, name='Dummy2', slug='dummy2',
|
||||
date_from=datetime.datetime(2022, 4, 15, 9, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
has_subevents=True
|
||||
)
|
||||
copied_event.copy_data_from(event)
|
||||
copied_event.refresh_from_db()
|
||||
event.refresh_from_db()
|
||||
|
||||
assert copied_event.sendmail_rules.count() == 1
|
||||
assert copied_event.sendmail_rules.first().scheduledmail_set.count() == 0
|
||||
assert str(copied_event.sendmail_rules.first().subject) == "Mail To All"
|
||||
|
||||
copied_event.subevents.create(name="subevent 1 in copied", date_from=dt_now)
|
||||
assert copied_event.sendmail_rules.first().scheduledmail_set.count() == 1
|
||||
|
||||
# Double-Check: no scheduled mails and rules exist where subevent.event and event do not align
|
||||
assert ScheduledMail.objects.filter(rule__subevent__isnull=False).exclude(rule__subevent__event=F('rule__event')).count() == 0
|
||||
assert Rule.objects.filter(subevent__isnull=False).exclude(subevent__event=F('event')).count() == 0
|
||||
|
||||
@@ -33,7 +33,7 @@ from django.conf import settings
|
||||
from django.core import mail as djmail
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.signing import dumps
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test import Client, TestCase, TransactionTestCase
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
@@ -99,6 +99,15 @@ class BaseCheckoutTestCase:
|
||||
self.workshopquota.variations.add(self.workshop2a)
|
||||
self.workshopquota.variations.add(self.workshop2b)
|
||||
|
||||
self.parkingcat = ItemCategory.objects.create(name="Parking", is_addon=True, event=self.event)
|
||||
self.parkingquota = Quota.objects.create(event=self.event, name='Parking', size=5)
|
||||
self.parking1 = Item.objects.create(event=self.event, name='Premium Parking',
|
||||
category=self.parkingcat, default_price=Decimal('15.00'))
|
||||
self.parking2 = Item.objects.create(event=self.event, name='Standard Parking',
|
||||
category=self.parkingcat, default_price=Decimal('5.00'))
|
||||
self.parkingquota.items.add(self.parking1)
|
||||
self.parkingquota.items.add(self.parking2)
|
||||
|
||||
def _set_session(self, key, value):
|
||||
session = self.client.session
|
||||
session['carts'][get_cart_session_key(self.client, self.event)][key] = value
|
||||
@@ -4202,6 +4211,58 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert '35.29' in response.content.decode()
|
||||
assert '10.08' in response.content.decode()
|
||||
|
||||
def test_set_addons_invalid_initial(self):
|
||||
self.event.settings.locales = ['de', 'en']
|
||||
self.event.settings.locale = 'de'
|
||||
with scopes_disabled():
|
||||
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat, min_count=1)
|
||||
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.parkingcat, min_count=1)
|
||||
cp1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() - timedelta(minutes=10)
|
||||
)
|
||||
self.workshop1.free_price = True
|
||||
self.workshop1.save()
|
||||
self.workshop2.free_price = True
|
||||
self.workshop2.save()
|
||||
|
||||
ws1_val = 'cp_{}_item_{}'.format(cp1.pk, self.workshop1.pk)
|
||||
ws1_price = 'cp_{}_item_{}_price'.format(cp1.pk, self.workshop1.pk)
|
||||
ws2a_val = 'cp_{}_variation_{}_{}'.format(cp1.pk, self.workshop2.pk, self.workshop2a.pk)
|
||||
ws2a_price = 'cp_{}_variation_{}_{}_price'.format(cp1.pk, self.workshop2.pk, self.workshop2a.pk)
|
||||
p1_val = 'cp_{}_item_{}'.format(cp1.pk, self.parking1.pk)
|
||||
p2_val = 'cp_{}_item_{}'.format(cp1.pk, self.parking2.pk)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug), {
|
||||
ws1_val: '1',
|
||||
ws2a_val: '1',
|
||||
})
|
||||
assert response.status_code == 200
|
||||
with scopes_disabled():
|
||||
assert cp1.addons.count() == 0
|
||||
doc = BeautifulSoup(response.text, 'lxml')
|
||||
assert doc.find('input', {'name': ws1_val}).attrs.get('checked')
|
||||
assert doc.find('input', {'name': ws2a_val}).attrs.get('checked')
|
||||
assert not doc.find('input', {'name': p1_val}).attrs.get('checked')
|
||||
assert not doc.find('input', {'name': p2_val}).attrs.get('checked')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug), {
|
||||
ws1_val: '1',
|
||||
ws1_price: '222,22',
|
||||
ws2a_val: '1',
|
||||
ws2a_price: '333.33',
|
||||
})
|
||||
assert response.status_code == 200
|
||||
with scopes_disabled():
|
||||
assert cp1.addons.count() == 0
|
||||
doc = BeautifulSoup(response.text, 'lxml')
|
||||
assert doc.find('input', {'name': ws1_val}).attrs.get('checked')
|
||||
assert doc.find('input', {'name': ws1_price}).attrs.get('value') in ['222.22', '222,22']
|
||||
assert doc.find('input', {'name': ws2a_val}).attrs.get('checked')
|
||||
assert doc.find('input', {'name': ws2a_price}).attrs.get('value') in ['333.33', '333,33']
|
||||
assert not doc.find('input', {'name': p1_val}).attrs.get('checked')
|
||||
assert not doc.find('input', {'name': p2_val}).attrs.get('checked')
|
||||
|
||||
def test_confirm_subevent_presale_not_yet(self):
|
||||
with scopes_disabled():
|
||||
self.event.has_subevents = True
|
||||
@@ -4413,6 +4474,18 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert len(djmail.outbox) == 1
|
||||
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
|
||||
|
||||
def test_checkout_empty_session_valid_cart(self):
|
||||
client = Client()
|
||||
with scopes_disabled():
|
||||
api_cid = "{}@api".format(get_random_string(48))
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=api_cid, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = client.get('/%s/%s/w/1234567890abcdef/checkout/questions/' % (self.orga.slug, self.event.slug), query_params={"take_cart_id": api_cid})
|
||||
assert '€23.00' in response.content.decode()
|
||||
|
||||
|
||||
class CheckoutTransactionTestCase(BaseCheckoutTestCase, TransactionTestCase):
|
||||
def test_order_confirmation_mail_invoice_sent_somewhere_else(self):
|
||||
|
||||
@@ -193,7 +193,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
||||
"picture": None,
|
||||
"picture_fullsize": None,
|
||||
"has_variations": 0,
|
||||
"has_variations": False,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
"description": None,
|
||||
@@ -214,7 +214,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"suggested_price": None,
|
||||
"picture": None,
|
||||
"picture_fullsize": None,
|
||||
"has_variations": 4,
|
||||
"has_variations": True,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
"description": None,
|
||||
@@ -284,7 +284,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"includes_mixed_tax_rate": False},
|
||||
"picture": None,
|
||||
"picture_fullsize": None,
|
||||
"has_variations": 0,
|
||||
"has_variations": False,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
"description": None,
|
||||
@@ -331,7 +331,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"suggested_price": None,
|
||||
"picture": None,
|
||||
"picture_fullsize": None,
|
||||
"has_variations": 4,
|
||||
"has_variations": True,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
"description": None,
|
||||
@@ -399,7 +399,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
|
||||
"picture": None,
|
||||
"picture_fullsize": None,
|
||||
"has_variations": 0,
|
||||
"has_variations": False,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
"description": None,
|
||||
@@ -457,7 +457,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
'picture': None,
|
||||
"picture_fullsize": None,
|
||||
'description': None,
|
||||
'has_variations': 2,
|
||||
'has_variations': True,
|
||||
"allow_waitinglist": True,
|
||||
"mandatory_priced_addons": False,
|
||||
'current_unavailability_reason': None,
|
||||
|
||||
Reference in New Issue
Block a user