1- from typing import Any , List , Tuple
1+ from typing import Dict
2+
3+ from typing_extensions import override
24
3- from zulip_bots .game_handler import GameInstance
45from zulip_bots .test_lib import BotTestCase , DefaultTests
56
67from .libraries .constants import EMPTY_BOARD
910class TestMerelsBot (BotTestCase , DefaultTests ):
1011 bot_name = "merels"
1112
12- def test_no_command (self ):
13+ def test_no_command (self ) -> None :
14+ # Sanity: out-of-game message for random content.
1315 message = dict (
1416 content = "magic" ,
type = "stream" ,
sender_email = "[email protected] " ,
sender_full_name = "boo" 1517 )
@@ -18,76 +20,107 @@ def test_no_command(self):
1820 res ["content" ], "You are not in a game at the moment. Type `help` for help."
1921 )
2022
21- # FIXME: Add tests for computer moves
22- # FIXME: Add test lib for game_handler
23+ def test_parse_board_identity_empty_board (self ) -> None :
24+ # parse_board is identity for Merels; verify with the canonical empty board.
25+ bot , _ = self ._get_handlers ()
26+ self .assertEqual (bot .game_message_handler .parse_board (EMPTY_BOARD ), EMPTY_BOARD )
2327
24- # Test for unchanging aspects within the game
25- # Player Color, Start Message, Moving Message
26- def test_static_responses (self ) -> None :
27- model , message_handler = self ._get_game_handlers ()
28- self .assertNotEqual (message_handler .get_player_color (0 ), None )
29- self .assertNotEqual (message_handler .game_start_message (), None )
30- self .assertEqual (
31- message_handler .alert_move_message ("foo" , "moved right" ), "foo :moved right"
32- )
3328
34- # Test to see if the attributes exist
35- def test_has_attributes (self ) -> None :
36- model , message_handler = self ._get_game_handlers ()
37- # Attributes from the Merels Handler
38- self .assertTrue (hasattr (message_handler , "parse_board" ) is not None )
39- self .assertTrue (hasattr (message_handler , "get_player_color" ) is not None )
40- self .assertTrue (hasattr (message_handler , "alert_move_message" ) is not None )
41- self .assertTrue (hasattr (message_handler , "game_start_message" ) is not None )
42- self .assertTrue (hasattr (message_handler , "alert_move_message" ) is not None )
43- # Attributes from the Merels Model
44- self .assertTrue (hasattr (model , "determine_game_over" ) is not None )
45- self .assertTrue (hasattr (model , "contains_winning_move" ) is not None )
46- self .assertTrue (hasattr (model , "make_move" ) is not None )
47-
48- def test_parse_board (self ) -> None :
49- board = EMPTY_BOARD
50- expect_response = EMPTY_BOARD
51- self ._test_parse_board (board , expect_response )
52-
53- def test_add_user_to_cache (self ):
54- self .add_user_to_cache ("Name" )
55-
56- def test_setup_game (self ):
57- self .setup_game ()
58-
59- def add_user_to_cache (self , name : str , bot : Any = None ) -> Any :
60- if bot is None :
61- bot , bot_handler = self ._get_handlers ()
62- message = {
63- "sender_email" : f"{ name } @example.com" ,
64- "sender_full_name" : f"{ name } " ,
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+ """
35+
36+ bot_name = "merels"
37+
38+ @override
39+ def make_request_message (
40+ self ,
content :
str ,
user :
str = "[email protected] " ,
user_name :
str = "foo" 41+ ) -> Dict [str , str ]:
42+ # Provide stream metadata; GameAdapter reads message["type"], topic, etc.
43+ return {
44+ "sender_email" : user ,
45+ "sender_full_name" : user_name ,
46+ "content" : content ,
47+ "type" : "stream" ,
48+ "display_recipient" : "general" ,
49+ "subject" : "merels-test-topic" ,
6550 }
66- bot .add_user_to_cache (message )
67- return bot
68-
69- def setup_game (self ) -> None :
70- bot = self .add_user_to_cache ("foo" )
71- self .add_user_to_cache ("baz" , bot )
72- instance = GameInstance (
73- bot ,
False ,
"test game" ,
"abc123" , [
"[email protected] " ,
"[email protected] " ],
"test" 51+
52+ def test_help_is_merels_help (self ) -> None :
53+ bot , bot_handler = self ._get_handlers ()
54+
55+ bot_handler .reset_transcript ()
56+ bot .handle_message (self .make_request_message ("help" ), bot_handler )
57+
58+ responses = [m for (_method , m ) in bot_handler .transcript ]
59+ self .assertTrue (responses , "No bot response to 'help'" )
60+ help_text = responses [0 ]["content" ]
61+
62+ # Stable fragments; resilient to copy tweaks.
63+ self .assertIn ("Merels Bot Help" , help_text )
64+ self .assertIn ("start game" , help_text )
65+ self .assertIn ("play game" , help_text )
66+ self .assertIn ("quit" , help_text )
67+ self .assertIn ("rules" , help_text )
68+ # Present today; OK if dropped in future wording changes.
69+ self .assertIn ("leaderboard" , help_text )
70+ self .assertIn ("cancel game" , help_text )
71+
72+ def test_start_game_emits_invite (self ) -> None :
73+ bot , bot_handler = self ._get_handlers ()
74+ bot_handler .reset_transcript ()
75+
76+ bot .handle_message (
77+ self .
make_request_message (
"start game" ,
user = "[email protected] " ,
user_name = "foo" ),
78+ bot_handler ,
7479 )
75- bot .instances .update ({"abc123" : instance })
76- instance .start ()
77- return bot
7880
79- def _get_game_handlers (self ) -> Tuple [Any , Any ]:
81+ contents = [m ["content" ] for (_method , m ) in bot_handler .transcript ]
82+ self .assertTrue (contents , "No bot reply recorded for 'start game'" )
83+ first = contents [0 ]
84+ self .assertIn ("wants to play" , first )
85+ self .assertIn ("Merels" , first )
86+ self .assertIn ("join" , first )
87+
88+ def test_join_starts_game_emits_start_message (self ) -> None :
8089 bot , bot_handler = self ._get_handlers ()
81- return bot .model , bot .game_message_handler
82-
83- def _test_parse_board (self , board : str , expected_response : str ) -> None :
84- model , message_handler = self ._get_game_handlers ()
85- response = message_handler .parse_board (board )
86- self .assertEqual (response , expected_response )
87-
88- def _test_determine_game_over (
89- self , board : List [List [int ]], players : List [str ], expected_response : str
90- ) -> None :
91- model , message_handler = self ._get_game_handlers ()
92- response = model .determine_game_over (players )
93- self .assertEqual (response , expected_response )
90+ expected_fragment = bot .game_message_handler .game_start_message ()
91+
92+ bot_handler .reset_transcript ()
93+ bot .handle_message (
94+ self .
make_request_message (
"start game" ,
"[email protected] " ,
"foo" ),
bot_handler 95+ )
96+ bot .
handle_message (
self .
make_request_message (
"join" ,
"[email protected] " ,
"bar" ),
bot_handler )
97+
98+ contents = [m ["content" ] for (_method , m ) in bot_handler .transcript ]
99+ self .assertTrue (
100+ any (expected_fragment in c for c in contents ),
101+ "Merels start message not found after 'join'" ,
102+ )
103+
104+ def test_message_handler_helpers (self ) -> None :
105+ bot , _ = self ._get_handlers ()
106+
107+ # parse_board returns the given board representation.
108+ self .assertEqual (
109+ bot .game_message_handler .parse_board ("sample_board_repr" ), "sample_board_repr"
110+ )
111+
112+ # Token color is one of the two known emoji.
113+ self .assertIn (
114+ bot .game_message_handler .get_player_color (0 ),
115+ (":o_button:" , ":cross_mark_button:" ),
116+ )
117+ self .assertIn (
118+ bot .game_message_handler .get_player_color (1 ),
119+ (":o_button:" , ":cross_mark_button:" ),
120+ )
121+
122+ # Basic move alert format.
123+ self .assertEqual (
124+ bot .game_message_handler .alert_move_message ("foo" , "move 1,1" ),
125+ "foo :move 1,1" ,
126+ )
0 commit comments