From 2b9e9b752c35fa32212f91d736e3f757f15c75a6 Mon Sep 17 00:00:00 2001 From: Jeff Ammons Date: Fri, 27 May 2016 17:59:40 -0700 Subject: [PATCH 1/2] Add tests and bugfix for b'' messages appearing in python3. I added unicode and non-unicode message testing for the output() method on RTMBot. I'm concerned about why the encode('ascii', 'ignore') was there in the first place, but not having it seems to do the right thing... --- requirements-dev.txt | 1 + rtmbot/core.py | 2 +- tests/test_example.py | 2 -- tests/test_rtmbot_core.py | 51 ++++++++++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 9 deletions(-) delete mode 100644 tests/test_example.py diff --git a/requirements-dev.txt b/requirements-dev.txt index 8e69fc4..b011dfe 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,7 @@ coveralls==1.1 ipdb==0.9.3 ipython==4.1.2 +mock==2.0.0 pdbpp==0.8.3 pytest>=2.8.2 pytest-cov==2.2.1 diff --git a/rtmbot/core.py b/rtmbot/core.py index 049bb77..9cc507d 100755 --- a/rtmbot/core.py +++ b/rtmbot/core.py @@ -101,7 +101,7 @@ class RtmBot(object): if limiter: time.sleep(.1) limiter = False - message = output[1].encode('ascii', 'ignore') + message = output[1] channel.send_message("{}".format(message)) limiter = True diff --git a/tests/test_example.py b/tests/test_example.py deleted file mode 100644 index c8d72d0..0000000 --- a/tests/test_example.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_example(): - assert True \ No newline at end of file diff --git a/tests/test_rtmbot_core.py b/tests/test_rtmbot_core.py index 0353147..85d7563 100644 --- a/tests/test_rtmbot_core.py +++ b/tests/test_rtmbot_core.py @@ -1,15 +1,25 @@ +# -*- coding: utf-8 -*- +try: + from unittest.mock import Mock +except ImportError: + from mock import Mock + from testfixtures import LogCapture from rtmbot.core import RtmBot +def init_rtmbot(): + ''' Initializes an instance of RTMBot with some default values ''' + rtmbot = RtmBot({ + 'SLACK_TOKEN': 'test-12345', + 'BASE_PATH': '/tmp/', + 'LOGFILE': '/tmp/rtmbot.log', + 'DEBUG': True + }) + return rtmbot def test_init(): with LogCapture() as l: - rtmbot = RtmBot({ - 'SLACK_TOKEN': 'test-12345', - 'BASE_PATH': '/tmp/', - 'LOGFILE': '/tmp/rtmbot.log', - 'DEBUG': True - }) + rtmbot = init_rtmbot() assert rtmbot.token == 'test-12345' assert rtmbot.directory == '/tmp/' @@ -18,3 +28,32 @@ def test_init(): l.check( ('root', 'INFO', 'Initialized in: /tmp/') ) + +def test_output(): + ''' Test that sending a message behaves as expected ''' + rtmbot = init_rtmbot() + + # Mock the slack_client object + slackclient_mock = Mock() + channel_mock = Mock() + slackclient_mock.server.channels.find.return_value = channel_mock + rtmbot.slack_client = slackclient_mock + + # mock the plugin object to return a sample response + plugin_mock = Mock() + plugin_mock.do_output.return_value = [['C12345678', 'test message']] + rtmbot.bot_plugins.append(plugin_mock) + + rtmbot.output() + + + # test that the output matches the expected value + channel_mock.send_message.assert_called_with('test message') + + # test that unicode messages work as expected + channel_mock.reset_mock() + plugin_mock.reset_mock() + plugin_mock.do_output.return_value = [['C12345678', '🚀 testing']] + rtmbot.output() + + channel_mock.send_message.assert_called_with('🚀 testing') From c81bdfec99e580d62bc6fdff9127fa167e927ccc Mon Sep 17 00:00:00 2001 From: Jeff Ammons Date: Sun, 5 Jun 2016 13:28:28 -0700 Subject: [PATCH 2/2] Fix unicode error in py2.7 for example plugin Strings should now be passed to the plugins as unicode values in py2.7 so we have to make sure that we don't convert those into ascii by using python str instead of u'' strings. --- README.md | 2 ++ doc/example-plugins/canary.py | 3 +++ doc/example-plugins/counter.py | 3 +++ doc/example-plugins/repeat.py | 3 +++ doc/example-plugins/todo.py | 3 +++ rtmbot/core.py | 4 ++-- setup.py | 2 +- tests/test_rtmbot_core.py | 32 ++++++++++++++++++++++++-------- 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 19e1aa3..062c3d2 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ This will print the incoming message json (dict) to the screen where the bot is Plugins having a method defined as ```catch_all(data)``` will receive ALL events from the websocket. This is useful for learning the names of events and debugging. +Note: If you're using Python 2.x, the incoming data should be a unicode string, be careful you don't coerce it into a normal str object as it will cause errors on output. You can add `from __future__ import unicode_literals` to your plugin file to avoid this. + ####Outgoing data Plugins can send messages back to any channel, including direct messages. This is done by appending a two item array to the outputs global array. The first item in the array is the channel ID and the second is the message text. Example that writes "hello world" when the plugin is started: diff --git a/doc/example-plugins/canary.py b/doc/example-plugins/canary.py index b2cea10..5a03a00 100644 --- a/doc/example-plugins/canary.py +++ b/doc/example-plugins/canary.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals +# don't convert to ascii in py2.7 when creating string to return + import time outputs = [] diff --git a/doc/example-plugins/counter.py b/doc/example-plugins/counter.py index 00fac1a..1b03305 100644 --- a/doc/example-plugins/counter.py +++ b/doc/example-plugins/counter.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals +# don't convert to ascii in py2.7 when creating string to return + import time crontable = [] outputs = [] diff --git a/doc/example-plugins/repeat.py b/doc/example-plugins/repeat.py index 3106457..a288eb3 100644 --- a/doc/example-plugins/repeat.py +++ b/doc/example-plugins/repeat.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals +# don't convert to ascii in py2.7 when creating string to return + crontable = [] outputs = [] diff --git a/doc/example-plugins/todo.py b/doc/example-plugins/todo.py index cd1db8d..3fba6cd 100644 --- a/doc/example-plugins/todo.py +++ b/doc/example-plugins/todo.py @@ -1,4 +1,7 @@ from __future__ import print_function +from __future__ import unicode_literals +# don't convert to ascii in py2.7 when creating string to return + import os import pickle diff --git a/rtmbot/core.py b/rtmbot/core.py index 9cc507d..1b391a3 100755 --- a/rtmbot/core.py +++ b/rtmbot/core.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import unicode_literals import sys import glob import os @@ -101,8 +102,7 @@ class RtmBot(object): if limiter: time.sleep(.1) limiter = False - message = output[1] - channel.send_message("{}".format(message)) + channel.send_message(output[1]) limiter = True def crons(self): diff --git a/setup.py b/setup.py index ef6cfc3..3f670ef 100755 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from distutils.core import setup setup( name='rtmbot', - version='0.10', + version='0.2.0', description='A Slack bot written in python that connects via the RTM API.', author='Ryan Huber', author_email='rhuber@gmail.com', diff --git a/tests/test_rtmbot_core.py b/tests/test_rtmbot_core.py index 85d7563..ef0a8ca 100644 --- a/tests/test_rtmbot_core.py +++ b/tests/test_rtmbot_core.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- try: - from unittest.mock import Mock + from unittest.mock import Mock, create_autospec except ImportError: - from mock import Mock + from mock import Mock, create_autospec from testfixtures import LogCapture -from rtmbot.core import RtmBot +from slackclient import SlackClient, _channel, _server, _util +from rtmbot.core import RtmBot, Plugin def init_rtmbot(): ''' Initializes an instance of RTMBot with some default values ''' @@ -33,14 +34,21 @@ def test_output(): ''' Test that sending a message behaves as expected ''' rtmbot = init_rtmbot() - # Mock the slack_client object - slackclient_mock = Mock() - channel_mock = Mock() + # Mock the slack_client object with Server, Channel objects and needed methods + slackclient_mock = create_autospec(SlackClient) + server_mock = create_autospec(_server.Server) + + # Mock Server with channels method and correct return value + slackclient_mock.server = server_mock + searchlist_mock = create_autospec(_util.SearchList) + server_mock.channels = searchlist_mock + channel_mock = create_autospec(_channel.Channel) slackclient_mock.server.channels.find.return_value = channel_mock + rtmbot.slack_client = slackclient_mock # mock the plugin object to return a sample response - plugin_mock = Mock() + plugin_mock = create_autospec(Plugin) plugin_mock.do_output.return_value = [['C12345678', 'test message']] rtmbot.bot_plugins.append(plugin_mock) @@ -50,10 +58,18 @@ def test_output(): # test that the output matches the expected value channel_mock.send_message.assert_called_with('test message') - # test that unicode messages work as expected + # test that emoji messages work as expected channel_mock.reset_mock() plugin_mock.reset_mock() plugin_mock.do_output.return_value = [['C12345678', '🚀 testing']] rtmbot.output() channel_mock.send_message.assert_called_with('🚀 testing') + + # test that unicode messages work as expected + channel_mock.reset_mock() + plugin_mock.reset_mock() + plugin_mock.do_output.return_value = [['C12345678', 'ù hœø3ö']] + rtmbot.output() + + channel_mock.send_message.assert_called_with('ù hœø3ö') \ No newline at end of file