From 74ff52f6a092d845a30c6e93095aeb04e71a970a Mon Sep 17 00:00:00 2001 From: Lukas Bockstaller Date: Wed, 19 Aug 2026 10:21:31 +0200 Subject: [PATCH] introduce missing check_ts everywhere and restructure tests --- src/pretix/base/models/cancellation.py | 14 +- src/pretix/base/services/orders.py | 3 +- .../base/test_self_service_cancellation.py | 698 ++++++++++-------- 3 files changed, 399 insertions(+), 316 deletions(-) diff --git a/src/pretix/base/models/cancellation.py b/src/pretix/base/models/cancellation.py index cd6bdbac2b..373d257dc1 100644 --- a/src/pretix/base/models/cancellation.py +++ b/src/pretix/base/models/cancellation.py @@ -179,12 +179,13 @@ PositionSet: TypeAlias = Set[OrderPosition] 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): - 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) 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: - 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: - return self.check_fn(order, keep) + return self.check_fn(order, keep, check_ts) else: raise ValidationError("Type of the rule doesn't match the check_fn") @@ -256,6 +257,7 @@ class ProcessResult: class CancellationResult: position_result: PositionResult process_result: ProcessResult + check_ts: datetime.datetime @property def cancellation_possible(self) -> bool: @@ -443,7 +445,7 @@ class CancellationRule(models.Model): process_result = ProcessResult(process_check_results=process_check_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 def _prefetch_order(event: Event, order: Order, checks: Checks) -> Order: diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 83d5d70173..bfab0595a7 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -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(): if pos == position and position not in keep: for checkin in pos.all_checkins.all(): diff --git a/src/tests/base/test_self_service_cancellation.py b/src/tests/base/test_self_service_cancellation.py index 060cf0e303..8a8e9efd62 100644 --- a/src/tests/base/test_self_service_cancellation.py +++ b/src/tests/base/test_self_service_cancellation.py @@ -13,7 +13,7 @@ from pretix.base.models import ( ) from pretix.base.models.cancellation import ( 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.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, - related_selects=None) -> CancellationCheck: + related_selects=None, + check_ts: datetime = datetime.now(tz=UTC)) -> CancellationCheck: if related_selects is None: related_selects = [] if prefetches is None: 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) - def check_fn(_order, _keep): + def check_fn(_order, _keep, _check_ts=check_ts): return make_check_result(result, id=id) 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) -@pytest.mark.parametrize("partial_results,expected", [ - ([True], True), - ([False], False), - ([True, True], True), - ([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] +class TestRuleResult: + @pytest.mark.parametrize("partial_results,expected", [ + ([True], True), + ([False], False), + ([True, True], True), + ([False, False], False), + ([True, False], False), + ]) + 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)) - assert result.cancellation_possible == expected + result = RuleResult(id=1, partial_results=check_results, fee_type=FeeType.POSITION, fee=Decimal(0)) + assert result.cancellation_possible == expected - -@pytest.mark.parametrize( - ("left", "right", "expected"), - [ - (("5", True), ("10", True), True), - (("10", True), ("5", True), False), - (("5", True), ("5", True), False), - (("5", False), ("10", False), True), - (("100", True), ("1", False), True), - (("1", False), ("100", True), False), - ], - ids=[ - "cheaper-lt-pricier-both-possible", - "pricier-not-lt-cheaper-both-possible", - "equal-fee-not-lt", - "cheaper-lt-pricier-both-impossible", - "possible-lt-impossible-despite-higher-fee", - "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'])] + @pytest.mark.parametrize( + ("left", "right", "expected"), + [ + (("5", True), ("10", True), True), + (("10", True), ("5", True), False), + (("5", True), ("5", True), False), + (("5", False), ("10", False), True), + (("100", True), ("1", False), True), + (("1", False), ("100", True), False), + ], + ids=[ + "cheaper-lt-pricier-both-possible", + "pricier-not-lt-cheaper-both-possible", + "equal-fee-not-lt", + "cheaper-lt-pricier-both-impossible", + "possible-lt-impossible-despite-higher-fee", + "impossible-not-lt-possible", + ], ) + 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): - prefetched_order = CancellationRule._prefetch_order(event, order, checks) - assert prefetched_order.id == order.id - - -@pytest.mark.django_db -def test_ticket_not_used(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) - 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) - - 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 + @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( + 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.fixture(params=["EARLIEST", "LATEST"]) -def rdt_mode_variants(request): - return request.param - - -@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' + @pytest.mark.parametrize( + ("position_price", "percentage", "result"), + [ + (Decimal(10), Decimal(10), Decimal(1)), + (Decimal(10), Decimal("9.9"), Decimal("0.99")) + ], ) - 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 -def rdt_order_positions(rdt_event_variants, rdt_events, rdt_item, rdt_order): - if rdt_event_variants == "single_event": - op = OrderPosition.objects.create( - order=rdt_order, - item=rdt_item, - variation=None, - price=Decimal("14"), +class TestPositionResult: + @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(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 = [] - for i in range(0, 3): - op.append(OrderPosition.objects.create( - subevent=rdt_events.subevents.all()[i], + + 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_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, item=rdt_item, variation=None, 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 -def test_resolve_date_field(rdt_reldate, rdt_reldate_variants, rdt_events, rdt_event_variants, rdt_mode_variants, - rdt_order, - rdt_order_positions): - with scope(organizer=rdt_events.organizer): - date = ProcessCancellationRule._resolve_date_field(rdt_reldate, rdt_order, rdt_mode_variants) - match rdt_reldate_variants: - case "date": - assert date == REFERENCE_DT - case "datetime": - assert date == REFERENCE_DT - case "order": - assert date == REFERENCE_DT + timedelta(days=1) + timedelta(hours=6) - case "event": - if rdt_event_variants == "single_event": - assert date == REFERENCE_DT + timedelta(days=1) - elif rdt_event_variants == "subevents": - if rdt_mode_variants == "EARLIEST": - assert date == REFERENCE_DT + timedelta(days=1) - elif rdt_mode_variants == "LATEST": - assert date == REFERENCE_DT + timedelta(days=1) + timedelta(days=2) + @pytest.mark.django_db + def test_process_rule_resolve_date_field( + self, + rdt_reldate, + rdt_reldate_variants, + rdt_events, + rdt_event_variants, + rdt_mode_variants, + rdt_order, + rdt_order_positions + ): + with scope(organizer=rdt_events.organizer): + date = ProcessCancellationRule._resolve_date_field(rdt_reldate, rdt_order, rdt_mode_variants) + 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": + 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: raise ValueError("Variant not known") - else: + case _: 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")