def debug_msg_nb(self, cardinal: CardinalBot, user, channel: str, msg) -> None: """ Method used for debugging purposes """ nick, ident, vhost = user # Send with passed-in object cardinal.sendMsg(channel, f"({nick}) debug 1 {time.time()}") # Send with original member variable object self._cardinal.sendMsg(self._channel, f"({nick}) debug 2 {time.time()}") # Send with updated original member variable object self._updated_cardinal.sendMsg(self._channel, f"({nick}) debug 3 {time.time()}")
def test_subwatch(): cardinal = CardinalBot() cardinal.sendMsg = MagicMock() praw_handler = PrawHandler() praw_handler.stream_new_submissions = MagicMock() submissions = [Submission("url1", "author1", "id1", "title1"), Submission("url2", "author2", "id2", "title2")] praw_handler.stream_new_submissions.return_value = (s for s in submissions) subwatch = SubWatchPlugin.create(cardinal, {}, praw_handler) subwatch.trigger_init(cardinal, "", "##bot-testing", "") calls = [call(channel="##bot-testing", message="New post by author1: title1 - url1"), call(channel="##bot-testing", message="New post by author2: title2 - url2")] cardinal.sendMsg.assert_has_calls(calls)
class TestCardinalBot(object): @patch('cardinal.bot.EventManager', autospec=True) def setup_method(self, method, mock_event_manager): self.cardinal = CardinalBot() mock_event_manager.assert_called_once_with(self.cardinal) self.factory = self.cardinal.factory = Mock(spec=CardinalBotFactory) self.factory.nickname = 'Cardinal' self.factory.username = '******' self.factory.realname = 'Cardinal' self.factory.password = None self.factory.network = 'irc.darkscience.net' self.factory.server_password = None self.factory.server_commands = [] self.factory.channels = [] self.factory.plugins = [] self.factory.blacklist = {} self.factory.booted = datetime.now() self.factory.storage_path = '.' self.event_manager = mock_event_manager.return_value self.plugin_manager = self.cardinal.plugin_manager = \ Mock(spec=plugins.PluginManager) # Some built-in Twisted methods will check self.supported, which is # typically setup during connectionMade(). That method won't get called # in testing. self.cardinal.supported = ServerSupportedFeatures() @staticmethod def get_user(): user = user_info('nick', 'user', 'vhost') return "{}!{}@{}".format(user.nick, user.user, user.vhost), user def test_constructor(self): assert isinstance(self.cardinal.logger, logging.Logger) assert isinstance(self.cardinal.irc_logger, logging.Logger) # Should setup EventManager with IRC events assert self.event_manager.register.mock_calls == [ call("irc.raw", 2), call("irc.invite", 2), call("irc.privmsg", 3), call("irc.notice", 3), call("irc.nick", 2), call("irc.mode", 3), call("irc.topic", 3), call("irc.join", 2), call("irc.part", 3), call("irc.kick", 4), call("irc.quit", 2), ] assert self.cardinal._who_cache == {} assert self.cardinal._who_deferreds == {} def test_factory_pass_thru_properties(self): assert self.cardinal.network == self.factory.network self.cardinal.network = 'irc.freenode.net' assert self.factory.network == 'irc.freenode.net' assert self.cardinal.nickname == self.factory.nickname self.cardinal.nickname = 'NotCardinal' assert self.factory.nickname == 'NotCardinal' assert self.cardinal.password == self.factory.server_password self.cardinal.password = '******' assert self.factory.server_password == 'server_password' assert self.cardinal.username == self.factory.username self.cardinal.username = '******' assert self.factory.username == 'username' assert self.cardinal.realname == self.factory.realname self.cardinal.realname = 'realname' assert self.factory.realname == 'realname' assert self.cardinal.storage_path == self.factory.storage_path with pytest.raises(AttributeError): self.cardinal.storage_path = '/path/to/storage' @patch.object(CardinalBot, 'join') @patch.object(CardinalBot, 'msg') @patch.object(CardinalBot, 'send') @patch('cardinal.bot.PluginManager', autospec=True) def test_signedOn_sets_bot_mode_joins_and_instantiates_plugin_manager( self, mock_plugin_manager, mock_send, mock_msg, mock_join, ): # we want to make sure this is created del self.cardinal.plugin_manager channels = ['#channel1', '#channel2'] self.factory.channels = channels self.cardinal.signedOn() assert not mock_msg.called # no nickserv password provided assert mock_join.mock_calls == [call(channel) for channel in channels] mock_send.assert_called_once_with("MODE {} +B".format( self.cardinal.nickname)) mock_plugin_manager.assert_called_once_with(self.cardinal, self.factory.plugins, self.factory.blacklist) assert isinstance(self.cardinal.plugin_manager, plugins.PluginManager) assert isinstance(self.cardinal.uptime, datetime) assert self.cardinal.booted == self.factory.booted @patch.object(CardinalBot, 'join') @patch.object(CardinalBot, 'msg') @patch.object(CardinalBot, 'send') @patch('cardinal.bot.PluginManager', autospec=True) def test_signedOn_messages_nickserv( self, mock_plugin_manager, _mock_send, mock_msg, mock_join, ): self.factory.password = '******' self.cardinal.signedOn() assert not mock_join.called mock_msg.assert_called_once_with( 'NickServ', 'IDENTIFY {}'.format(self.factory.password)) @patch.object(CardinalBot, 'msg') @patch.object(CardinalBot, 'send') @patch('cardinal.bot.PluginManager', autospec=True) def test_signedOn_sends_server_commands( self, mock_plugin_manager, mock_send, _mock_msg, ): command1 = 'AUTH foobar' command2 = 'PING' self.factory.server_commands = [ command1, command2, ] self.cardinal.signedOn() mock_send.assert_has_calls([ call(command1), call(command2), call('MODE {} +B'.format(self.factory.nickname)) ]) def test_joined(self): self.cardinal.joined("#bots") # This just logs, nothing to assert @patch('cardinal.bot.irc.IRCClient.lineReceived') def test_lineReceived(self, mock_parent_linereceived): line = b':irc.example.com TEST :foobar foobar' self.cardinal.lineReceived(line) self.event_manager.fire.assert_called_once_with( 'irc.raw', 'TEST', line.decode('utf-8'), ) mock_parent_linereceived.assert_called_once_with(line) @patch('cardinal.bot.irc.IRCClient.lineReceived') def test_lineReceived_non_utf8(self, mock_parent_linereceived): line = b":irc-us-east-2.darkscience.net 332 Cardinal #pirates :\x031 \x0311,10[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0313,6[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0311,10[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0313,6[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0311,10[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0313,6[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0311,10[\x031]\x03\x0311,6\x030 Pirates Game! - Welcome aboard Dark Sails, Season 4, Mod: Pauper Privateers! - \x1dJoin wit\' !Pirates\x1d - \x0311\x1fwww.piratesirc.com\x1f \x0311,6\x0311,10[\x031]\x031,1\x1f\xc3\x82\xc2\xaf\x1f\x0313,6[\x031]\x031,1\x1f\xc3\x82\xc2" # noqa: E501 expected_line = line.decode('utf-8', 'replace') self.cardinal.lineReceived(line) self.event_manager.fire.assert_called_once_with( 'irc.raw', '332', expected_line, ) mock_parent_linereceived.assert_called_once_with( expected_line.encode('utf-8', 'replace')) @patch('cardinal.bot.irc.IRCClient.lineReceived') def test_lineReceived_error(self, mock_parent_linereceived): line = b':irc.example.com 401 Cardinal :No nick/channel' self.cardinal.lineReceived(line) self.event_manager.fire.assert_called_once_with( 'irc.raw', '401', line.decode('utf-8'), ) mock_parent_linereceived.assert_called_once_with(line) # Errors are logged, but we don't test for log messages def test_irc_PRIVMSG(self): self.plugin_manager.call_command.side_effect = \ exceptions.CommandNotFoundError # should be caught prefix, source = self.get_user() channel = '#test' message = 'this is a test' self.cardinal.irc_PRIVMSG(prefix, [channel, message]) self.event_manager.fire.assert_called_once_with( 'irc.privmsg', source, '#test', 'this is a test', ) self.plugin_manager.call_command.assert_called_once_with( source, channel, message, ) def test_irc_PRIVMSG_in_private_chat(self): self.plugin_manager.call_command.side_effect = \ exceptions.CommandNotFoundError # should be caught prefix, source = self.get_user() channel = self.cardinal.nickname message = 'this is a test' self.cardinal.irc_PRIVMSG(prefix, [channel, message]) self.event_manager.fire.assert_called_once_with( 'irc.privmsg', source, channel, 'this is a test', ) self.plugin_manager.call_command.assert_called_once_with( source, source.nick, message, ) def test_irc_NOTICE(self): prefix, source = self.get_user() channel = '#test' message = 'this is a test' self.cardinal.irc_NOTICE(prefix, [channel, message]) self.event_manager.fire.assert_called_once_with( 'irc.notice', source, '#test', 'this is a test', ) def test_irc_NOTICE_from_server_no_events(self): channel = self.factory.nickname message = 'this is a test' self.cardinal.irc_NOTICE('irc.freenode.net', [channel, message]) assert not self.event_manager.fire.called def test_irc_NICK(self): prefix, source = self.get_user() new_nick = 'new_nick' self.cardinal.irc_NICK(prefix, [new_nick]) self.event_manager.fire.assert_called_once_with( 'irc.nick', source, new_nick, ) def test_irc_TOPIC(self): prefix, source = self.get_user() channel = '#channel' topic = 'New topic' self.cardinal.irc_TOPIC(prefix, [channel, topic]) self.event_manager.fire.assert_called_once_with( 'irc.topic', source, channel, topic, ) def test_irc_MODE(self): prefix, source = self.get_user() channel = '#channel' self.cardinal.irc_MODE(prefix, [channel, '+b', 'user!*@*']) self.event_manager.fire.assert_called_once_with( 'irc.mode', source, channel, '+b user!*@*', ) def test_irc_MODE_from_server_no_events(self): channel = '#channel' self.cardinal.irc_MODE('irc.freenode.net', [channel, '+b', 'user!*@*']) assert not self.event_manager.fire.called def test_irc_JOIN(self): prefix, source = self.get_user() channel = '#channel' self.cardinal.irc_JOIN(prefix, [channel]) self.event_manager.fire.assert_called_once_with( 'irc.join', source, channel, ) def test_irc_PART(self): prefix, source = self.get_user() channel = '#channel' message = 'Leaving the channel now' self.cardinal.irc_PART(prefix, [channel, message]) self.event_manager.fire.assert_called_once_with( 'irc.part', source, channel, message, ) def test_irc_PART_no_message(self): prefix, source = self.get_user() channel = '#channel' self.cardinal.irc_PART(prefix, [channel]) self.event_manager.fire.assert_called_once_with( 'irc.part', source, channel, None, ) def test_irc_KICK(self): prefix, source = self.get_user() nick = 'kicked_nick' channel = '#channel' message = 'And stay out!' self.cardinal.irc_KICK(prefix, [channel, nick, message]) self.event_manager.fire.assert_called_once_with( 'irc.kick', source, channel, nick, message, ) def test_irc_KICK_no_message(self): prefix, source = self.get_user() nick = 'kicked_nick' channel = '#channel' self.cardinal.irc_KICK(prefix, [channel, nick]) self.event_manager.fire.assert_called_once_with( 'irc.kick', source, channel, nick, None, ) def test_irc_QUIT(self): prefix, source = self.get_user() message = "Goodbye now!" self.cardinal.irc_QUIT(prefix, [message]) self.event_manager.fire.assert_called_once_with( 'irc.quit', source, message, ) def test_irc_QUIT_no_message(self): prefix, source = self.get_user() self.cardinal.irc_QUIT(prefix, [""]) self.event_manager.fire.assert_called_once_with( 'irc.quit', source, None, ) def test_irc_unknown_no_op(self): prefix, _ = self.get_user() self.cardinal.irc_unknown(prefix, 'UNKNOWN', []) assert not self.event_manager.fire.called def test_irc_unknown_handles_INVITE(self): prefix, user = self.get_user() channel = '#channel' self.cardinal.irc_unknown(prefix, 'INVITE', ['Cardinal', channel]) self.event_manager.fire.assert_called_once_with( 'irc.invite', user, channel) @defer.inlineCallbacks def test_who(self): _, user = self.get_user() channel = '#channel' # Issue WHO to server with patch.object(self.cardinal, 'sendLine'): d = self.cardinal.who(channel) assert isinstance(d, defer.Deferred) # Simulate another plugin separately issuing WHO with patch.object(self.cardinal, 'sendLine'): d2 = self.cardinal.who(channel) # Need separate Deferreds to prevent each callback needing to return # the results, and prevent an error in one callback from breaking # another assert d is not d2 self.cardinal.irc_RPL_WHOREPLY( 'irc.freenode.net', [ 'Cardinal', # nick requesting WHO channel, # channel WHO refers to user.user, # username/ident user.vhost, # user hostname 'celadon.darkscience.net', # server connected to user.nick, # nickserv namea 'H', # H = here, G = gone, * suffix = IRC Operator '0 Mr. Cardinal', # 0 = number of servers between you and user, # then space precedes realname ]) self.cardinal.irc_RPL_ENDOFWHO( 'irc.freenode.net', ['Cardinal', channel, 'End of /WHO list.']) users = yield d assert users == [user] users2 = yield d2 assert users == users2 def test_config_raises_without_plugin_manager(self): self.cardinal.plugin_manager = None with pytest.raises(exceptions.PluginError): self.cardinal.config('plugin') def test_config_raises_for_config_not_found(self): plugin_name = 'plugin' self.plugin_manager.get_config.side_effect = \ exceptions.ConfigNotFoundError with pytest.raises(exceptions.ConfigNotFoundError): self.cardinal.config(plugin_name) self.plugin_manager.get_config.assert_called_once_with(plugin_name) def test_config(self): plugin_name = 'plugin' return_value = {} self.plugin_manager.get_config.return_value = return_value assert self.cardinal.config(plugin_name) == return_value self.plugin_manager.get_config.assert_called_once_with(plugin_name) def test_sendMsg(self): # passes through to Twisted w/ additional logging channel = '#channel' message = 'this is some message' length = 5 with patch.object(self.cardinal, 'msg') as msg_mock: self.cardinal.sendMsg(channel, message, length) msg_mock.assert_called_once_with(channel, message, length) def test_sendMsg_no_length(self): # passes through to Twisted w/ additional logging channel = '#channel' message = 'this is some message' with patch.object(self.cardinal, 'msg') as msg_mock: self.cardinal.sendMsg(channel, message) msg_mock.assert_called_once_with(channel, message, None) def test_send(self): # passes through to Twisted w/ additional logging message = 'PRIVMSG #channel :this is a message' with patch.object(self.cardinal, 'sendLine') as sendLine_mock: self.cardinal.send(message) sendLine_mock.assert_called_once_with(message) def test_disconnect(self): with patch.object(self.cardinal, 'quit') as quit_mock: self.cardinal.disconnect() self.plugin_manager.unload_all.assert_called_once_with() assert self.factory.disconnect is True quit_mock.assert_called_once_with('') def test_disconnect_with_message(self): message = 'Quitting now' with patch.object(self.cardinal, 'quit') as quit_mock: self.cardinal.disconnect(message) self.plugin_manager.unload_all.assert_called_once_with() assert self.factory.disconnect is True quit_mock.assert_called_once_with(message) def test_get_db(self): assert self.cardinal.db_locks == {} with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) db = self.cardinal.get_db('test') assert len(self.cardinal.db_locks) == 1 db_path = list(self.cardinal.db_locks.keys())[0] assert db_path.endswith( os.path.join('database', 'test-{}.json'.format(self.factory.network))) assert callable(db) def test_get_db_not_network_specific(self): with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) self.cardinal.get_db('test', network_specific=False) assert len(self.cardinal.db_locks) == 1 db_path = list(self.cardinal.db_locks.keys())[0] # note lack of network formatted in below assert db_path.endswith(os.path.join('database', 'test.json')) def test_get_db_db(self): with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) db = self.cardinal.get_db('test', network_specific=False) with db() as db1: assert db1 == {} db1['test'] = 'x' with db() as db2: assert db1 == db2 def test_db_contextmanager_locks(self): with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) db = self.cardinal.get_db('test', network_specific=False) with db(): with pytest.raises(exceptions.LockInUseError): with db(): pass def test_db_contextmanager_locks_with_multiple_get_db_calls(self): with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) db1 = self.cardinal.get_db('test', network_specific=False) db2 = self.cardinal.get_db('test', network_specific=False) with db1(): with pytest.raises(exceptions.LockInUseError): with db2(): pass def test_db_contextmanager_exception(self): with tempdir('database') as database_path: self.factory.storage_path = os.path.dirname(database_path) db = self.cardinal.get_db('test', network_specific=False) try: with db() as db_obj: assert db_obj == {} db_obj['x'] = True raise Exception() except Exception: pass with db() as db_obj: assert db_obj == {} def test_get_user_tuple(self): assert CardinalBot.get_user_tuple('unit|test!unit~@unit/test') == \ ('unit|test', 'unit~', 'unit/test') def test_get_user_tuple_names(self): user = CardinalBot.get_user_tuple('[email protected]') assert user.nick == 'unittest' assert user.user == 'unit' assert user.vhost == 'unit.test' def test_get_user_tuple_doesnt_match(self): assert CardinalBot.get_user_tuple('foobar') is None