55from types import SimpleNamespace
66from unittest .mock import AsyncMock , patch
77
8- from const import FLAG , TEST_PREFIX , T_FINISHED
8+ from const import FLAG , TEST_PREFIX , T_FINISHED , VARIANTEND
99from glicko2 .glicko2 import new_default_perf_map
1010from newid import id8
1111from pychess_global_app_state_utils import get_app_state
@@ -128,9 +128,31 @@ def _record_finished_round(
128128 pairing : list [tuple [str , str ]],
129129 ) -> None :
130130 result_cycle = ("1-0" , "0-1" , "1/2-1/2" , "1-0" , "0-1" )
131+ result_specs = [
132+ (result_cycle [(round_number + board_index ) % len (result_cycle )], None )
133+ for board_index in range (len (pairing ))
134+ ]
135+ self ._record_finished_round_with_specs (
136+ tournament ,
137+ round_number = round_number ,
138+ pairing = pairing ,
139+ result_specs = result_specs ,
140+ )
141+
142+ def _record_finished_round_with_specs (
143+ self ,
144+ tournament : SwissTestTournament ,
145+ * ,
146+ round_number : int ,
147+ pairing : list [tuple [str , str ]],
148+ result_specs : list [tuple [str , int | None ]],
149+ ) -> None :
150+ self .assertEqual (len (pairing ), len (result_specs ))
131151 base_time = datetime (2026 , 3 , 10 , 12 , 0 , tzinfo = timezone .utc )
132152
133- for board_index , (white_name , black_name ) in enumerate (pairing ):
153+ for board_index , ((white_name , black_name ), (result , status )) in enumerate (
154+ zip (pairing , result_specs , strict = True )
155+ ):
134156 white_user = tournament .get_player_by_name (white_name )
135157 black_user = tournament .get_player_by_name (black_name )
136158 self .assertIsNotNone (white_user )
@@ -151,15 +173,19 @@ def _record_finished_round(
151173 str (white_data .rating ),
152174 black_name ,
153175 str (black_data .rating ),
154- result_cycle [( round_number + board_index ) % len ( result_cycle )] ,
176+ result ,
155177 base_time + timedelta (minutes = round_number * 10 + board_index ),
156178 False ,
157179 False ,
180+ status = status ,
158181 round_no = round_number ,
159182 )
160183
161184 tournament .update_players (game )
162- wpoint , bpoint , _wperf , _bperf = tournament .points_perfs (game )
185+ if tournament .variant == "janggi" :
186+ wpoint , bpoint , _wperf , _bperf = tournament .points_perfs_janggi (game )
187+ else :
188+ wpoint , bpoint , _wperf , _bperf = tournament .points_perfs (game )
163189
164190 white_data .points .append (wpoint )
165191 black_data .points .append (bpoint )
@@ -380,6 +406,40 @@ async def test_create_pairing_async_uses_bbp_backend_output(self):
380406 self .assertEqual (pairing , fake_pairing )
381407 create_bbp_pairing .assert_awaited_once_with (self .tournament , waiting , 0 )
382408
409+ async def test_create_pairing_defaults_to_py4swiss_when_backend_env_is_unset (self ):
410+ app_state = get_app_state (self .app )
411+ tid = id8 ()
412+ self .tournament = SwissTestTournament (
413+ app_state , tid , before_start = 1 , rounds = 3 , with_clock = False
414+ )
415+ app_state .tournaments [tid ] = self .tournament
416+ await self .tournament .join_players (4 )
417+
418+ waiting = list (self .tournament .waiting_players ())
419+ users_by_id = {index : user for index , user in enumerate (waiting , start = 1 )}
420+ fake_state = SimpleNamespace (
421+ trf = object (),
422+ waiting_ids = set (users_by_id ),
423+ users_by_id = users_by_id ,
424+ )
425+
426+ class _Engine :
427+ @staticmethod
428+ def generate_pairings (_trf ):
429+ return [
430+ SimpleNamespace (white = 1 , black = 2 ),
431+ SimpleNamespace (white = 3 , black = 4 ),
432+ ]
433+
434+ with (
435+ patch .dict ("os.environ" , {"SWISS_PAIRING_BACKEND" : "" }),
436+ patch ("tournament.swiss._build_dutch_pairing_state" , return_value = fake_state ),
437+ patch ("tournament.swiss.DutchEngine" , _Engine ),
438+ ):
439+ pairing = self .tournament .create_pairing (waiting )
440+
441+ self .assertEqual (pairing , [(waiting [0 ], waiting [1 ]), (waiting [2 ], waiting [3 ])])
442+
383443 async def test_build_bbp_trf_export_includes_point_system_and_absent_players (self ):
384444 app_state = get_app_state (self .app )
385445 tid = id8 ()
@@ -552,6 +612,73 @@ async def test_real_swisspairing_backend_matches_py4swiss_after_late_join(self):
552612 {player_name for pair in round_2_pairing for player_name in pair },
553613 )
554614
615+ @unittest .skipUnless (
616+ _has_swisspairing_runtime (),
617+ "swisspairing import unavailable for live backend parity test" ,
618+ )
619+ async def test_real_swisspairing_backend_matches_py4swiss_for_janggi_scores (self ):
620+ app_state = get_app_state (self .app )
621+ tid = id8 ()
622+ self .tournament = SwissTestTournament (
623+ app_state ,
624+ tid ,
625+ before_start = 1 ,
626+ rounds = 4 ,
627+ with_clock = False ,
628+ variant = "janggi" ,
629+ )
630+ app_state .tournaments [tid ] = self .tournament
631+ await self .tournament .join_players (5 )
632+ await self .tournament .start (datetime .now (timezone .utc ))
633+
634+ self .tournament .current_round = 1
635+ round_1_pairing , round_1_byes = self ._assert_backend_outcome_match (
636+ self .tournament ,
637+ round_number = 1 ,
638+ keep_swisspairing_byes = True ,
639+ )
640+ self .assertEqual (len (round_1_byes ), 1 )
641+
642+ await self .tournament .persist_byes ()
643+ self ._record_finished_round_with_specs (
644+ self .tournament ,
645+ round_number = 1 ,
646+ pairing = round_1_pairing ,
647+ result_specs = [
648+ ("1-0" , VARIANTEND ),
649+ ("0-1" , FLAG ),
650+ ],
651+ )
652+
653+ late = User (
654+ app_state ,
655+ username = "late_join_janggi_backend_parity" ,
656+ perfs = make_test_perfs (),
657+ )
658+ app_state .users [late .username ] = late
659+ late .tournament_sockets [tid ] = set ((None ,))
660+
661+ join_error = await self .tournament .join (late )
662+ self .assertIsNone (join_error )
663+
664+ late_data = self .tournament .player_data_by_name (late .username )
665+ self .assertIsNotNone (late_data )
666+ assert late_data is not None
667+ self .assertEqual (late_data .joined_round , 2 )
668+ self .assertEqual ([getattr (game , "token" , "" ) for game in late_data .games ], ["H" ])
669+ self .assertEqual ([point [0 ] for point in late_data .points ], [2 ])
670+
671+ self .tournament .current_round = 2
672+ round_2_pairing , round_2_byes = self ._assert_backend_outcome_match (
673+ self .tournament ,
674+ round_number = 2 ,
675+ )
676+ self .assertEqual (round_2_byes , [])
677+ self .assertIn (
678+ late .username ,
679+ {player_name for pair in round_2_pairing for player_name in pair },
680+ )
681+
555682 @unittest .skipUnless (
556683 _has_swisspairing_runtime (),
557684 "swisspairing import unavailable for live backend parity test" ,
0 commit comments