Skip to content

Commit 9c44fe5

Browse files
committed
tests: Add type annotations to test_lib.
Signed-off-by: Anders Kaseorg <[email protected]>
1 parent 6aedfe6 commit 9c44fe5

File tree

3 files changed

+55
-47
lines changed

3 files changed

+55
-47
lines changed

tools/run-mypy

-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ exclude = [
2121
# fully annotate their bots.
2222
"zulip_bots/zulip_bots/bots",
2323
"zulip_bots/zulip_bots/bots_unmaintained",
24-
# Excluded out of laziness:
25-
"zulip_bots/zulip_bots/tests/test_lib.py",
2624
]
2725

2826
# These files will be included even if excluded by a rule above.

zulip_bots/zulip_bots/lib.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ class ExternalBotHandler:
213213
def __init__(
214214
self,
215215
client: Client,
216-
root_dir: str,
217-
bot_details: Dict[str, Any],
216+
root_dir: Optional[str],
217+
bot_details: Optional[Dict[str, Any]],
218218
bot_config_file: Optional[str] = None,
219219
bot_config_parser: Optional[configparser.ConfigParser] = None,
220220
) -> None:
@@ -363,6 +363,7 @@ def upload_file(self, file: IO[Any]) -> Dict[str, Any]:
363363
return self._client.upload_file(file)
364364

365365
def open(self, filepath: str) -> IO[str]:
366+
assert self._root_dir is not None
366367
filepath = os.path.normpath(filepath)
367368
abs_filepath = os.path.join(self._root_dir, filepath)
368369
if abs_filepath.startswith(self._root_dir):
@@ -434,8 +435,8 @@ def prepare_message_handler(bot: str, bot_handler: BotHandler, bot_lib_module: A
434435
def run_message_handler_for_bot(
435436
lib_module: Any,
436437
quiet: bool,
437-
config_file: str,
438-
bot_config_file: str,
438+
config_file: Optional[str],
439+
bot_config_file: Optional[str],
439440
bot_name: str,
440441
bot_source: str,
441442
) -> Any:
@@ -459,6 +460,7 @@ def run_message_handler_for_bot(
459460
try:
460461
client = Client(config_file=config_file, client=client_name)
461462
except configparser.Error as e:
463+
assert config_file is not None
462464
display_config_file_errors(str(e), config_file)
463465
sys.exit(1)
464466

zulip_bots/zulip_bots/tests/test_lib.py

+49-41
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import io
2+
from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, cast
23
from unittest import TestCase
34
from unittest.mock import ANY, MagicMock, create_autospec, patch
45

6+
from zulip import Client
57
from zulip_bots.lib import (
8+
BotHandler,
69
ExternalBotHandler,
710
StateHandler,
811
extract_query_without_mention,
@@ -12,64 +15,64 @@
1215

1316

1417
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()
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
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

4952
class 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

6063
class 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": "[email protected]"}, {"email": "[email protected]"}]
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

Comments
 (0)