@@ -11,7 +11,7 @@ class TestMerelsBot(BotTestCase, DefaultTests):
1111 bot_name = "merels"
1212
1313 def test_no_command (self ) -> None :
14- # Sanity: out -of-game message for random content .
14+ # Out -of-game message for arbitrary input .
1515 message = dict (
1616 content = "magic" ,
type = "stream" ,
sender_email = "[email protected] " ,
sender_full_name = "boo" 1717 )
@@ -21,25 +21,60 @@ def test_no_command(self) -> None:
2121 )
2222
2323 def test_parse_board_identity_empty_board (self ) -> None :
24- # parse_board is identity for Merels ; verify with the canonical empty board.
24+ # Merels parse_board is identity; verify with the canonical empty board.
2525 bot , _ = self ._get_handlers ()
2626 self .assertEqual (bot .game_message_handler .parse_board (EMPTY_BOARD ), EMPTY_BOARD )
2727
2828
29- class TestMerelsAdapter (BotTestCase , DefaultTests ):
30- """
31- Adapter-focused tests mirroring connect_four, kept in this file to
32- keep Merels tests cohesive. Assert on stable fragments to avoid brittle
33- exact-string matches.
34- """
29+ class GameAdapterTestLib :
30+ """Small helpers for driving GameAdapter-based bots in tests."""
31+
32+ def send (
33+ self ,
34+ bot ,
35+ bot_handler ,
36+ content : str ,
37+ * ,
38+ 39+ user_name : str = "foo" ,
40+ ) -> None :
41+ bot .handle_message (
42+ self .make_request_message (content , user = user , user_name = user_name ),
43+ bot_handler ,
44+ )
45+
46+ def replies (self , bot_handler ):
47+ # Return the bot message 'content' fields from the transcript.
48+ return [m ["content" ] for (_method , m ) in bot_handler .transcript ]
49+
50+ def send_and_collect (
51+ self ,
52+ bot ,
53+ bot_handler ,
54+ content : str ,
55+ * ,
56+ 57+ user_name : str = "foo" ,
58+ ):
59+ bot_handler .reset_transcript ()
60+ self .send (bot , bot_handler , content , user = user , user_name = user_name )
61+ return self .replies (bot_handler )
62+
63+
64+ # Note: Merels has no vs-computer mode (in merels.py, supports_computer=False).
65+ # If computer mode is added in the future, add adapter-level tests here.
66+
67+
68+ class TestMerelsAdapter (BotTestCase , DefaultTests , GameAdapterTestLib ):
69+ """Adapter-focused tests (mirrors connect_four); use stable fragment assertions."""
3570
3671 bot_name = "merels"
3772
3873 @override
3974 def make_request_message (
4075 self ,
content :
str ,
user :
str = "[email protected] " ,
user_name :
str = "foo" 4176 ) -> Dict [str , str ]:
42- # Provide stream metadata; GameAdapter reads message["type"], topic, etc .
77+ # Provide stream metadata consumed by GameAdapter .
4378 return {
4479 "sender_email" : user ,
4580 "sender_full_name" : user_name ,
@@ -59,7 +94,7 @@ def test_help_is_merels_help(self) -> None:
5994 self .assertTrue (responses , "No bot response to 'help'" )
6095 help_text = responses [0 ]["content" ]
6196
62- # Stable fragments; resilient to copy tweaks .
97+ # Assert on stable fragments to avoid brittle exact matches .
6398 self .assertIn ("Merels Bot Help" , help_text )
6499 self .assertIn ("start game" , help_text )
65100 self .assertIn ("play game" , help_text )
@@ -104,12 +139,12 @@ def test_join_starts_game_emits_start_message(self) -> None:
104139 def test_message_handler_helpers (self ) -> None :
105140 bot , _ = self ._get_handlers ()
106141
107- # parse_board returns the given board representation .
142+ # Identity parse_board .
108143 self .assertEqual (
109144 bot .game_message_handler .parse_board ("sample_board_repr" ), "sample_board_repr"
110145 )
111146
112- # Token color is one of the two known emoji .
147+ # Token color in allowed set .
113148 self .assertIn (
114149 bot .game_message_handler .get_player_color (0 ),
115150 (":o_button:" , ":cross_mark_button:" ),
@@ -124,3 +159,39 @@ def test_message_handler_helpers(self) -> None:
124159 bot .game_message_handler .alert_move_message ("foo" , "move 1,1" ),
125160 "foo :move 1,1" ,
126161 )
162+
163+ def test_move_after_join_invokes_make_move_and_replies (self ) -> None :
164+ """
165+ After start/join, Merels begins in placement (Phase 1). Use 'put v,h'
166+ and assert the adapter emits an acknowledgement. Try both players to
167+ avoid assuming turn order.
168+ """
169+ bot , bot_handler = self ._get_handlers ()
170+
171+ # Start 2P game.
172+ _ = self .send_and_collect (
173+ bot ,
bot_handler ,
"start game" ,
user = "[email protected] " ,
user_name = "foo" 174+ )
175+ _ = self .
send_and_collect (
bot ,
bot_handler ,
"join" ,
user = "[email protected] " ,
user_name = "bar" )
176+
177+ # Stable oracles from the handler's formatter.
178+ ack_foo = bot .game_message_handler .alert_move_message ("foo" , "put 1,1" )
179+ ack_bar = bot .game_message_handler .alert_move_message ("bar" , "put 1,1" )
180+
181+ # Try current player first (unknown), then the other.
182+ contents_foo = self .send_and_collect (
183+ bot ,
bot_handler ,
"put 1,1" ,
user = "[email protected] " ,
user_name = "foo" 184+ )
185+ joined = " " .join (contents_foo )
186+
187+ if (ack_foo not in joined ) and (ack_bar not in joined ) and (":put 1,1" not in joined ):
188+ contents_bar = self .send_and_collect (
189+ bot ,
bot_handler ,
"put 1,1" ,
user = "[email protected] " ,
user_name = "bar" 190+ )
191+ joined += " " + " " .join (contents_bar )
192+
193+ # Assert the adapter produced a placement acknowledgement.
194+ self .assertTrue (
195+ any (h in joined for h in (":put 1,1" , ack_foo , ack_bar )),
196+ f"No placement acknowledgement found in: { joined } " ,
197+ )
0 commit comments