Esempio n. 1
0
 def test_help_for_command(self):
     self.object._bot.dispatcher = MessageDispatcher()
     self.object._bot.webserver = Webserver(test_host, test_port)
     self.object._bot.dispatcher.register_plugin(self.object)
     assert self.object.help(self.test_event, ["help"]) == format_docstring(
         "Displays help for each command"
     )
Esempio n. 2
0
    def __init__(self, config, test_mode=False):
        self.always_send_dm = []
        self.config = config
        self.dispatcher = MessageDispatcher()
        self.event_handlers = {}
        self.is_setup = False
        self.log = logging.getLogger(type(self).__name__)
        self.plugins = PluginManager(self, test_mode)
        self.runnable = True
        self.sc = None
        self.webserver = None
        self.test_mode = test_mode
        self.reconnect_needed = True
        self.bot_start_time = None

        if self.test_mode:
            self.metrics = {
                'startup_time': 0
            }

        from slackminion.plugins.core import version
        self.version = version
        try:
            from slackminion.plugins.core import commit
            self.commit = commit
        except ImportError:
            self.commit = "HEAD"
Esempio n. 3
0
 def setUp(self, *args):
     self.object = AuthManager(bot=mock.Mock())
     self.object._bot.dispatcher = MessageDispatcher()
     self.object._bot.api_client.users_find.return_value = TestUser()
     self.test_event = SlackEvent(event_type='tests', **test_payload)
     self.test_user = SlackUser(user_info=test_user_response['user'],
                                api_client=self.object._bot.api_client)
     self.test_event.user = self.test_user
Esempio n. 4
0
    def __init__(self, config, test_mode=False, dev_mode=False):
        self.config = config
        self.dispatcher = MessageDispatcher()
        self.log = logging.getLogger(type(self).__name__)
        self.plugins = PluginManager(self, test_mode)
        self.test_mode = test_mode
        self.dev_mode = dev_mode
        self.event_loop = asyncio.get_event_loop()

        if self.test_mode:
            self.metrics = {'startup_time': 0}

        self.version = my_version
        try:
            from slackminion.plugins.core import commit
            self.commit = commit
        except ImportError:
            self.commit = "HEAD"
