1
1
import io
2
+ from typing import IO , Any , Callable , Dict , List , Optional , Set , Tuple , cast
2
3
from unittest import TestCase
3
4
from unittest .mock import ANY , MagicMock , create_autospec , patch
4
5
6
+ from zulip import Client
5
7
from zulip_bots .lib import (
8
+ BotHandler ,
6
9
ExternalBotHandler ,
7
10
StateHandler ,
8
11
extract_query_without_mention ,
12
15
13
16
14
17
class 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 ()
17
20
18
- def get_profile (self ):
21
+ def get_profile (self ) -> Dict [ str , Any ] :
19
22
return dict (
20
23
user_id = "alice" ,
21
24
full_name = "Alice" ,
22
25
23
26
id = 42 ,
24
27
)
25
28
26
- def update_storage (self , payload ) :
29
+ def update_storage (self , payload : Dict [ str , Any ]) -> Dict [ str , Any ] :
27
30
new_data = payload ["storage" ]
28
31
self .storage .update (new_data )
29
32
30
33
return dict (
31
34
result = "success" ,
32
35
)
33
36
34
- def get_storage (self , request ) :
37
+ def get_storage (self , request : Dict [ str , Any ]) -> Dict [ str , Any ] :
35
38
return dict (
36
39
result = "success" ,
37
40
storage = self .storage ,
38
41
)
39
42
40
- def send_message (self , message ) :
43
+ def send_message (self , message : Dict [ str , Any ]) -> Dict [ str , Any ] :
41
44
return dict (
42
45
result = "success" ,
43
46
)
44
47
45
- def upload_file (self , file ) :
48
+ def upload_file (self , file : IO [ Any ]) -> None :
46
49
pass
47
50
48
51
49
52
class FakeBotHandler :
50
- def usage (self ):
53
+ def usage (self ) -> str :
51
54
return """
52
55
This is a fake bot handler that is used
53
56
to spec BotHandler mocks.
54
57
"""
55
58
56
- def handle_message (self , message , bot_handler ) :
59
+ def handle_message (self , message : Dict [ str , str ], bot_handler : BotHandler ) -> None :
57
60
pass
58
61
59
62
60
63
class LibTest (TestCase ):
61
- def test_basics (self ):
62
- client = FakeClient ()
64
+ def test_basics (self ) -> None :
65
+ client = cast ( Client , FakeClient () )
63
66
64
67
handler = ExternalBotHandler (
65
68
client = client , root_dir = None , bot_details = None , bot_config_file = None
66
69
)
67
70
68
- message = None
71
+ message : Dict [ str , Any ] = {}
69
72
handler .send_message (message )
70
73
71
- def test_state_handler (self ):
72
- client = FakeClient ()
74
+ def test_state_handler (self ) -> None :
75
+ client = cast ( Client , FakeClient () )
73
76
74
77
state_handler = StateHandler (client )
75
78
state_handler .put ("key" , [1 , 2 , 3 ])
@@ -81,7 +84,7 @@ def test_state_handler(self):
81
84
val = state_handler .get ("key" )
82
85
self .assertEqual (val , [1 , 2 , 3 ])
83
86
84
- def test_state_handler_by_mock (self ):
87
+ def test_state_handler_by_mock (self ) -> None :
85
88
client = MagicMock ()
86
89
87
90
state_handler = StateHandler (client )
@@ -109,8 +112,8 @@ def test_state_handler_by_mock(self):
109
112
client .get_storage .assert_not_called ()
110
113
self .assertEqual (val , [5 ])
111
114
112
- def test_react (self ):
113
- client = FakeClient ()
115
+ def test_react (self ) -> None :
116
+ client = cast ( Client , FakeClient () )
114
117
handler = ExternalBotHandler (
115
118
client = client , root_dir = None , bot_details = None , bot_config_file = None
116
119
)
@@ -121,18 +124,18 @@ def test_react(self):
121
124
"emoji_name" : "wave" ,
122
125
"reaction_type" : "unicode_emoji" ,
123
126
}
124
- client .add_reaction = MagicMock ()
127
+ client .add_reaction = MagicMock () # type: ignore[method-assign]
125
128
handler .react (message , emoji_name )
126
129
client .add_reaction .assert_called_once_with (dict (expected ))
127
130
128
- def test_send_reply (self ):
129
- client = FakeClient ()
131
+ def test_send_reply (self ) -> None :
132
+ client = cast ( Client , FakeClient () )
130
133
profile = client .get_profile ()
131
134
handler = ExternalBotHandler (
132
135
client = client , root_dir = None , bot_details = None , bot_config_file = None
133
136
)
134
137
to = {"id" : 43 }
135
- expected = [
138
+ expected : List [ Tuple [ Dict [ str , Any ], Dict [ str , Any ], Optional [ str ]]] = [
136
139
(
137
140
{"type" : "private" , "display_recipient" : [to ]},
138
141
{"type" : "private" , "to" : [to ["id" ]]},
@@ -151,27 +154,32 @@ def test_send_reply(self):
151
154
]
152
155
response_text = "Response"
153
156
for test in expected :
154
- client .send_message = MagicMock ()
157
+ client .send_message = MagicMock () # type: ignore[method-assign]
155
158
handler .send_reply (test [0 ], response_text , test [2 ])
156
159
client .send_message .assert_called_once_with (
157
160
dict (test [1 ], content = response_text , widget_content = test [2 ])
158
161
)
159
162
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 () )
162
165
client .get_profile ()
163
166
ExternalBotHandler (client = client , root_dir = None , bot_details = None , bot_config_file = None )
164
167
165
- def test_run_message_handler_for_bot (self ):
168
+ def test_run_message_handler_for_bot (self ) -> None :
166
169
with patch ("zulip_bots.lib.Client" , new = FakeClient ) as fake_client :
167
170
mock_lib_module = MagicMock ()
168
171
# __file__ is not mocked by MagicMock(), so we assign a mock value manually.
169
172
mock_lib_module .__file__ = "foo"
170
173
mock_bot_handler = create_autospec (FakeBotHandler )
171
174
mock_lib_module .handler_class .return_value = mock_bot_handler
172
175
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 :
175
183
event = {"message" : message , "flags" : flags , "type" : "message" }
176
184
callback (event )
177
185
@@ -188,8 +196,8 @@ def test_message(message, flags):
188
196
message = expected_message , bot_handler = ANY
189
197
)
190
198
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 )
193
201
)
194
202
run_message_handler_for_bot (
195
203
lib_module = mock_lib_module ,
@@ -200,25 +208,25 @@ def test_message(message, flags):
200
208
bot_source = "bot code location" ,
201
209
)
202
210
203
- def test_upload_file (self ):
211
+ def test_upload_file (self ) -> None :
204
212
client , handler = self ._create_client_and_handler_for_file_upload ()
205
213
file = io .BytesIO (b"binary" )
206
214
207
215
handler .upload_file (file )
208
216
209
- client .upload_file .assert_called_once_with (file )
217
+ client .upload_file .assert_called_once_with (file ) # type: ignore[attr-defined]
210
218
211
- def test_upload_file_from_path (self ):
219
+ def test_upload_file_from_path (self ) -> None :
212
220
client , handler = self ._create_client_and_handler_for_file_upload ()
213
221
file = io .BytesIO (b"binary" )
214
222
215
223
with patch ("builtins.open" , return_value = file ):
216
224
handler .upload_file_from_path ("file.txt" )
217
225
218
- client .upload_file .assert_called_once_with (file )
226
+ client .upload_file .assert_called_once_with (file ) # type: ignore[attr-defined]
219
227
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 () )
222
230
handler = ExternalBotHandler (
223
231
client = client , root_dir = None , bot_details = None , bot_config_file = None
224
232
)
@@ -231,12 +239,12 @@ def test_extract_query_without_mention(self):
231
239
message = {"content" : "Not at start @**Alice|alice** Hello World" }
232
240
self .assertEqual (extract_query_without_mention (message , handler ), None )
233
241
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 () )
236
244
handler = ExternalBotHandler (
237
245
client = client , root_dir = None , bot_details = None , bot_config_file = None
238
246
)
239
- message = {}
247
+ message : Dict [ str , Any ] = {}
240
248
message ["display_recipient" ] = "some stream"
241
249
message ["type" ] = "stream"
242
250
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):
249
257
message [
"display_recipient" ]
= [{
"email" :
"[email protected] " }, {
"email" :
"[email protected] " }]
250
258
self .assertFalse (is_private_message_but_not_group_pm (message , handler ))
251
259
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]
255
263
256
264
handler = ExternalBotHandler (
257
265
client = client , root_dir = None , bot_details = None , bot_config_file = None
0 commit comments