Skip to content

Commit

Permalink
Added tests
Browse files Browse the repository at this point in the history
Fixed some stuff
Made stuff more explicit
  • Loading branch information
ojii committed Dec 26, 2011
1 parent 4539a16 commit e4e5160
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .coveragerc
@@ -0,0 +1,6 @@
[run]
branch = True
source = ircbotframework

[report]
precision = 2
9 changes: 9 additions & 0 deletions .gitignore
@@ -0,0 +1,9 @@
*.pyc
.*
!.gitignore
!.coveragerc
/htmlcov/
/ircbotframework.egg-info/
/env/
/dist/
/build/
4 changes: 2 additions & 2 deletions ircbotframework/bot.py
Expand Up @@ -187,7 +187,7 @@ def handle_message(self, message, channel, user):
for plugin in self.plugins:
plugin.handle_message(message, channel, user)
if command:
plugin.handle_command(command, rest, channel, user)
plugin._handle_command(command, rest, channel, user)

def handle_joined(self, channel):
"""
Expand All @@ -201,7 +201,7 @@ def handle_http(self, request):
Handles a http request by calling each plugins handle_http method
"""
for plugin in self.plugins:
plugin.handle_http(request)
plugin._handle_http(request)


class IRCBotFactory(protocol.ClientFactory):
Expand Down
3 changes: 2 additions & 1 deletion ircbotframework/conf.py
Expand Up @@ -17,6 +17,7 @@ def from_configuration(cls, conf, key, **defaults):
defaults.update({k[len(fullkey):]:v for k,v in conf.items() if k.startswith(fullkey)})
conf = cls(defaults)
conf.chain = [key]
return conf

def get_sub_conf(self, key, **defaults):
conf = Configuration.from_configuration(self, key, **defaults)
Expand All @@ -27,7 +28,7 @@ def get_sub_conf(self, key, **defaults):
def ensure(self, *keys):
chain = getattr(self, 'chain', [])
if chain:
chainstr = '_'.join(chain + [''])
chainstr = '_'.join(chain)
translate = lambda key: '%s_%s' % (chainstr, key)
else:
translate = lambda key: key
Expand Down
8 changes: 4 additions & 4 deletions ircbotframework/main.py
Expand Up @@ -10,7 +10,7 @@
import os
import sys

def run(conf):
def run(conf): # pragma: no cover
conf.ensure('NETWORK', 'PORT', 'CHANNEL', 'NICKNAME', 'COMMAND_PREFIX',
'WEBHOOKS', 'PLUGINS')
if conf['WEBHOOKS']:
Expand All @@ -23,7 +23,7 @@ def run(conf):
pass
reactor.run()

def run_with_settings_module(module):
def run_with_settings_module(module): # pragma: no cover
conf = Configuration.from_module(module,
WEBHOOKS=False,
PLUGINS=[],
Expand All @@ -32,7 +32,7 @@ def run_with_settings_module(module):
)
run(conf)

def main():
def main(): # pragma: no cover
sys.path.insert(0, os.getcwd())
parser = argparse.ArgumentParser()
parser.add_argument('settings')
Expand All @@ -43,5 +43,5 @@ def main():
module = import_module(args.settings)
run_with_settings_module(module)

if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
main()
76 changes: 60 additions & 16 deletions ircbotframework/plugin.py
@@ -1,28 +1,78 @@
# -*- coding: utf-8 -*-
from ircbotframework.utils import get_plugin_conf_key

class CommandsRegistry(dict):
def __call__(self, name):
def decorator(meth):
self[name] = meth
return decorator


class RoutesRegistry(dict):
def __call__(self, route):
def decorator(meth):
self[route] = meth
return decorator


class BasePlugin(object):
commands = CommandsRegistry()
routes = RoutesRegistry()
conf_key = None
default_confs = None
required_confs = None

def __init__(self, protocol, conf):
self.protocol = protocol
self.conf = conf
conf_key = self.conf_key or get_plugin_conf_key(self.__class__.__name__)
defaults = self.default_confs or {}
required = self.required_confs or ()
self.plugin_conf = self.conf.get_sub_conf(conf_key, **defaults)
self.plugin_conf.ensure(*required)
self.post_init()

# Internal

def _handle_command(self, command, rest, channel, user):
"""
INTERNAL
"""
handler = self.commands.get(command, None)
if handler is not None:
handler(self, rest, channel, user)

def _handle_http(self, request):
"""
INTERNAL
"""
for route, handler in self.routes.items():
match = route.match(request.path)
if match:
kwargs = match.groupdict()
if kwargs:
args = ()
else:
args = match.groups()
handler(self, request, *args, **kwargs)

# Utility

def message_channel(self, message):
"""
Convenience function to message the default channel.
"""
self.protocol.msg(self.protocol.factory.conf['CHANNEL'], message)

# API

def handle_command(self, command, rest, channel, user):
def post_init(self):
"""
Semi-internal API.
You should implement a command_xyz method for all your commands,
replacing xyz with your command name.
Can be overwritten by subclasses if they want to do special stuff after
init.
"""
handler = getattr(self, 'command_%s' % command, None)
if callable(handler):
handler(rest, channel, user)

pass

def handle_message(self, message, channel, user):
"""
Handle a single message sent to a channel by a user
Expand All @@ -33,10 +83,4 @@ def handle_joined(self, channel):
"""
After the channel was joined
"""
pass

def handle_http(self, request):
"""
Handle a HTTP request to the webhook
"""
pass
pass
3 changes: 3 additions & 0 deletions ircbotframework/utils.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from importlib import import_module
import re

def load_object(import_path):
"""
Expand All @@ -21,3 +22,5 @@ def load_object(import_path):
module_name, object_name = import_path.rsplit('.', 1)
module = import_module(module_name)
return getattr(module, object_name)

