bot lib tests: Move tests of bot library to tests/.
This makes it clearer which files are for writing bot tests vs tests of the infrastructure.
This commit is contained in:
parent
9e6b4edee6
commit
4c8d86c1d9
4 changed files with 4 additions and 4 deletions
168
zulip_bots/zulip_bots/tests/test_lib.py
Normal file
168
zulip_bots/zulip_bots/tests/test_lib.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch, ANY, create_autospec
|
||||
from zulip_bots.lib import (
|
||||
ExternalBotHandler,
|
||||
StateHandler,
|
||||
run_message_handler_for_bot,
|
||||
)
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.storage = dict()
|
||||
|
||||
def get_profile(self):
|
||||
return dict(
|
||||
user_id='alice',
|
||||
full_name='Alice',
|
||||
email='alice@example.com',
|
||||
)
|
||||
|
||||
def update_storage(self, payload):
|
||||
new_data = payload['storage']
|
||||
self.storage.update(new_data)
|
||||
|
||||
return dict(
|
||||
result='success',
|
||||
)
|
||||
|
||||
def get_storage(self):
|
||||
return dict(
|
||||
result='success',
|
||||
storage=self.storage,
|
||||
)
|
||||
|
||||
def send_message(self, message):
|
||||
pass
|
||||
|
||||
class FakeBotHandler:
|
||||
def usage(self):
|
||||
return '''
|
||||
This is a fake bot handler that is used
|
||||
to spec BotHandler mocks.
|
||||
'''
|
||||
|
||||
def handle_message(self, message, bot_handler):
|
||||
pass
|
||||
|
||||
class LibTest(TestCase):
|
||||
def test_basics(self):
|
||||
client = FakeClient()
|
||||
|
||||
handler = ExternalBotHandler(
|
||||
client=client,
|
||||
root_dir=None,
|
||||
bot_details=None,
|
||||
bot_config_file=None
|
||||
)
|
||||
|
||||
message = None
|
||||
handler.send_message(message)
|
||||
|
||||
def test_state_handler(self):
|
||||
client = FakeClient()
|
||||
|
||||
state_handler = StateHandler(client)
|
||||
state_handler.put('key', [1, 2, 3])
|
||||
val = state_handler.get('key')
|
||||
self.assertEqual(val, [1, 2, 3])
|
||||
|
||||
# force us to get non-cached values
|
||||
state_handler = StateHandler(client)
|
||||
val = state_handler.get('key')
|
||||
self.assertEqual(val, [1, 2, 3])
|
||||
|
||||
def test_state_handler(self):
|
||||
client = MagicMock()
|
||||
|
||||
state_handler = StateHandler(client)
|
||||
client.get_storage.assert_not_called()
|
||||
|
||||
client.update_storage = MagicMock(return_value=dict(result='success'))
|
||||
state_handler.put('key', [1, 2, 3])
|
||||
client.update_storage.assert_called_with(dict(storage=dict(key='[1, 2, 3]')))
|
||||
|
||||
val = state_handler.get('key')
|
||||
client.get_storage.assert_not_called()
|
||||
self.assertEqual(val, [1, 2, 3])
|
||||
|
||||
# force us to get non-cached values
|
||||
client.get_storage = MagicMock(return_value=dict(
|
||||
result='success',
|
||||
storage=dict(non_cached_key='[5]')))
|
||||
val = state_handler.get('non_cached_key')
|
||||
client.get_storage.assert_called_with(keys=('non_cached_key',))
|
||||
self.assertEqual(val, [5])
|
||||
|
||||
# value must already be cached
|
||||
client.get_storage = MagicMock()
|
||||
val = state_handler.get('non_cached_key')
|
||||
client.get_storage.assert_not_called()
|
||||
self.assertEqual(val, [5])
|
||||
|
||||
def test_send_reply(self):
|
||||
client = FakeClient()
|
||||
profile = client.get_profile()
|
||||
handler = ExternalBotHandler(
|
||||
client=client,
|
||||
root_dir=None,
|
||||
bot_details=None,
|
||||
bot_config_file=None
|
||||
)
|
||||
to = {'email': 'Some@User'}
|
||||
expected = [({'type': 'private', 'display_recipient': [to]},
|
||||
{'type': 'private', 'to': [to['email']]}),
|
||||
({'type': 'private', 'display_recipient': [to, profile]},
|
||||
{'type': 'private', 'to': [to['email']]}),
|
||||
({'type': 'stream', 'display_recipient': 'Stream name', 'subject': 'Topic'},
|
||||
{'type': 'stream', 'to': 'Stream name', 'subject': 'Topic'})]
|
||||
response_text = "Response"
|
||||
for test in expected:
|
||||
client.send_message = MagicMock()
|
||||
handler.send_reply(test[0], response_text)
|
||||
client.send_message.assert_called_once_with(dict(test[1], content=response_text))
|
||||
|
||||
def test_content_and_full_content(self):
|
||||
client = FakeClient()
|
||||
profile = client.get_profile()
|
||||
handler = ExternalBotHandler(
|
||||
client=client,
|
||||
root_dir=None,
|
||||
bot_details=None,
|
||||
bot_config_file=None
|
||||
)
|
||||
to = {'email': 'Some@User'}
|
||||
|
||||
def test_run_message_handler_for_bot(self):
|
||||
with patch('zulip_bots.lib.Client', new=FakeClient) as fake_client:
|
||||
mock_lib_module = MagicMock()
|
||||
# __file__ is not mocked by MagicMock(), so we assign a mock value manually.
|
||||
mock_lib_module.__file__ = "foo"
|
||||
mock_bot_handler = create_autospec(FakeBotHandler)
|
||||
mock_lib_module.handler_class.return_value = mock_bot_handler
|
||||
|
||||
def call_on_each_event_mock(self, callback, event_types=None, narrow=None):
|
||||
def test_message(message, flags):
|
||||
event = {'message': message,
|
||||
'flags': flags,
|
||||
'type': 'message'}
|
||||
callback(event)
|
||||
|
||||
# In the following test, expected_message is the dict that we expect
|
||||
# to be passed to the bot's handle_message function.
|
||||
original_message = {'content': '@**Alice** bar',
|
||||
'type': 'stream'}
|
||||
expected_message = {'type': 'stream',
|
||||
'content': 'bar',
|
||||
'full_content': '@**Alice** bar'}
|
||||
test_message(original_message, {'mentioned'})
|
||||
mock_bot_handler.handle_message.assert_called_with(
|
||||
message=expected_message,
|
||||
bot_handler=ANY)
|
||||
|
||||
fake_client.call_on_each_event = call_on_each_event_mock.__get__(
|
||||
fake_client, fake_client.__class__)
|
||||
run_message_handler_for_bot(lib_module=mock_lib_module,
|
||||
quiet=True,
|
||||
config_file=None,
|
||||
bot_config_file=None,
|
||||
bot_name='testbot')
|
89
zulip_bots/zulip_bots/tests/test_run.py
Normal file
89
zulip_bots/zulip_bots/tests/test_run.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import zulip_bots.run
|
||||
from zulip_bots.lib import extract_query_without_mention
|
||||
import unittest
|
||||
from typing import Optional
|
||||
from unittest import TestCase
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestDefaultArguments(TestCase):
|
||||
|
||||
our_dir = os.path.dirname(__file__)
|
||||
path_to_bot = os.path.abspath(os.path.join(our_dir, '../bots/giphy/giphy.py'))
|
||||
|
||||
@patch('sys.argv', ['zulip-run-bot', 'giphy', '--config-file', '/foo/bar/baz.conf'])
|
||||
@patch('zulip_bots.run.run_message_handler_for_bot')
|
||||
def test_argument_parsing_with_bot_name(self, mock_run_message_handler_for_bot: mock.Mock) -> None:
|
||||
with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'):
|
||||
zulip_bots.run.main()
|
||||
|
||||
mock_run_message_handler_for_bot.assert_called_with(bot_name='giphy',
|
||||
config_file='/foo/bar/baz.conf',
|
||||
bot_config_file=None,
|
||||
lib_module=mock.ANY,
|
||||
quiet=False)
|
||||
|
||||
@patch('sys.argv', ['zulip-run-bot', path_to_bot, '--config-file', '/foo/bar/baz.conf'])
|
||||
@patch('zulip_bots.run.run_message_handler_for_bot')
|
||||
def test_argument_parsing_with_bot_path(self, mock_run_message_handler_for_bot: mock.Mock) -> None:
|
||||
with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'):
|
||||
zulip_bots.run.main()
|
||||
|
||||
mock_run_message_handler_for_bot.assert_called_with(
|
||||
bot_name='giphy',
|
||||
config_file='/foo/bar/baz.conf',
|
||||
bot_config_file=None,
|
||||
lib_module=mock.ANY,
|
||||
quiet=False)
|
||||
|
||||
def test_adding_bot_parent_dir_to_sys_path_when_bot_name_specified(self) -> None:
|
||||
bot_name = 'any_bot_name'
|
||||
expected_bot_dir_path = os.path.join(
|
||||
os.path.dirname(zulip_bots.run.__file__),
|
||||
'bots',
|
||||
bot_name
|
||||
)
|
||||
self._test_adding_bot_parent_dir_to_sys_path(bot_qualifier=bot_name, bot_dir_path=expected_bot_dir_path)
|
||||
|
||||
@patch('os.path.isfile', return_value=True)
|
||||
def test_adding_bot_parent_dir_to_sys_path_when_bot_path_specified(self, mock_os_path_isfile: mock.Mock) -> None:
|
||||
bot_path = '/path/to/bot'
|
||||
expected_bot_dir_path = '/path/to'
|
||||
self._test_adding_bot_parent_dir_to_sys_path(bot_qualifier=bot_path, bot_dir_path=expected_bot_dir_path)
|
||||
|
||||
def _test_adding_bot_parent_dir_to_sys_path(self, bot_qualifier, bot_dir_path):
|
||||
# type: (str, str) -> None
|
||||
with patch('sys.argv', ['zulip-run-bot', bot_qualifier, '--config-file', '/path/to/config']):
|
||||
with patch('zulip_bots.run.import_module_from_source', return_value=mock.Mock()):
|
||||
with patch('zulip_bots.run.run_message_handler_for_bot'):
|
||||
with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'):
|
||||
zulip_bots.run.main()
|
||||
|
||||
self.assertIn(bot_dir_path, sys.path)
|
||||
|
||||
|
||||
class TestBotLib(TestCase):
|
||||
def test_extract_query_without_mention(self) -> None:
|
||||
|
||||
def test_message(name: str, message: str, expected_return: Optional[str]) -> None:
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.full_name = name
|
||||
mock_message = {'content': message}
|
||||
self.assertEqual(expected_return, extract_query_without_mention(mock_message, mock_client))
|
||||
test_message("xkcd", "@**xkcd**foo", "foo")
|
||||
test_message("xkcd", "@**xkcd** foo", "foo")
|
||||
test_message("xkcd", "@**xkcd** foo bar baz", "foo bar baz")
|
||||
test_message("xkcd", "@**xkcd** foo bar baz", "foo bar baz")
|
||||
test_message("xkcd", "@**xkcd** 123_) (/&%) +}}}l", "123_) (/&%) +}}}l")
|
||||
test_message("brokenmention", "@**brokenmention* foo", None)
|
||||
test_message("nomention", "foo", None)
|
||||
test_message("Max Mustermann", "@**Max Mustermann** foo", "foo")
|
||||
test_message("Max (Mustermann)#(*$&12]\]", "@**Max (Mustermann)#(*$&12]\]** foo", "foo")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Add table
Add a link
Reference in a new issue