Esempio n. 5
0
class Bot(object):
    def __init__(self, config, test_mode=False):
        self.always_send_dm = []
        self.config = config
        self.dispatcher = MessageDispatcher()
        self.event_handlers = {}
        self.is_setup = False
        self.log = logging.getLogger(type(self).__name__)
        self.plugins = PluginManager(self, test_mode)
        self.runnable = True
        self.sc = None
        self.webserver = None
        self.test_mode = test_mode
        self.reconnect_needed = True
        self.bot_start_time = None

        if self.test_mode:
            self.metrics = {
                'startup_time': 0
            }

        from slackminion.plugins.core import version
        self.version = version
        try:
            from slackminion.plugins.core import commit
            self.commit = commit
        except ImportError:
            self.commit = "HEAD"

    def start(self):
        """Initializes the bot, plugins, and everything."""
        self.bot_start_time = datetime.now()
        self.webserver = Webserver(self.config['webserver']['host'], self.config['webserver']['port'])
        self.plugins.load()
        self.plugins.load_state()
        self._find_event_handlers()
        self.sc = ThreadedSlackClient(self.config['slack_token'])

        self.always_send_dm = ['_unauthorized_']
        if 'always_send_dm' in self.config:
            self.always_send_dm.extend(map(lambda x: '!' + x, self.config['always_send_dm']))

        # Rocket is very noisy at debug
        logging.getLogger('Rocket.Errors.ThreadPool').setLevel(logging.INFO)

        self.is_setup = True
        if self.test_mode:
            self.metrics['startup_time'] = (datetime.now() - self.bot_start_time).total_seconds() * 1000.0

    def _find_event_handlers(self):
        for name in dir(self):
            method = getattr(self, name)
            if callable(method) and hasattr(method, 'is_eventhandler'):
                for event in method.events:
                    self.event_handlers[event] = method

    def run(self, start=True):
        """
        Connects to slack and enters the main loop.

        * start - If True, rtm.start API is used. Else rtm.connect API is used

        For more info, refer to
        https://python-slackclient.readthedocs.io/en/latest/real_time_messaging.html#rtm-start-vs-rtm-connect
        """
        # Fail out if setup wasn't run
        if not self.is_setup:
            raise NotSetupError

        # Start the web server
        self.webserver.start()

        first_connect = True

        try:
            while self.runnable:
                if self.reconnect_needed:
                    if not self.sc.rtm_connect(with_team_state=start):
                        return False
                    self.reconnect_needed = False
                    if first_connect:
                        first_connect = False
                        self.plugins.connect()

                # Get all waiting events - this always returns a list
                try:
                    events = self.sc.rtm_read()
                except AttributeError:
                    self.log.exception('Something has failed in the slack rtm library.  This is fatal.')
                    self.runnable = False
                    events = []
                except:
                    self.log.exception('Unhandled exception in rtm_read()')
                    self.reconnect_needed = True
                    events = []
                for e in events:
                    try:
                        self._handle_event(e)
                    except KeyboardInterrupt:
                        # Gracefully shutdown
                        self.runnable = False
                    except:
                        self.log.exception('Unhandled exception in event handler')
                sleep(0.1)
        except KeyboardInterrupt:
            # On ctrl-c, just exit
            pass
        except:
            self.log.exception('Unhandled exception')

    def stop(self):
        """Does cleanup of bot and plugins."""
        if self.webserver is not None:
            self.webserver.stop()
        if not self.test_mode:
            self.plugins.save_state()

    def send_message(self, channel, text, thread=None, reply_broadcast=None):
        """
        Sends a message to the specified channel

        * channel - The channel to send to.  This can be a SlackChannel object, a channel id, or a channel name
        (without the #)
        * text - String to send
        * thread - reply to the thread. See https://api.slack.com/docs/message-threading#threads_party
        * reply_broadcast - Set to true to indicate your reply is germane to all members of a channel
        """
        # This doesn't want the # in the channel name
        if isinstance(channel, SlackRoomIMBase):
            channel = channel.id
        self.log.debug("Trying to send to %s: %s", channel, text)
        self.sc.rtm_send_message(channel, text, thread=thread, reply_broadcast=reply_broadcast)

    def send_im(self, user, text):
        """
        Sends a message to a user as an IM

        * user - The user to send to.  This can be a SlackUser object, a user id, or the username (without the @)
        * text - String to send
        """
        if isinstance(user, SlackUser):
            user = user.id
            channelid = self._find_im_channel(user)
        else:
            channelid = user.id
        self.send_message(channelid, text)

    def _find_im_channel(self, user):
        resp = self.sc.api_call('im.list')
        channels = filter(lambda x: x['user'] == user, resp['ims'])
        if len(channels) > 0:
            return channels[0]['id']
        resp = self.sc.api_call('im.open', user=user)
        return resp['channel']['id']

    def _load_user_rights(self, user):
        if user is not None:
            if 'bot_admins' in self.config:
                if user.username in self.config['bot_admins']:
                    user.is_admin = True

    def _handle_event(self, event):
        if 'type' not in event:
            # This is likely a notification that the bot was mentioned
            self.log.debug("Received odd event: %s", event)
            return
        e = SlackEvent(sc=self.sc, **event)
        self.log.debug("Received event type: %s", e.type)

        if e.user is not None:
            if hasattr(self, 'user_manager'):
                if not isinstance(e.user, SlackUser):
                    self.log.debug("User is not SlackUser: %s", e.user)
                else:
                    user = self.user_manager.get(e.user.id)
                    if user is None:
                        user = self.user_manager.set(e.user)
                    e.user = user

        if e.type in self.event_handlers:
            self.event_handlers[e.type](e)

    @eventhandler(events='message')
    def _event_message(self, msg):
        self.log.debug("Message.message: %s: %s: %s", msg.channel, msg.user, msg.__dict__)

        # The user manager should load rights when a user is added
        if not hasattr(self, 'user_manager'):
            self._load_user_rights(msg.user)
        try:
            cmd, output = self.dispatcher.push(msg)
        except:
            self.log.exception('Unhandled exception')
            return
        self.log.debug("Output from dispatcher: %s", output)
        if output:
            if cmd in self.always_send_dm:
                self.send_im(msg.user, output)
            else:
                self.send_message(msg.channel, output)

    @eventhandler(events='error')
    def _event_error(self, msg):
        self.log.error("Received an error response from Slack: %s", msg.__dict__)

    @eventhandler(events='team_migration_started')
    def _event_team_migration_started(self, msg):
        self.log.warn("Slack has initiated a team migration to a new server.  Attempting to reconnect...")
        self.reconnect_needed = True