get_plugin_conf_key = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', class_name).upper().strip('_')
File renamed without changes.
19 changes: 15 additions & 4 deletions test_plugins.py
Expand Up @@ -2,13 +2,24 @@
"""
Some simple test plugins for me to check if stuff works or not
"""
from ircbotframework.plugin import BasePlugin
from ircbotframework.plugin import BasePlugin, CommandsRegistry, RoutesRegistry
import re


class EchoPlugin(BasePlugin):
def handle_message(self, user, channel, message):
channel.msg(message)
def handle_message(self, message, channel, user):
channel.msg("%s said %r in %r" % (user, message, channel))

class WebhookPlugin(BasePlugin):
def handle_http(self, request):
routes = RoutesRegistry()

@routes(re.compile('^/$'))
def root(self, request):
self.message_channel("HTTP %r: %r" % (request.method, request.path))

class CommandPlugin(BasePlugin):
commands = CommandsRegistry()

@commands('test')
def test_command(self, rest, channel, user):
channel.msg("Got test command: %r" % rest)
1 change: 1 addition & 0 deletions test_settings.py
Expand Up @@ -12,4 +12,5 @@
PLUGINS = [
'test_plugins.EchoPlugin',
'test_plugins.WebhookPlugin',
'test_plugins.CommandPlugin',
]
109 changes: 109 additions & 0 deletions tests.py
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
from ircbotframework.conf import Configuration, ImproperlyConfigured
from ircbotframework.http import Webhook
from ircbotframework.main import run_with_settings_module
from ircbotframework.plugin import BasePlugin, RoutesRegistry
from ircbotframework.utils import load_object, get_plugin_conf_key
from twisted.internet import reactor
from twisted.trial import unittest as twistedtest
from twisted.web.client import getPage
from twisted.web.server import Site
import re
import unittest


class ConfTests(unittest.TestCase):
def test_simple_conf(self):
conf = Configuration({'KEY': 'value', 'SUB_KEY': 'sub value'})
self.assertEqual(conf['KEY'], 'value')
self.assertEqual(conf['SUB_KEY'], 'sub value')

def test_subconf(self):
conf = Configuration({'KEY': 'value', 'SUB_KEY': 'sub value'})
subconf = conf.get_sub_conf('SUB')
self.assertEqual(subconf['KEY'], 'sub value')

def test_sub_sub_conf(self):
conf = Configuration({'KEY': 'value', 'SUB_KEY': 'sub value', 'SUB_SUB_KEY': 'sub sub value'})
subsubconf = conf.get_sub_conf('SUB').get_sub_conf('SUB')
self.assertEqual(subsubconf['KEY'], 'sub sub value')

def test_ensure_simple(self):
conf = Configuration({'KEY': 'value'})
self.assertEqual(conf.ensure('KEY'), None)

def test_ensure_fails(self):
conf = Configuration()
with self.assertRaises(ImproperlyConfigured):
conf.ensure('KEY')

def test_ensure_fails_chaind(self):
conf = Configuration({'KEY': 'value', 'SUB_KEY': 'sub value'})
subconf = conf.get_sub_conf('SUB')
with self.assertRaisesRegexp(ImproperlyConfigured, "SUB_SUB_KEY"):
subconf.ensure('SUB_KEY')

def test_from_module(self):
mock_module = type('Module', (object,), {'KEY': 'value'})
conf = Configuration.from_module(mock_module)
self.assertEqual(conf['KEY'], 'value')


class UtilsTests(unittest.TestCase):
def test_load_object(self):
baseplugin = load_object('ircbotframework.plugin.BasePlugin')
self.assertTrue(baseplugin is BasePlugin)

def test_load_object_fails(self):
with self.assertRaises(TypeError):
load_object('fail')

def test_get_plugin_conf_key(self):
self.assertEqual(get_plugin_conf_key('Test'), 'TEST')
self.assertEqual(get_plugin_conf_key('TestTwo'), 'TEST_TWO')


class WebhookTests(twistedtest.TestCase):
def setUp(self):
self.irc_mock = type('MockFactory', (object,), {'protocol_instance': None, 'unhandled_requests': []})
self.webhook_response = 'test'
webhook = Webhook(self.irc_mock, Configuration({'WEBHOOK_RESPONSE': self.webhook_response}))
factory = Site(webhook)
self.port = reactor.listenTCP(0, factory, interface="127.0.0.1")
self.portnum = self.port.getHost().port

def tearDown(self):
port, self.port = self.port, None
return port.stopListening()

def test_simple(self):
deferred = getPage('http://127.0.0.1:%s/' % self.portnum)
def do_test(response):
self.assertEqual(response, self.webhook_response)
deferred.addCallback(do_test)
return deferred

def test_irc_already_connected(self):
class PluginMock(BasePlugin):
routes = RoutesRegistry()
def __init__(self):
self.requests = []

@routes(re.compile(r'.*'))
def handle(self, request):
self.requests.append(request)
mock_plugin = PluginMock()
class MockProtocol(object):
plugins = [mock_plugin]
def handle_http(self, request):
mock_plugin._handle_http(request)
self.irc_mock.protocol_instance = MockProtocol()
deferred = getPage('http://127.0.0.1:%s/' % self.portnum)
def do_test(response):
self.assertEqual(len(mock_plugin.requests), 1)
deferred.addCallback(do_test)
return deferred


if __name__ == '__main__':
unittest.main()

0 comments on commit e4e5160

Please sign in to comment.