@@ -313,11 +313,26 @@ def test_checkins_list_mixed(client, checkin_list_env, query, expected):
313313 assert item_keys == expected
314314
315315
316+ def _validate_position_for_checkin_list (position , checkin_list ):
317+ """
318+ Validate that an OrderPosition belongs to the event and is allowed in the check-in list.
319+ """
320+ if position .order .event != checkin_list .event :
321+ return False
322+
323+ if checkin_list .all_products :
324+ return True
325+
326+ return position .item in checkin_list .limit_products .all ()
327+
328+
316329@pytest .mark .django_db
317330def test_manual_checkins (client , checkin_list_env ):
318331 client .
login (
email = '[email protected] ' ,
password = 'dummy' )
319332 with scopes_disabled ():
320333 assert not checkin_list_env [5 ][3 ].checkins .exists ()
334+
335+ # Test with valid position
321336 client .post (
322337 '/control/event/dummy/dummy/checkinlists/{}/' .format (checkin_list_env [6 ].pk ),
323338 {'checkin' : [checkin_list_env [5 ][3 ].pk ]},
@@ -329,6 +344,60 @@ def test_manual_checkins(client, checkin_list_env):
329344 ).exists ()
330345
331346
347+ @pytest .mark .django_db
348+ def test_manual_checkins_unauthorized_position (client , checkin_list_env ):
349+ client .
login (
email = '[email protected] ' ,
password = 'dummy' )
350+
351+ # Create a position from a different event that shouldn't be checkable
352+ other_orga = Organizer .objects .create (name = 'Other' , slug = 'other' )
353+ other_event = Event .objects .create (
354+ organizer = other_orga ,
355+ name = 'Other Event' ,
356+ slug = 'other' ,
357+ date_from = now (),
358+ )
359+ other_item = Item .objects .create (event = other_event , name = 'Other Ticket' , default_price = 23 , admission = True )
360+ other_order = Order .objects .create (
361+ code = 'OTHER' ,
362+ event = other_event ,
363+ 364+ status = Order .STATUS_PAID ,
365+ datetime = now (),
366+ expires = now () + timedelta (days = 10 ),
367+ total = 23 ,
368+ locale = 'en' ,
369+ )
370+ other_position = OrderPosition .objects .create (
371+ order = other_order ,
372+ item = other_item ,
373+ variation = None ,
374+ price = Decimal ('23' ),
375+ attendee_name_parts = {'full_name' : 'Other' },
376+ )
377+
378+ # Try to check in the unauthorized position
379+ with scopes_disabled ():
380+ assert not _validate_position_for_checkin_list (other_position , checkin_list_env [6 ])
381+ initial_checkin_count = Checkin .objects .count ()
382+ initial_log_count = LogEntry .objects .count ()
383+
384+ client .post (
385+ '/control/event/dummy/dummy/checkinlists/{}/' .format (checkin_list_env [6 ].pk ),
386+ {'checkin' : [other_position .pk ]},
387+ )
388+
389+ # Verify the unauthorized position was not checked in
390+ with scopes_disabled ():
391+ assert Checkin .objects .count () == initial_checkin_count
392+ assert not other_position .checkins .exists ()
393+ # Verify no log entries were created for the unauthorized operation
394+ assert LogEntry .objects .count () == initial_log_count
395+ assert not LogEntry .objects .filter (
396+ action_type = 'pretix.event.checkin' ,
397+ object_id = other_position .order .pk
398+ ).exists ()
399+
400+
332401@pytest .mark .django_db
333402def test_manual_checkins_revert (client , checkin_list_env ):
334403 client .
login (
email = '[email protected] ' ,
password = 'dummy' )
@@ -353,6 +422,61 @@ def test_manual_checkins_revert(client, checkin_list_env):
353422 ).exists ()
354423
355424
425+ @pytest .mark .django_db
426+ def test_manual_checkins_revert_unauthorized_position (client , checkin_list_env ):
427+ client .
login (
email = '[email protected] ' ,
password = 'dummy' )
428+
429+ # Create a position from a different event
430+ other_orga = Organizer .objects .create (name = 'Other' , slug = 'other' )
431+ other_event = Event .objects .create (
432+ organizer = other_orga ,
433+ name = 'Other Event' ,
434+ slug = 'other' ,
435+ date_from = now (),
436+ )
437+ other_item = Item .objects .create (event = other_event , name = 'Other Ticket' , default_price = 23 , admission = True )
438+ other_order = Order .objects .create (
439+ code = 'OTHER' ,
440+ event = other_event ,
441+ 442+ status = Order .STATUS_PAID ,
443+ datetime = now (),
444+ expires = now () + timedelta (days = 10 ),
445+ total = 23 ,
446+ locale = 'en' ,
447+ )
448+ other_position = OrderPosition .objects .create (
449+ order = other_order ,
450+ item = other_item ,
451+ variation = None ,
452+ price = Decimal ('23' ),
453+ attendee_name_parts = {'full_name' : 'Other' },
454+ )
455+
456+ # Create a checkin for the unauthorized position (simulating it was somehow created)
457+ with scopes_disabled ():
458+ Checkin .objects .create (position = other_position , list = checkin_list_env [6 ])
459+ initial_checkin_count = Checkin .objects .count ()
460+ initial_log_count = LogEntry .objects .count ()
461+
462+ # Try to revert the unauthorized position
463+ client .post (
464+ '/control/event/dummy/dummy/checkinlists/{}/' .format (checkin_list_env [6 ].pk ),
465+ {'checkin' : [other_position .pk ], 'revert' : 'true' },
466+ )
467+
468+ # Verify the unauthorized position checkin was not reverted
469+ with scopes_disabled ():
470+ assert Checkin .objects .count () == initial_checkin_count
471+ assert other_position .checkins .exists ()
472+ # Verify no log entries were created for the unauthorized operation
473+ assert LogEntry .objects .count () == initial_log_count
474+ assert not LogEntry .objects .filter (
475+ action_type = 'pretix.event.checkin.reverted' ,
476+ object_id = other_position .order .pk
477+ ).exists ()
478+
479+
356480@pytest .fixture
357481def checkin_list_with_addon_env ():
358482 # permission
@@ -574,4 +698,4 @@ def test_delete(self):
574698 assert doc .select ('.alert-success' )
575699 self .assertNotIn ('VAT' , doc .select ('#page-wrapper' )[0 ].text )
576700 with scopes_disabled ():
577- assert not self .event1 .checkin_lists .exists ()
701+ assert not self .event1 .checkin_lists .exists ()
0 commit comments