class TestDispatcher(unittest.TestCase):
    @mock.patch('slackminion.slack.SlackUser')
    def setUp(self, mock_user):
        mock_user.return_value = test_user
        self.dispatcher = MessageDispatcher()
        self.p = DummyPlugin(None)
        self.test_payload = deepcopy(test_payload)

    def tearDown(self):
        self.dispatcher = None
        self.test_payload = None

    def test_register_plugin(self):
        self.dispatcher.register_plugin(self.p)

    def test_register_duplicate_plugin(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(DuplicateCommandError) as e:
            self.dispatcher.register_plugin(self.p)
            self.assertIn('abc', str(e))

    def test_get_command(self):
        self.dispatcher.register_plugin(self.p)
        method = self.dispatcher._get_command('!abc', None).method
        assert method == self.p.abc
        assert method(None, None) == 'abcba'

    def test_get_invalid_command(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(KeyError):
            self.dispatcher._get_command('!def', None)

    def test_parse_message(self):
        e = SlackEvent(event_type='message',
                       **{'data': {
                           'text': 'Hello world'
                       }})
        assert self.dispatcher._parse_message(e) == ['Hello', 'world']

    def test_parse_message_unicode(self):
        e = SlackEvent(event_type='message',
                       **{'data': {
                           'text': 'Hello\xa0world'
                       }})
        assert self.dispatcher._parse_message(e) == ['Hello', 'world']

    @async_test
    async def test_strip_formatting(self):
        test_string = "!stripformat <@U123456> check <#C123456|test-channel> has <https://www.pinterest.com|www.pinterest.com>"
        expected_response = "@U123456 check #test-channel has www.pinterest.com"
        e = SlackEvent(event_type="message", **{"data": {"text": test_string}})
        e.user = mock.Mock()
        e.channel = test_conversation
        self.dispatcher.register_plugin(self.p)
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd_opts.get("strip_formatting") is True
        self.assertEqual(expected_response, output)

    def test_unignore_nonignored_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get('api_client'))
        self.assertFalse(self.dispatcher.unignore(c))
        assert 'testchannel' not in self.dispatcher.ignored_channels

    def test_ignore_duplicate_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get('api_client'))
        assert self.dispatcher.ignore(c) is True
        assert c.name in self.dispatcher.ignored_channels
        assert self.dispatcher.ignore(c) is False
        assert len(self.dispatcher.ignored_channels) == 1

    def test_ignore_slackchannel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get('api_client'))
        c.name = 'testchannel'
        assert self.dispatcher.ignore(c) is True
        assert 'testchannel' in self.dispatcher.ignored_channels
        assert self.dispatcher._is_channel_ignored(self.p.abc, c) is True

    def test_unignore_slackchannel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get('api_client'))
        self.assertTrue(self.dispatcher.ignore(c))
        self.assertTrue(self.dispatcher.unignore(c))
        self.assertNotIn(test_channel.get('name'),
                         self.dispatcher.ignored_channels)
        self.assertFalse(self.dispatcher._is_channel_ignored(self.p.abc, c))

    @async_test
    async def test_push(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload['data'].update({'text': '!abc'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!abc'
        assert output == 'abcba'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is False
        assert cmd_opts.get('reply_in_thread') is False

    @async_test
    async def test_push_alias(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload['data'].update({'text': '!bca'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!bca'
        assert output == 'abcba'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is False
        assert cmd_opts.get('reply_in_thread') is False

    @async_test
    async def test_push_to_thread(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload['data'].update({'text': '!efg'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!efg'
        assert output == 'efgfe'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is False
        assert cmd_opts.get('reply_in_thread') is True

    @async_test
    async def test_push_to_thread_with_broadcast(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload['data'].update({'text': '!hij'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!hij'
        assert output == 'hijih'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is True
        assert cmd_opts.get('reply_in_thread') is True

    @async_test
    async def test_push_not_command(self):
        payload = dict(self.test_payload)
        payload['data'].update({'text': 'Not a command'})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_message_replied_event(self):
        payload = dict(self.test_payload)
        payload['data'].update({'subtype': 'message_replied'})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_no_user(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload['data'].update({'subtype': 'message_replied'})
        payload['data'].pop('user')
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_ignored_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get('api_client'))
        self.dispatcher.ignore(c)
        self.dispatcher.register_plugin(self.p)
        self.dispatcher._is_channel_ignored = mock.Mock(return_value=True)
        self.test_payload['data'].update({
            'text': '!abc',
            'user': test_user_id,
            'channel': test_channel_id
        })
        e = SlackEvent(event_type='message', **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        assert await self.dispatcher.push(e) == ('_ignored_', '', None)

    @async_test
    async def test_async_cmd(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload['data'].update({'text': '!asyncabc'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!asyncabc'
        assert output == 'asyncabc response'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert ('parse' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is True
        assert cmd_opts.get('reply_in_thread') is True
        assert cmd_opts.get('parse') is None

    @async_test
    async def test_async_cmd_parse(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload['data'].update({'text': '!asyncparse'})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == '!asyncparse'
        assert output == 'async parse command #parse'
        assert type(cmd_opts) == dict
        assert ('reply_in_thread' in list(cmd_opts.keys())) is True
        assert ('reply_broadcast' in list(cmd_opts.keys())) is True
        assert ('parse' in list(cmd_opts.keys())) is True
        assert cmd_opts.get('reply_broadcast') is False
        assert cmd_opts.get('reply_in_thread') is False
        assert cmd_opts.get('parse') is True
 def setUp(self, mock_user):
     mock_user.return_value = test_user
     self.dispatcher = MessageDispatcher()
     self.p = DummyPlugin(None)
     self.test_payload = deepcopy(test_payload)
Esempio n. 8
0
 def setup(self):
     self.object = MessageDispatcher()
     self.p = DummyPlugin(None)
Esempio n. 9
0
class TestDispatcher(object):
    def setup(self):
        self.object = MessageDispatcher()
        self.p = DummyPlugin(None)

    def teardown(self):
        self.object = None

    def test_register_plugin(self):
        self.object.register_plugin(self.p)

    def test_register_duplicate_plugin(self):
        self.object.register_plugin(self.p)
        with pytest.raises(DuplicateCommandError) as e:
            self.object.register_plugin(self.p)
        assert 'abc' in str(e)

    def test_get_command(self):
        self.object.register_plugin(self.p)
        method = self.object._get_command('!abc', None).method
        assert method == self.p.abc
        assert method(None, None) == 'xyzzy'

    def test_get_invalid_command(self):
        self.object.register_plugin(self.p)
        with pytest.raises(KeyError):
            self.object._get_command('!def', None)

    def test_parse_message(self):
        e = SlackEvent(text='Hello world')
        assert self.object._parse_message(e) == ['Hello', 'world']

    def test_ignore_channel(self):
        assert self.object.ignore('testchannel') is True
        assert 'testchannel' in self.object.ignored_channels

    def test_unignore_channel(self):
        assert self.object.ignore('testchannel') is True
        assert self.object.unignore('testchannel') is True
        assert 'testchannel' not in self.object.ignored_channels

    def test_unignore_nonignored_channel(self):
        self.object.unignore('testchannel') is False
        assert 'testchannel' not in self.object.ignored_channels

    def test_ignore_duplicate_channel(self):
        assert self.object.ignore('testchannel') is True
        assert 'testchannel' in self.object.ignored_channels
        assert self.object.ignore('testchannel') is False
        assert len(self.object.ignored_channels) == 1

    def test_ignore_slackchannel(self):
        c = SlackChannel('CTEST')
        c.name = 'testchannel'
        assert self.object.ignore(c) is True
        assert 'testchannel' in self.object.ignored_channels
        assert self.object._is_channel_ignored(self.p.abc, c) is True

    def test_unignore_slackchannel(self):
        c = SlackChannel('CTEST')
        c.name = 'testchannel'
        assert self.object.ignore(c) is True
        assert self.object.unignore(c) is True
        assert 'testchannel' not in self.object.ignored_channels
        assert self.object._is_channel_ignored(self.p.abc, c) is False

    def test_push(self):
        self.object.register_plugin(self.p)
        e = SlackEvent(DummySlackConnection(), **{'text': '!abc', 'user': test_user_id, 'channel': test_channel_id})
        assert self.object.push(e) == ('!abc', 'xyzzy')

    def test_push_alias(self):
        self.object.register_plugin(self.p)
        e = SlackEvent(DummySlackConnection(), **{'text': '!xyz', 'user': test_user_id, 'channel': test_channel_id})
        assert self.object.push(e) == ('!xyz', 'xyzzy')

    def test_push_not_command(self):
        e = SlackEvent(DummySlackConnection(), **{'text': 'Not a command'})
        assert self.object.push(e) == (None, None)

    def test_push_message_replied_event(self):
        e = SlackEvent(DummySlackConnection(), **{'subtype': 'message_replied'})
        assert self.object.push(e) == (None, None)

    def test_push_no_user(self):
        self.object.register_plugin(self.p)
        e = SlackEvent(DummySlackConnection(), **{'text': '!abc'})
        assert self.object.push(e) == (None, None)

    def test_push_ignored_channel(self):
        c = SlackChannel('CTEST')
        c.name = 'testchannel'
        self.object.ignore(c)
        self.object.register_plugin(self.p)
        e = SlackEvent(DummySlackConnection(), **{'text': '!abc', 'user': test_user_id, 'channel': test_channel_id})
        assert self.object.push(e) == ('_ignored_', '')
Esempio n. 10
0
 def test_get_short_help_for_command(self):
     for command, helpstr in test_help_short_data:
         self.object._bot.dispatcher = MessageDispatcher()
         self.object._bot.dispatcher.register_plugin(DummyPlugin(self.object._bot))
         assert self.object._get_short_help_for_command(command) == helpstr
Esempio n. 11
0
class TestDispatcher(unittest.TestCase):
    @mock.patch("slackminion.slack.SlackUser")
    def setUp(self, mock_user):
        mock_user.return_value = test_user
        self.dispatcher = MessageDispatcher()
        self.p = DummyPlugin(None)
        self.test_payload = deepcopy(test_payload)

    def tearDown(self):
        self.dispatcher = None
        self.test_payload = None

    def test_register_plugin(self):
        self.dispatcher.register_plugin(self.p)

    def test_register_duplicate_plugin(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(DuplicateCommandError) as e:
            self.dispatcher.register_plugin(self.p)
            self.assertIn("abc", str(e))

    def test_get_command(self):
        self.dispatcher.register_plugin(self.p)
        method = self.dispatcher._get_command("!abc", None).method
        assert method == self.p.abc
        assert method(None, None) == "abcba"

    def test_get_invalid_command(self):
        self.dispatcher.register_plugin(self.p)
        with self.assertRaises(KeyError):
            self.dispatcher._get_command("!def", None)

    def test_parse_message(self):
        e = SlackEvent(event_type="message",
                       **{"data": {
                           "text": "Hello world"
                       }})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    def test_parse_message_extra_space(self):
        # Strip out extra spaces
        e = SlackEvent(event_type="message",
                       **{"data": {
                           "text": "Hello  world"
                       }})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    def test_parse_message_unicode(self):
        e = SlackEvent(event_type="message",
                       **{"data": {
                           "text": "Hello\xa0world"
                       }})
        assert self.dispatcher._parse_message(e) == ["Hello", "world"]

    @async_test
    async def test_strip_formatting(self):
        test_string = "!stripformat <@U123456> check <#C123456|test-channel> has <https://www.pinterest.com|www.pinterest.com>"
        expected_response = "@U123456 check #test-channel has www.pinterest.com"
        e = SlackEvent(event_type="message", **{"data": {"text": test_string}})
        e.user = mock.Mock()
        e.channel = test_conversation
        self.dispatcher.register_plugin(self.p)
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd_opts.get("strip_formatting") is True
        self.assertEqual(expected_response, output)

    def test_unignore_nonignored_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get("api_client"))
        self.assertFalse(self.dispatcher.unignore(c))
        assert "testchannel" not in self.dispatcher.ignored_channels

    def test_ignore_duplicate_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get("api_client"))
        assert self.dispatcher.ignore(c) is True
        assert c.name in self.dispatcher.ignored_channels
        assert self.dispatcher.ignore(c) is False
        assert len(self.dispatcher.ignored_channels) == 1

    def test_ignore_slackchannel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get("api_client"))
        c.name = "testchannel"
        assert self.dispatcher.ignore(c) is True
        assert "testchannel" in self.dispatcher.ignored_channels
        assert self.dispatcher._is_channel_ignored(self.p.abc, c) is True

    def test_unignore_slackchannel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get("api_client"))
        self.assertTrue(self.dispatcher.ignore(c))
        self.assertTrue(self.dispatcher.unignore(c))
        self.assertNotIn(test_channel.get("name"),
                         self.dispatcher.ignored_channels)
        self.assertFalse(self.dispatcher._is_channel_ignored(self.p.abc, c))

    @async_test
    async def test_push(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!abc"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!abc"
        assert output == "abcba"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False

    @async_test
    async def test_push_alias(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!bca"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!bca"
        assert output == "abcba"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False

    @async_test
    async def test_push_to_thread(self):
        self.dispatcher.register_plugin(self.p)
        self.test_payload["data"].update({"text": "!efg"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!efg"
        assert output == "efgfe"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is True

    @async_test
    async def test_push_to_thread_with_broadcast(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!hij"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!hij"
        assert output == "hijih"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is True
        assert cmd_opts.get("reply_in_thread") is True

    @async_test
    async def test_push_not_command(self):
        payload = dict(self.test_payload)
        payload["data"].update({"text": "Not a command"})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_no_text(self):
        payload = dict(self.test_payload)
        payload["data"].update({"text": ""})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_message_replied_event(self):
        payload = dict(self.test_payload)
        payload["data"].update({"subtype": "message_replied"})
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_no_user(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"subtype": "message_replied"})
        payload["data"].pop("user")
        e = SlackEvent(event_type="message", **payload)
        assert await self.dispatcher.push(e) == (None, None, None)

    @async_test
    async def test_push_ignored_channel(self):
        c = SlackConversation(conversation=test_channel,
                              api_client=test_payload.get("api_client"))
        self.dispatcher.ignore(c)
        self.dispatcher.register_plugin(self.p)
        self.dispatcher._is_channel_ignored = mock.Mock(return_value=True)
        self.test_payload["data"].update({
            "text": "!abc",
            "user": test_user_id,
            "channel": test_channel_id
        })
        e = SlackEvent(event_type="message", **self.test_payload)
        e.channel = test_conversation
        e.user = mock.Mock()
        assert await self.dispatcher.push(e) == ("_ignored_", "", None)

    @async_test
    async def test_async_cmd(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!asyncabc"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!asyncabc"
        assert output == "asyncabc response"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert ("parse" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is True
        assert cmd_opts.get("reply_in_thread") is True
        assert cmd_opts.get("parse") is None

    @async_test
    async def test_async_cmd_parse(self):
        self.dispatcher.register_plugin(self.p)
        payload = dict(self.test_payload)
        payload["data"].update({"text": "!asyncparse"})
        e = SlackEvent(event_type="message", **self.test_payload)
        e.user = mock.Mock()
        e.channel = test_conversation
        cmd, output, cmd_opts = await self.dispatcher.push(e)
        assert cmd == "!asyncparse"
        assert output == "async parse command #parse"
        assert type(cmd_opts) == dict
        assert ("reply_in_thread" in list(cmd_opts.keys())) is True
        assert ("reply_broadcast" in list(cmd_opts.keys())) is True
        assert ("parse" in list(cmd_opts.keys())) is True
        assert cmd_opts.get("reply_broadcast") is False
        assert cmd_opts.get("reply_in_thread") is False
        assert cmd_opts.get("parse") is True