mirror of
https://github.com/pretix/pretix.git
synced 2026-08-28 13:34:40 +00:00
introduce missing check_ts everywhere and restructure tests
This commit is contained in:
@@ -179,12 +179,13 @@ PositionSet: TypeAlias = Set[OrderPosition]
|
|||||||
|
|
||||||
|
|
||||||
class PositionCheckFn(Protocol):
|
class PositionCheckFn(Protocol):
|
||||||
def __call__(self, order: Order, keep: PositionSet, position: OrderPosition, /) -> CheckResult:
|
def __call__(self, order: Order, keep: PositionSet, position: OrderPosition, check_ts: datetime.datetime,
|
||||||
|
/) -> CheckResult:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class ProcessCheckFn(Protocol):
|
class ProcessCheckFn(Protocol):
|
||||||
def __call__(self, order: Order, keep: PositionSet, /) -> CheckResult:
|
def __call__(self, order: Order, keep: PositionSet, check_ts: datetime.datetime, /) -> CheckResult:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@@ -197,11 +198,11 @@ class CancellationCheck:
|
|||||||
related_selects: List[str] = field(default_factory=list)
|
related_selects: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
def evaluate(self, order: Order, keep: PositionSet,
|
def evaluate(self, order: Order, keep: PositionSet,
|
||||||
position: OrderPosition | None) -> CheckResult:
|
position: OrderPosition | None, check_ts: datetime.datetime) -> CheckResult:
|
||||||
if position and self.type == CheckTypes.POSITION:
|
if position and self.type == CheckTypes.POSITION:
|
||||||
return self.check_fn(order, keep, position)
|
return self.check_fn(order, keep, position, check_ts)
|
||||||
elif position is None and self.type == CheckTypes.PROCESS:
|
elif position is None and self.type == CheckTypes.PROCESS:
|
||||||
return self.check_fn(order, keep)
|
return self.check_fn(order, keep, check_ts)
|
||||||
else:
|
else:
|
||||||
raise ValidationError("Type of the rule doesn't match the check_fn")
|
raise ValidationError("Type of the rule doesn't match the check_fn")
|
||||||
|
|
||||||
@@ -256,6 +257,7 @@ class ProcessResult:
|
|||||||
class CancellationResult:
|
class CancellationResult:
|
||||||
position_result: PositionResult
|
position_result: PositionResult
|
||||||
process_result: ProcessResult
|
process_result: ProcessResult
|
||||||
|
check_ts: datetime.datetime
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cancellation_possible(self) -> bool:
|
def cancellation_possible(self) -> bool:
|
||||||
@@ -443,7 +445,7 @@ class CancellationRule(models.Model):
|
|||||||
process_result = ProcessResult(process_check_results=process_check_results,
|
process_result = ProcessResult(process_check_results=process_check_results,
|
||||||
process_rule_results=process_rule_results)
|
process_rule_results=process_rule_results)
|
||||||
|
|
||||||
return CancellationResult(position_result=position_results, process_result=process_result)
|
return CancellationResult(position_result=position_results, process_result=process_result, check_ts=check_ts)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _prefetch_order(event: Event, order: Order, checks: Checks) -> Order:
|
def _prefetch_order(event: Event, order: Order, checks: Checks) -> Order:
|
||||||
|
|||||||
@@ -3563,7 +3563,8 @@ def signal_listener_issue_media(sender: Event, order: Order, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def position_not_used_cancellation_check(order: Order, keep: PositionSet, position: OrderPosition):
|
def position_not_used_cancellation_check(order: Order, keep: PositionSet, position: OrderPosition,
|
||||||
|
check_ts: datetime):
|
||||||
for pos in order.all_positions.all():
|
for pos in order.all_positions.all():
|
||||||
if pos == position and position not in keep:
|
if pos == position and position not in keep:
|
||||||
for checkin in pos.all_checkins.all():
|
for checkin in pos.all_checkins.all():
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.models.cancellation import (
|
from pretix.base.models.cancellation import (
|
||||||
CancellationCheck, CancellationRule, CheckResult, Checks, CheckTypes,
|
CancellationCheck, CancellationRule, CheckResult, Checks, CheckTypes,
|
||||||
FeeType, PositionResult, ProcessCancellationRule, ProcessResult, RuleResult,
|
FeeType, PositionCancellationRule, PositionResult, ProcessCancellationRule, ProcessResult, RuleResult,
|
||||||
)
|
)
|
||||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||||
from pretix.base.services.orders import signal_listener_position_not_used
|
from pretix.base.services.orders import signal_listener_position_not_used
|
||||||
@@ -82,16 +82,17 @@ def make_rule_result(fee, *, possible: bool = True, fee_type: FeeType = FeeType.
|
|||||||
|
|
||||||
|
|
||||||
def make_cancellation_check(id: str, type: CheckTypes, result: bool, prefetches=None,
|
def make_cancellation_check(id: str, type: CheckTypes, result: bool, prefetches=None,
|
||||||
related_selects=None) -> CancellationCheck:
|
related_selects=None,
|
||||||
|
check_ts: datetime = datetime.now(tz=UTC)) -> CancellationCheck:
|
||||||
if related_selects is None:
|
if related_selects is None:
|
||||||
related_selects = []
|
related_selects = []
|
||||||
if prefetches is None:
|
if prefetches is None:
|
||||||
prefetches = []
|
prefetches = []
|
||||||
|
|
||||||
def position_check_fn(_order, _keep, _position):
|
def position_check_fn(_order, _keep, _position, _check_ts=check_ts):
|
||||||
return make_check_result(result, id=id)
|
return make_check_result(result, id=id)
|
||||||
|
|
||||||
def check_fn(_order, _keep):
|
def check_fn(_order, _keep, _check_ts=check_ts):
|
||||||
return make_check_result(result, id=id)
|
return make_check_result(result, id=id)
|
||||||
|
|
||||||
if type == CheckTypes.POSITION:
|
if type == CheckTypes.POSITION:
|
||||||
@@ -100,326 +101,405 @@ def make_cancellation_check(id: str, type: CheckTypes, result: bool, prefetches=
|
|||||||
return CancellationCheck(id, type, check_fn, prefetches=prefetches, related_selects=related_selects)
|
return CancellationCheck(id, type, check_fn, prefetches=prefetches, related_selects=related_selects)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("partial_results,expected", [
|
class TestRuleResult:
|
||||||
([True], True),
|
@pytest.mark.parametrize("partial_results,expected", [
|
||||||
([False], False),
|
([True], True),
|
||||||
([True, True], True),
|
([False], False),
|
||||||
([False, False], False),
|
([True, True], True),
|
||||||
([True, False], False),
|
([False, False], False),
|
||||||
])
|
([True, False], False),
|
||||||
def test_rule_result_cancellation_possible(partial_results: List[bool], expected: bool):
|
])
|
||||||
check_results = [make_check_result(state) for state in partial_results]
|
def test_rule_result_cancellation_possible(self, partial_results: List[bool], expected: bool):
|
||||||
|
check_results = [make_check_result(state) for state in partial_results]
|
||||||
|
|
||||||
result = RuleResult(id=1, partial_results=check_results, fee_type=FeeType.POSITION, fee=Decimal(0))
|
result = RuleResult(id=1, partial_results=check_results, fee_type=FeeType.POSITION, fee=Decimal(0))
|
||||||
assert result.cancellation_possible == expected
|
assert result.cancellation_possible == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
@pytest.mark.parametrize(
|
("left", "right", "expected"),
|
||||||
("left", "right", "expected"),
|
[
|
||||||
[
|
(("5", True), ("10", True), True),
|
||||||
(("5", True), ("10", True), True),
|
(("10", True), ("5", True), False),
|
||||||
(("10", True), ("5", True), False),
|
(("5", True), ("5", True), False),
|
||||||
(("5", True), ("5", True), False),
|
(("5", False), ("10", False), True),
|
||||||
(("5", False), ("10", False), True),
|
(("100", True), ("1", False), True),
|
||||||
(("100", True), ("1", False), True),
|
(("1", False), ("100", True), False),
|
||||||
(("1", False), ("100", True), False),
|
],
|
||||||
],
|
ids=[
|
||||||
ids=[
|
"cheaper-lt-pricier-both-possible",
|
||||||
"cheaper-lt-pricier-both-possible",
|
"pricier-not-lt-cheaper-both-possible",
|
||||||
"pricier-not-lt-cheaper-both-possible",
|
"equal-fee-not-lt",
|
||||||
"equal-fee-not-lt",
|
"cheaper-lt-pricier-both-impossible",
|
||||||
"cheaper-lt-pricier-both-impossible",
|
"possible-lt-impossible-despite-higher-fee",
|
||||||
"possible-lt-impossible-despite-higher-fee",
|
"impossible-not-lt-possible",
|
||||||
"impossible-not-lt-possible",
|
],
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_lt_returns_expected(left, right, expected):
|
|
||||||
a = make_rule_result(left[0], possible=left[1])
|
|
||||||
b = make_rule_result(right[0], possible=right[1])
|
|
||||||
assert (a < b) is expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("fee_type", "absolute", "reference", "result"),
|
|
||||||
[
|
|
||||||
(FeeType.MINIMUM, Decimal(10), Decimal(1), Decimal(9)),
|
|
||||||
(FeeType.MINIMUM, Decimal(1), Decimal(10), Decimal(0)),
|
|
||||||
(FeeType.MINIMUM, Decimal(10), Decimal(10), Decimal(0)),
|
|
||||||
(FeeType.ADDITIONAL, Decimal(10), Decimal(1), Decimal(10)),
|
|
||||||
(FeeType.ADDITIONAL, Decimal(1), Decimal(10), Decimal(1)),
|
|
||||||
(FeeType.ADDITIONAL, Decimal(10), Decimal(10), Decimal(10)),
|
|
||||||
],
|
|
||||||
ids=[
|
|
||||||
"minimum-absolute-less-than-reference",
|
|
||||||
"minimum-absolute-more-than-reference",
|
|
||||||
"minimum-absolute-equal-reference",
|
|
||||||
"additional-absolute-less-than-reference",
|
|
||||||
"additional-absolute-more-than-reference",
|
|
||||||
"additional-absolute-equal-reference",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_from_process_fee(fee_type: Literal[FeeType.MINIMUM, FeeType.ADDITIONAL], absolute: Decimal, reference: Decimal,
|
|
||||||
result: Decimal):
|
|
||||||
res = RuleResult.from_process_fee(id=1, partial_results=[],
|
|
||||||
fee_type=fee_type, absolute_fee=absolute, reference_price=reference)
|
|
||||||
assert res.fee == result
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("check_results", "rule_results", "cancellation_possible", "fee"),
|
|
||||||
[
|
|
||||||
({1: [make_check_result(True)]}, {1: [make_rule_result(Decimal(10), possible=True)]}, True, Decimal(10)),
|
|
||||||
({1: [make_check_result(False)]}, {1: [make_rule_result(Decimal(10), possible=True)]}, False, Decimal(10)),
|
|
||||||
({1: [make_check_result(False)]}, {1: [make_rule_result(Decimal(10), possible=False)]}, False, Decimal(0)),
|
|
||||||
({1: [make_check_result(True), make_check_result(False)]}, {1: [make_rule_result(Decimal(10), possible=True)]},
|
|
||||||
False, Decimal(10)),
|
|
||||||
({1: [make_check_result(True)]},
|
|
||||||
{1: [make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=True)]}, True,
|
|
||||||
Decimal(5)),
|
|
||||||
({1: [make_check_result(True)]},
|
|
||||||
{1: [make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=False)]}, False,
|
|
||||||
Decimal(0)),
|
|
||||||
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_position_results(check_results, rule_results, cancellation_possible, fee):
|
|
||||||
pos_res = PositionResult(position_check_results=check_results, position_rule_results=rule_results, )
|
|
||||||
assert pos_res.cancellation_possible == cancellation_possible
|
|
||||||
assert pos_res.fee_value == fee
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("check_results", "rule_results", "cancellation_possible", "fee"),
|
|
||||||
[
|
|
||||||
([make_check_result(True)], [make_rule_result(Decimal(10), possible=True)], True, Decimal(10)),
|
|
||||||
([make_check_result(False)], [make_rule_result(Decimal(10), possible=True)], False, Decimal(10)),
|
|
||||||
([make_check_result(False)], [make_rule_result(Decimal(10), possible=False)], False, Decimal(0)),
|
|
||||||
([make_check_result(True), make_check_result(False)], [make_rule_result(Decimal(10), possible=True)],
|
|
||||||
False, Decimal(10)),
|
|
||||||
([make_check_result(True)],
|
|
||||||
[make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=True)], True,
|
|
||||||
Decimal(5)),
|
|
||||||
([make_check_result(True)],
|
|
||||||
[make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=False)], False,
|
|
||||||
Decimal(0)),
|
|
||||||
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_process_results(check_results, rule_results, cancellation_possible, fee):
|
|
||||||
pos_res = ProcessResult(process_check_results=check_results, process_rule_results=rule_results, )
|
|
||||||
assert pos_res.cancellation_possible == cancellation_possible
|
|
||||||
assert pos_res.fee_value == fee
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("received", "position_checks", "process_checks", "raises"),
|
|
||||||
[
|
|
||||||
([('', make_cancellation_check('pos-1', CheckTypes.POSITION, True))],
|
|
||||||
[make_cancellation_check('pos-1', CheckTypes.POSITION, True)],
|
|
||||||
[],
|
|
||||||
contextlib.nullcontext()),
|
|
||||||
([('', make_cancellation_check('proc-1', CheckTypes.PROCESS, True))],
|
|
||||||
[],
|
|
||||||
[make_cancellation_check('proc-1', CheckTypes.PROCESS, True)],
|
|
||||||
contextlib.nullcontext()),
|
|
||||||
([('', make_cancellation_check('proc-1', CheckTypes.PROCESS, True)),
|
|
||||||
('', make_cancellation_check('proc-1', CheckTypes.PROCESS, True))],
|
|
||||||
[],
|
|
||||||
[make_cancellation_check('proc-1', CheckTypes.PROCESS, True)],
|
|
||||||
pytest.raises(ValueError)),
|
|
||||||
([('', make_cancellation_check('pos-1', CheckTypes.POSITION, True)),
|
|
||||||
('', make_cancellation_check('pos-1', CheckTypes.POSITION, True))],
|
|
||||||
[],
|
|
||||||
[make_cancellation_check('pos-1', CheckTypes.POSITION, True)],
|
|
||||||
pytest.raises(ValueError)),
|
|
||||||
([('', 1)],
|
|
||||||
[],
|
|
||||||
[make_cancellation_check('pos-1', CheckTypes.POSITION, True)],
|
|
||||||
pytest.raises(ValueError)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_cancellation_rule_collect_checks(received, position_checks, process_checks, raises):
|
|
||||||
event = cast(Event, cast(object, {}))
|
|
||||||
|
|
||||||
def send_fn(_event):
|
|
||||||
return received
|
|
||||||
|
|
||||||
with raises:
|
|
||||||
checks = CancellationRule._collect_checks(event=event, send_fn=send_fn)
|
|
||||||
assert checks.position == position_checks
|
|
||||||
assert checks.process == process_checks
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_prefetch_empty(event, order):
|
|
||||||
checks = Checks(position=[], process=[])
|
|
||||||
with scope(organizer=event.organizer):
|
|
||||||
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
|
||||||
assert prefetched_order.id == order.id
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_prefetch_incl_values_select_related(event, order):
|
|
||||||
checks = Checks(
|
|
||||||
position=[
|
|
||||||
make_cancellation_check('pos_1', CheckTypes.POSITION, True, prefetches=[lambda: Prefetch('all_positions')],
|
|
||||||
related_selects=['organizer'])],
|
|
||||||
process=[
|
|
||||||
make_cancellation_check('proc_1', CheckTypes.PROCESS, True, prefetches=[lambda: Prefetch('all_positions')],
|
|
||||||
related_selects=['organizer'])]
|
|
||||||
)
|
)
|
||||||
|
def test_lt_returns_expected(self, left, right, expected):
|
||||||
|
a = make_rule_result(left[0], possible=left[1])
|
||||||
|
b = make_rule_result(right[0], possible=right[1])
|
||||||
|
assert (a < b) is expected
|
||||||
|
|
||||||
with scope(organizer=event.organizer):
|
@pytest.mark.parametrize(
|
||||||
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
("fee_type", "absolute", "reference", "result"),
|
||||||
assert prefetched_order.id == order.id
|
[
|
||||||
|
(FeeType.MINIMUM, Decimal(10), Decimal(1), Decimal(9)),
|
||||||
|
(FeeType.MINIMUM, Decimal(1), Decimal(10), Decimal(0)),
|
||||||
@pytest.mark.django_db
|
(FeeType.MINIMUM, Decimal(10), Decimal(10), Decimal(0)),
|
||||||
def test_ticket_not_used(event, order, order_position, checkin_list):
|
(FeeType.ADDITIONAL, Decimal(10), Decimal(1), Decimal(10)),
|
||||||
position_not_used_check = signal_listener_position_not_used(event)
|
(FeeType.ADDITIONAL, Decimal(1), Decimal(10), Decimal(1)),
|
||||||
checks = Checks(position=[position_not_used_check], process=[])
|
(FeeType.ADDITIONAL, Decimal(10), Decimal(10), Decimal(10)),
|
||||||
keep = set()
|
],
|
||||||
|
ids=[
|
||||||
with scope(organizer=event.organizer):
|
"minimum-absolute-less-than-reference",
|
||||||
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
"minimum-absolute-more-than-reference",
|
||||||
with ensure_no_queries():
|
"minimum-absolute-equal-reference",
|
||||||
result = position_not_used_check.evaluate(prefetched_order, keep, order_position)
|
"additional-absolute-less-than-reference",
|
||||||
assert result.cancellation_possible is True
|
"additional-absolute-more-than-reference",
|
||||||
|
"additional-absolute-equal-reference",
|
||||||
Checkin.objects.create(
|
],
|
||||||
list=checkin_list,
|
|
||||||
position=order_position,
|
|
||||||
successful=True
|
|
||||||
)
|
|
||||||
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
|
||||||
|
|
||||||
with ensure_no_queries():
|
|
||||||
result = position_not_used_check.evaluate(prefetched_order, keep, order_position)
|
|
||||||
|
|
||||||
assert result.cancellation_possible is False
|
|
||||||
|
|
||||||
|
|
||||||
REFERENCE_DT = datetime(2017, 12, 27, 4, 0, 0, tzinfo=UTC)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["date", "datetime", "order", "event"])
|
|
||||||
def rdt_reldate_variants(request):
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def rdt_reldate(rdt_reldate_variants) -> RelativeDateWrapper:
|
|
||||||
if rdt_reldate_variants == 'date' or rdt_reldate_variants == 'datetime':
|
|
||||||
return RelativeDateWrapper.from_string(REFERENCE_DT.isoformat())
|
|
||||||
elif rdt_reldate_variants == 'order':
|
|
||||||
return RelativeDateWrapper(
|
|
||||||
RelativeDate(days=1, time=None, base_date_name='order__datetime', minutes=None, is_after=True))
|
|
||||||
elif rdt_reldate_variants == 'event':
|
|
||||||
return RelativeDateWrapper(
|
|
||||||
RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None, is_after=True))
|
|
||||||
else:
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["single_event", "subevents"])
|
|
||||||
def rdt_event_variants(request):
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def rdt_events(rdt_event_variants, event):
|
|
||||||
if rdt_event_variants == "single_event":
|
|
||||||
event.date_from = REFERENCE_DT
|
|
||||||
event.save()
|
|
||||||
else:
|
|
||||||
event.has_subevents = True
|
|
||||||
event.subevents.create(
|
|
||||||
name='1',
|
|
||||||
date_from=REFERENCE_DT,
|
|
||||||
)
|
|
||||||
event.subevents.create(
|
|
||||||
name='2',
|
|
||||||
date_from=REFERENCE_DT + timedelta(days=1),
|
|
||||||
)
|
|
||||||
event.subevents.create(
|
|
||||||
name='3',
|
|
||||||
date_from=REFERENCE_DT + timedelta(days=2),
|
|
||||||
)
|
|
||||||
return event
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def rdt_item(rdt_events):
|
|
||||||
return rdt_events.items.create(
|
|
||||||
name='Ticket',
|
|
||||||
category=None, default_price=23,
|
|
||||||
admission=True
|
|
||||||
)
|
)
|
||||||
|
def test_from_process_fee(
|
||||||
|
self,
|
||||||
|
fee_type: Literal[FeeType.MINIMUM, FeeType.ADDITIONAL],
|
||||||
|
absolute: Decimal,
|
||||||
|
reference: Decimal,
|
||||||
|
result: Decimal
|
||||||
|
):
|
||||||
|
res = RuleResult.from_process_fee(id=1, partial_results=[],
|
||||||
|
fee_type=fee_type, absolute_fee=absolute, reference_price=reference)
|
||||||
|
assert res.fee == result
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
@pytest.fixture(params=["EARLIEST", "LATEST"])
|
("position_price", "percentage", "result"),
|
||||||
def rdt_mode_variants(request):
|
[
|
||||||
return request.param
|
(Decimal(10), Decimal(10), Decimal(1)),
|
||||||
|
(Decimal(10), Decimal("9.9"), Decimal("0.99"))
|
||||||
|
],
|
||||||
@pytest.fixture
|
|
||||||
def rdt_order(rdt_events):
|
|
||||||
o = Order.objects.create(
|
|
||||||
code='123456', event=rdt_events, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=REFERENCE_DT + timedelta(hours=6), # 6 hours offset mark orders
|
|
||||||
sales_channel=rdt_events.organizer.sales_channels.get(identifier="web"),
|
|
||||||
total=14, locale='en'
|
|
||||||
)
|
)
|
||||||
return o
|
def test_from_relative_fee(self, position_price, percentage, result):
|
||||||
|
res = RuleResult.from_relative_fee(id=1,
|
||||||
|
partial_results=[],
|
||||||
|
fee_type=FeeType.POSITION,
|
||||||
|
position_price=position_price,
|
||||||
|
percentage=percentage,
|
||||||
|
currency="EUR")
|
||||||
|
assert res.fee == result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
class TestPositionResult:
|
||||||
def rdt_order_positions(rdt_event_variants, rdt_events, rdt_item, rdt_order):
|
@pytest.mark.parametrize(
|
||||||
if rdt_event_variants == "single_event":
|
("check_results", "rule_results", "cancellation_possible", "fee"),
|
||||||
op = OrderPosition.objects.create(
|
[
|
||||||
order=rdt_order,
|
({1: [make_check_result(True)]}, {1: [make_rule_result(Decimal(10), possible=True)]}, True, Decimal(10)),
|
||||||
item=rdt_item,
|
({1: [make_check_result(False)]}, {1: [make_rule_result(Decimal(10), possible=True)]}, False, Decimal(10)),
|
||||||
variation=None,
|
({1: [make_check_result(False)]}, {1: [make_rule_result(Decimal(10), possible=False)]}, False, Decimal(0)),
|
||||||
price=Decimal("14"),
|
({1: [make_check_result(True), make_check_result(False)]},
|
||||||
|
{1: [make_rule_result(Decimal(10), possible=True)]},
|
||||||
|
False, Decimal(10)),
|
||||||
|
({1: [make_check_result(True)]},
|
||||||
|
{1: [make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=True)]}, True,
|
||||||
|
Decimal(5)),
|
||||||
|
({1: [make_check_result(True)]},
|
||||||
|
{1: [make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=False)]}, False,
|
||||||
|
Decimal(0)),
|
||||||
|
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_position_results(self, check_results, rule_results, cancellation_possible, fee):
|
||||||
|
pos_res = PositionResult(position_check_results=check_results, position_rule_results=rule_results, )
|
||||||
|
assert pos_res.cancellation_possible == cancellation_possible
|
||||||
|
assert pos_res.fee_value == fee
|
||||||
|
|
||||||
|
|
||||||
|
class TestProcessResults:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("check_results", "rule_results", "cancellation_possible", "fee"),
|
||||||
|
[
|
||||||
|
([make_check_result(True)], [make_rule_result(Decimal(10), possible=True)], True, Decimal(10)),
|
||||||
|
([make_check_result(False)], [make_rule_result(Decimal(10), possible=True)], False, Decimal(10)),
|
||||||
|
([make_check_result(False)], [make_rule_result(Decimal(10), possible=False)], False, Decimal(0)),
|
||||||
|
([make_check_result(True), make_check_result(False)], [make_rule_result(Decimal(10), possible=True)],
|
||||||
|
False, Decimal(10)),
|
||||||
|
([make_check_result(True)],
|
||||||
|
[make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=True)], True,
|
||||||
|
Decimal(5)),
|
||||||
|
([make_check_result(True)],
|
||||||
|
[make_rule_result(Decimal(10), possible=False), make_rule_result(Decimal(5), possible=False)], False,
|
||||||
|
Decimal(0)),
|
||||||
|
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_process_results(self, check_results, rule_results, cancellation_possible, fee):
|
||||||
|
pos_res = ProcessResult(process_check_results=check_results, process_rule_results=rule_results, )
|
||||||
|
assert pos_res.cancellation_possible == cancellation_possible
|
||||||
|
assert pos_res.fee_value == fee
|
||||||
|
|
||||||
|
|
||||||
|
class TestCancellationRule:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("received", "position_checks", "process_checks", "raises"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[make_cancellation_check('pos-1', CheckTypes.POSITION, True)],
|
||||||
|
[0],
|
||||||
|
[],
|
||||||
|
contextlib.nullcontext()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[make_cancellation_check('proc-1', CheckTypes.PROCESS, True)],
|
||||||
|
[],
|
||||||
|
[0],
|
||||||
|
contextlib.nullcontext()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[make_cancellation_check('pos-1', CheckTypes.POSITION, True),
|
||||||
|
make_cancellation_check('proc-1', CheckTypes.PROCESS, True)],
|
||||||
|
[0],
|
||||||
|
[1],
|
||||||
|
contextlib.nullcontext()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[make_cancellation_check('proc-1', CheckTypes.PROCESS, True),
|
||||||
|
make_cancellation_check('proc-1', CheckTypes.PROCESS, True)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
pytest.raises(ValueError)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[make_cancellation_check('pos-1', CheckTypes.POSITION, True),
|
||||||
|
make_cancellation_check('pos-1', CheckTypes.POSITION, True)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
pytest.raises(ValueError)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[('', 1)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
pytest.raises(ValueError)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_cancellation_rule_collect_checks(
|
||||||
|
self,
|
||||||
|
received,
|
||||||
|
position_checks,
|
||||||
|
process_checks,
|
||||||
|
raises
|
||||||
|
):
|
||||||
|
event = cast(Event, cast(object, {}))
|
||||||
|
|
||||||
|
def send_fn(_event):
|
||||||
|
return [("", res) for res in received]
|
||||||
|
|
||||||
|
with raises:
|
||||||
|
checks = CancellationRule._collect_checks(event=event, send_fn=send_fn)
|
||||||
|
|
||||||
|
for pos in process_checks:
|
||||||
|
assert received[pos] in checks.process
|
||||||
|
|
||||||
|
for pos in position_checks:
|
||||||
|
assert received[pos] in checks.position
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_prefetch_no_checks_collected(self, event, order):
|
||||||
|
checks = Checks(position=[], process=[])
|
||||||
|
with scope(organizer=event.organizer):
|
||||||
|
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
||||||
|
assert prefetched_order.id == order.id
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_prefetch_incl_values_select_related(self, event, order):
|
||||||
|
checks = Checks(
|
||||||
|
position=[
|
||||||
|
make_cancellation_check('pos_1', CheckTypes.POSITION, True,
|
||||||
|
prefetches=[lambda: Prefetch('all_positions')],
|
||||||
|
related_selects=['organizer'])],
|
||||||
|
process=[
|
||||||
|
make_cancellation_check('proc_1', CheckTypes.PROCESS, True,
|
||||||
|
prefetches=[lambda: Prefetch('all_positions')],
|
||||||
|
related_selects=['organizer'])]
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
op = []
|
with scope(organizer=event.organizer):
|
||||||
for i in range(0, 3):
|
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
||||||
op.append(OrderPosition.objects.create(
|
assert prefetched_order.id == order.id
|
||||||
subevent=rdt_events.subevents.all()[i],
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_ticket_not_used(self, event, order, order_position, checkin_list):
|
||||||
|
position_not_used_check = signal_listener_position_not_used(event)
|
||||||
|
checks = Checks(position=[position_not_used_check], process=[])
|
||||||
|
keep = set()
|
||||||
|
|
||||||
|
with scope(organizer=event.organizer):
|
||||||
|
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
||||||
|
with ensure_no_queries():
|
||||||
|
result = position_not_used_check.evaluate(prefetched_order, keep, order_position, datetime.now(tz=UTC))
|
||||||
|
assert result.cancellation_possible is True
|
||||||
|
|
||||||
|
Checkin.objects.create(
|
||||||
|
list=checkin_list,
|
||||||
|
position=order_position,
|
||||||
|
successful=True
|
||||||
|
)
|
||||||
|
prefetched_order = CancellationRule._prefetch_order(event, order, checks)
|
||||||
|
|
||||||
|
with ensure_no_queries():
|
||||||
|
result = position_not_used_check.evaluate(prefetched_order, keep, order_position, datetime.now(tz=UTC))
|
||||||
|
|
||||||
|
assert result.cancellation_possible is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolveDateFields:
|
||||||
|
REFERENCE_DT = datetime(2017, 12, 27, 4, 0, 0, tzinfo=UTC)
|
||||||
|
|
||||||
|
@pytest.fixture(params=["date", "datetime", "order", "event"])
|
||||||
|
def rdt_reldate_variants(self, request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rdt_reldate(self, rdt_reldate_variants) -> RelativeDateWrapper:
|
||||||
|
if rdt_reldate_variants == 'date' or rdt_reldate_variants == 'datetime':
|
||||||
|
return RelativeDateWrapper.from_string(self.REFERENCE_DT.isoformat())
|
||||||
|
elif rdt_reldate_variants == 'order':
|
||||||
|
return RelativeDateWrapper(
|
||||||
|
RelativeDate(days=1, time=None, base_date_name='order__datetime', minutes=None, is_after=True))
|
||||||
|
elif rdt_reldate_variants == 'event':
|
||||||
|
return RelativeDateWrapper(
|
||||||
|
RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None, is_after=True))
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
@pytest.fixture(params=["single_event", "subevents"])
|
||||||
|
def rdt_event_variants(self, request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rdt_events(self, rdt_event_variants, event):
|
||||||
|
if rdt_event_variants == "single_event":
|
||||||
|
event.date_from = self.REFERENCE_DT
|
||||||
|
event.save()
|
||||||
|
else:
|
||||||
|
event.has_subevents = True
|
||||||
|
event.subevents.create(
|
||||||
|
name='1',
|
||||||
|
date_from=self.REFERENCE_DT,
|
||||||
|
)
|
||||||
|
event.subevents.create(
|
||||||
|
name='2',
|
||||||
|
date_from=self.REFERENCE_DT + timedelta(days=1),
|
||||||
|
)
|
||||||
|
event.subevents.create(
|
||||||
|
name='3',
|
||||||
|
date_from=self.REFERENCE_DT + timedelta(days=2),
|
||||||
|
)
|
||||||
|
return event
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rdt_item(self, rdt_events):
|
||||||
|
return rdt_events.items.create(
|
||||||
|
name='Ticket',
|
||||||
|
category=None, default_price=23,
|
||||||
|
admission=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture(params=["EARLIEST", "LATEST"])
|
||||||
|
def rdt_mode_variants(self, request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rdt_order(self, rdt_events):
|
||||||
|
o = Order.objects.create(
|
||||||
|
code='123456', event=rdt_events, email='dummy@dummy.test',
|
||||||
|
status=Order.STATUS_PENDING,
|
||||||
|
datetime=self.REFERENCE_DT + timedelta(hours=6), # 6 hours offset mark orders
|
||||||
|
sales_channel=rdt_events.organizer.sales_channels.get(identifier="web"),
|
||||||
|
total=14, locale='en'
|
||||||
|
)
|
||||||
|
return o
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rdt_order_positions(self, rdt_event_variants, rdt_events, rdt_item, rdt_order):
|
||||||
|
if rdt_event_variants == "single_event":
|
||||||
|
op = [OrderPosition.objects.create(
|
||||||
order=rdt_order,
|
order=rdt_order,
|
||||||
item=rdt_item,
|
item=rdt_item,
|
||||||
variation=None,
|
variation=None,
|
||||||
price=Decimal("14"),
|
price=Decimal("14"),
|
||||||
))
|
)]
|
||||||
return op
|
else:
|
||||||
|
op = []
|
||||||
|
for i in range(0, 3):
|
||||||
|
op.append(OrderPosition.objects.create(
|
||||||
|
subevent=rdt_events.subevents.all()[i],
|
||||||
|
order=rdt_order,
|
||||||
|
item=rdt_item,
|
||||||
|
variation=None,
|
||||||
|
price=Decimal("14"),
|
||||||
|
))
|
||||||
|
return op
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
@pytest.mark.django_db
|
def test_process_rule_resolve_date_field(
|
||||||
def test_resolve_date_field(rdt_reldate, rdt_reldate_variants, rdt_events, rdt_event_variants, rdt_mode_variants,
|
self,
|
||||||
rdt_order,
|
rdt_reldate,
|
||||||
rdt_order_positions):
|
rdt_reldate_variants,
|
||||||
with scope(organizer=rdt_events.organizer):
|
rdt_events,
|
||||||
date = ProcessCancellationRule._resolve_date_field(rdt_reldate, rdt_order, rdt_mode_variants)
|
rdt_event_variants,
|
||||||
match rdt_reldate_variants:
|
rdt_mode_variants,
|
||||||
case "date":
|
rdt_order,
|
||||||
assert date == REFERENCE_DT
|
rdt_order_positions
|
||||||
case "datetime":
|
):
|
||||||
assert date == REFERENCE_DT
|
with scope(organizer=rdt_events.organizer):
|
||||||
case "order":
|
date = ProcessCancellationRule._resolve_date_field(rdt_reldate, rdt_order, rdt_mode_variants)
|
||||||
assert date == REFERENCE_DT + timedelta(days=1) + timedelta(hours=6)
|
match rdt_reldate_variants:
|
||||||
case "event":
|
case "date":
|
||||||
if rdt_event_variants == "single_event":
|
assert date == self.REFERENCE_DT
|
||||||
assert date == REFERENCE_DT + timedelta(days=1)
|
case "datetime":
|
||||||
elif rdt_event_variants == "subevents":
|
assert date == self.REFERENCE_DT
|
||||||
if rdt_mode_variants == "EARLIEST":
|
case "order":
|
||||||
assert date == REFERENCE_DT + timedelta(days=1)
|
assert date == self.REFERENCE_DT + timedelta(days=1) + timedelta(hours=6)
|
||||||
elif rdt_mode_variants == "LATEST":
|
case "event":
|
||||||
assert date == REFERENCE_DT + timedelta(days=1) + timedelta(days=2)
|
if rdt_event_variants == "single_event":
|
||||||
|
assert date == self.REFERENCE_DT + timedelta(days=1)
|
||||||
|
elif rdt_event_variants == "subevents":
|
||||||
|
if rdt_mode_variants == "EARLIEST":
|
||||||
|
assert date == self.REFERENCE_DT + timedelta(days=1)
|
||||||
|
elif rdt_mode_variants == "LATEST":
|
||||||
|
assert date == self.REFERENCE_DT + timedelta(days=1) + timedelta(days=2)
|
||||||
|
else:
|
||||||
|
raise ValueError("Variant not known")
|
||||||
else:
|
else:
|
||||||
raise ValueError("Variant not known")
|
raise ValueError("Variant not known")
|
||||||
else:
|
case _:
|
||||||
raise ValueError("Variant not known")
|
raise ValueError("Variant not known")
|
||||||
case _:
|
|
||||||
raise ValueError("Variant not known")
|
@pytest.mark.django_db
|
||||||
|
def test_position_rule_resolve_date_field(
|
||||||
|
self,
|
||||||
|
rdt_reldate,
|
||||||
|
rdt_reldate_variants,
|
||||||
|
rdt_events,
|
||||||
|
rdt_event_variants,
|
||||||
|
rdt_order,
|
||||||
|
rdt_order_positions
|
||||||
|
):
|
||||||
|
with scope(organizer=rdt_events.organizer):
|
||||||
|
for pos in rdt_order_positions:
|
||||||
|
date = PositionCancellationRule._resolve_date_field(rdt_reldate, rdt_order, pos)
|
||||||
|
match rdt_reldate_variants:
|
||||||
|
case "date":
|
||||||
|
assert date == self.REFERENCE_DT
|
||||||
|
case "datetime":
|
||||||
|
assert date == self.REFERENCE_DT
|
||||||
|
case "order":
|
||||||
|
assert date == self.REFERENCE_DT + timedelta(days=1) + timedelta(hours=6)
|
||||||
|
case "event":
|
||||||
|
if rdt_event_variants == "single_event":
|
||||||
|
assert date == self.REFERENCE_DT + timedelta(days=1)
|
||||||
|
elif rdt_event_variants == "subevents":
|
||||||
|
assert date == pos.subevent.date_from + timedelta(days=1)
|
||||||
|
else:
|
||||||
|
raise ValueError("Variant not known")
|
||||||
|
case _:
|
||||||
|
raise ValueError("Variant not known")
|
||||||
|
|||||||
Reference in New Issue
Block a user