11import io
2+ from typing import IO , Any , Callable , Dict , List , Optional , Set , Tuple , cast
23from unittest import TestCase
34from unittest .mock import ANY , MagicMock , create_autospec , patch
45
6+ from zulip import Client
57from zulip_bots .lib import (
8+ BotHandler ,
69 ExternalBotHandler ,
710 StateHandler ,
811 extract_query_without_mention ,
1215
1316
1417class FakeClient :
15- def __init__ (self , * args , ** kwargs ) :
16- self .storage = dict ()
18+ def __init__ (self , * args : object , ** kwargs : object ) -> None :
19+ self .storage : Dict [ str , str ] = dict ()
1720
18- def get_profile (self ):
21+ def get_profile (self ) -> Dict [ str , Any ] :
1922 return dict (
2023 user_id = "alice" ,
2124 full_name = "Alice" ,
2225 email = "alice@example.com" ,
2326 id = 42 ,
2427 )
2528
26- def update_storage (self , payload ) :
29+ def update_storage (self , payload : Dict [ str , Any ]) -> Dict [ str , Any ] :
2730 new_data = payload ["storage" ]
2831 self .storage .update (new_data )
2932
3033 return dict (
3134 result = "success" ,
3235 )
3336
34- def get_storage (self , request ) :
37+ def get_storage (self , request : Dict [ str , Any ]) -> Dict [ str , Any ] :
3538 return dict (
3639 result = "success" ,
3740 storage = self .storage ,
3841 )
3942
40- def send_message (self , message ) :
43+ def send_message (self , message : Dict [ str , Any ]) -> Dict [ str , Any ] :
4144 return dict (
4245 result = "success" ,
4346 )
4447
45- def upload_file (self , file ) :
48+ def upload_file (self , file : IO [ Any ]) -> None :
4649 pass
4750
4851
4952class FakeBotHandler :
50- def usage (self ):
53+ def usage (self ) -> str :
5154 return """
5255 This is a fake bot handler that is used
5356 to spec BotHandler mocks.
5457 """
5558
56- def handle_message (self , message , bot_handler ) :
59+ def handle_message (self , message : Dict [ str , str ], bot_handler : BotHandler ) -> None :
5760 pass
5861
5962
6063class LibTest (TestCase ):
61- def test_basics (self ):
62- client = FakeClient ()
64+ def test_basics (self ) -> None :
65+ client = cast ( Client , FakeClient () )
6366
6467 handler = ExternalBotHandler (
6568 client = client , root_dir = None , bot_details = None , bot_config_file = None
6669 )
6770
68- message = None
71+ message : Dict [ str , Any ] = {}
6972 handler .send_message (message )
7073
71- def test_state_handler (self ):
72- client = FakeClient ()
74+ def test_state_handler (self ) -> None :
75+ client = cast ( Client , FakeClient () )
7376
7477 state_handler = StateHandler (client )
7578 state_handler .put ("key" , [1 , 2 , 3 ])
@@ -81,7 +84,7 @@ def test_state_handler(self):
8184 val = state_handler .get ("key" )
8285 self .assertEqual (val , [1 , 2 , 3 ])
8386
84- def test_state_handler_by_mock (self ):
87+ def test_state_handler_by_mock (self ) -> None :
8588 client = MagicMock ()
8689
8790 state_handler = StateHandler (client )
@@ -109,8 +112,8 @@ def test_state_handler_by_mock(self):
109112 client .get_storage .assert_not_called ()
110113 self .assertEqual (val , [5 ])
111114
112- def test_react (self ):
113- client = FakeClient ()
115+ def test_react (self ) -> None :
116+ client = cast ( Client , FakeClient () )
114117 handler = ExternalBotHandler (
115118 client = client , root_dir = None , bot_details = None , bot_config_file = None
116119 )
@@ -121,18 +124,18 @@ def test_react(self):
121124 "emoji_name" : "wave" ,
122125 "reaction_type" : "unicode_emoji" ,
123126 }
124- client .add_reaction = MagicMock ()
127+ client .add_reaction = MagicMock () # type: ignore[method-assign]
125128 handler .react (message , emoji_name )
126129 client .add_reaction .assert_called_once_with (dict (expected ))
127130
128- def test_send_reply (self ):
129- client = FakeClient ()
131+ def test_send_reply (self ) -> None :
132+ client = cast ( Client , FakeClient () )
130133 profile = client .get_profile ()
131134 handler = ExternalBotHandler (
132135 client = client , root_dir = None , bot_details = None , bot_config_file = None
133136 )
134137 to = {"id" : 43 }
135- expected = [
138+ expected : List [ Tuple [ Dict [ str , Any ], Dict [ str , Any ], Optional [ str ]]] = [
136139 (
137140 {"type" : "private" , "display_recipient" : [to ]},
138141 {"type" : "private" , "to" : [to ["id" ]]},
@@ -151,27 +154,32 @@ def test_send_reply(self):
151154 ]
152155 response_text = "Response"
153156 for test in expected :
154- client .send_message = MagicMock ()
157+ client .send_message = MagicMock () # type: ignore[method-assign]
155158 handler .send_reply (test [0 ], response_text , test [2 ])
156159 client .send_message .assert_called_once_with (
157160 dict (test [1 ], content = response_text , widget_content = test [2 ])
158161 )
159162
160- def test_content_and_full_content (self ):
161- client = FakeClient ()
163+ def test_content_and_full_content (self ) -> None :
164+ client = cast ( Client , FakeClient () )
162165 client .get_profile ()
163166 ExternalBotHandler (client = client , root_dir = None , bot_details = None , bot_config_file = None )
164167
165- def test_run_message_handler_for_bot (self ):
168+ def test_run_message_handler_for_bot (self ) -> None :
166169 with patch ("zulip_bots.lib.Client" , new = FakeClient ) as fake_client :
167170 mock_lib_module = MagicMock ()
168171 # __file__ is not mocked by MagicMock(), so we assign a mock value manually.
169172 mock_lib_module .__file__ = "foo"
170173 mock_bot_handler = create_autospec (FakeBotHandler )
171174 mock_lib_module .handler_class .return_value = mock_bot_handler
172175
173- def call_on_each_event_mock (self , callback , event_types = None , narrow = None ):
174- def test_message (message , flags ):
176+ def call_on_each_event_mock (
177+ self : FakeClient ,
178+ callback : Callable [[Dict [str , Any ]], None ],
179+ event_types : Optional [List [str ]] = None ,
180+ narrow : Optional [List [List [str ]]] = None ,
181+ ) -> None :
182+ def test_message (message : Dict [str , Any ], flags : Set [str ]) -> None :
175183 event = {"message" : message , "flags" : flags , "type" : "message" }
176184 callback (event )
177185
@@ -188,8 +196,8 @@ def test_message(message, flags):
188196 message = expected_message , bot_handler = ANY
189197 )
190198
191- fake_client .call_on_each_event = call_on_each_event_mock .__get__ (
192- fake_client , fake_client . __class__
199+ fake_client .call_on_each_event = call_on_each_event_mock .__get__ ( # type: ignore[attr-defined]
200+ fake_client , type ( fake_client )
193201 )
194202 run_message_handler_for_bot (
195203 lib_module = mock_lib_module ,
@@ -200,25 +208,25 @@ def test_message(message, flags):
200208 bot_source = "bot code location" ,
201209 )
202210
203- def test_upload_file (self ):
211+ def test_upload_file (self ) -> None :
204212 client , handler = self ._create_client_and_handler_for_file_upload ()
205213 file = io .BytesIO (b"binary" )
206214
207215 handler .upload_file (file )
208216
209- client .upload_file .assert_called_once_with (file )
217+ client .upload_file .assert_called_once_with (file ) # type: ignore[attr-defined]
210218
211- def test_upload_file_from_path (self ):
219+ def test_upload_file_from_path (self ) -> None :
212220 client , handler = self ._create_client_and_handler_for_file_upload ()
213221 file = io .BytesIO (b"binary" )
214222
215223 with patch ("builtins.open" , return_value = file ):
216224 handler .upload_file_from_path ("file.txt" )
217225
218- client .upload_file .assert_called_once_with (file )
226+ client .upload_file .assert_called_once_with (file ) # type: ignore[attr-defined]
219227
220- def test_extract_query_without_mention (self ):
221- client = FakeClient ()
228+ def test_extract_query_without_mention (self ) -> None :
229+ client = cast ( Client , FakeClient () )
222230 handler = ExternalBotHandler (
223231 client = client , root_dir = None , bot_details = None , bot_config_file = None
224232 )
@@ -231,12 +239,12 @@ def test_extract_query_without_mention(self):
231239 message = {"content" : "Not at start @**Alice|alice** Hello World" }
232240 self .assertEqual (extract_query_without_mention (message , handler ), None )
233241
234- def test_is_private_message_but_not_group_pm (self ):
235- client = FakeClient ()
242+ def test_is_private_message_but_not_group_pm (self ) -> None :
243+ client = cast ( Client , FakeClient () )
236244 handler = ExternalBotHandler (
237245 client = client , root_dir = None , bot_details = None , bot_config_file = None
238246 )
239- message = {}
247+ message : Dict [ str , Any ] = {}
240248 message ["display_recipient" ] = "some stream"
241249 message ["type" ] = "stream"
242250 self .assertFalse (is_private_message_but_not_group_pm (message , handler ))
@@ -249,9 +257,9 @@ def test_is_private_message_but_not_group_pm(self):
249257 message ["display_recipient" ] = [{"email" : "a1@b.com" }, {"email" : "a2@b.com" }]
250258 self .assertFalse (is_private_message_but_not_group_pm (message , handler ))
251259
252- def _create_client_and_handler_for_file_upload (self ):
253- client = FakeClient ()
254- client .upload_file = MagicMock ()
260+ def _create_client_and_handler_for_file_upload (self ) -> Tuple [ Client , ExternalBotHandler ] :
261+ client = cast ( Client , FakeClient () )
262+ client .upload_file = MagicMock () # type: ignore[method-assign]
255263
256264 handler = ExternalBotHandler (
257265 client = client , root_dir = None , bot_details = None , bot_config_file = None
0 commit comments