diff --git a/classes.py b/classes.py index d8ba00a..8f24742 100644 --- a/classes.py +++ b/classes.py @@ -311,29 +311,6 @@ class IrcChannel(): ### FakeIRC classes, used for test cases -global testconf -testconf = {'bot': - { - 'nick': 'PyLink', - 'user': 'pylink', - 'realname': 'PyLink Service Client', - 'loglevel': 'DEBUG', - }, - 'servers': - {'unittest': - { - 'ip': '0.0.0.0', - 'port': 7000, - 'recvpass': "abcd", - 'sendpass': "abcd", - 'protocol': "null", - 'hostname': "pylink.unittest", - 'sid': "9PY", - 'channels': ["#pylink"], - }, - }, - } - class FakeIRC(Irc): def connect(self): self.messages = [] @@ -382,11 +359,16 @@ class FakeIRC(Irc): @staticmethod def dummyhook(irc, source, command, parsed_args): - """Dummy function to bind to hooks.""" + """Dummy function to bind to hooks. This is what allows takeHooks() to work.""" irc.hookmsgs.append(parsed_args) class FakeProto(): """Dummy protocol module for testing purposes.""" + def __init__(self): + self.hook_map = {} + self.casemapping = 'rfc1459' + self.__name__ = 'FakeProto' + @staticmethod def handle_events(irc, data): pass diff --git a/conf.py b/conf.py index 8a102b5..f1e6bee 100644 --- a/conf.py +++ b/conf.py @@ -1,5 +1,8 @@ import yaml import sys +from collections import defaultdict + +import world global confname try: @@ -14,6 +17,40 @@ except IndexError: confname = 'pylink' fname = 'config.yml' +global testconf +testconf = {'bot': + { + 'nick': 'PyLink', + 'user': 'pylink', + 'realname': 'PyLink Service Client', + 'loglevel': 'DEBUG', + 'serverdesc': 'PyLink unit tests' + }, + 'servers': + # Wildcard defaultdict! This means that + # any network name you try will work and return + # this basic template: + defaultdict(lambda: { + 'ip': '0.0.0.0', + 'port': 7000, + 'recvpass': "abcd", + 'sendpass': "chucknorris", + 'protocol': "null", + 'hostname': "pylink.unittest", + 'sid': "9PY", + 'channels': ["#pylink"], + 'maxnicklen': 20 + }) + } + with open(fname, 'r') as f: global conf - conf = yaml.load(f) + try: + conf = yaml.load(f) + except Exception as e: + if world.testing: + conf = testconf + confname = 'testconf' + else: + print('ERROR: Failed to load config from %r: %s: %s' % (fname, type(e).__name__, e)) + sys.exit(4) diff --git a/main.py b/main.py index 7719fea..5b92a4e 100755 --- a/main.py +++ b/main.py @@ -9,8 +9,10 @@ import conf import classes import utils import coreplugin +import world if __name__ == '__main__': + world.testing = False log.info('PyLink starting...') if conf.conf['login']['password'] == 'changeme': log.critical("You have not set the login details correctly! Exiting...") diff --git a/tests/test_relay.py b/tests/test_relay.py index 07240b0..0f82299 100644 --- a/tests/test_relay.py +++ b/tests/test_relay.py @@ -7,34 +7,41 @@ import unittest import utils import classes import relay +import conf def dummyf(): pass class TestRelay(unittest.TestCase): def setUp(self): - self.irc = classes.FakeIRC('unittest', classes.FakeProto(), classes.testconf) + self.irc = classes.FakeIRC('unittest', classes.FakeProto(), conf.testconf) self.irc.maxnicklen = 20 - self.irc.proto.__name__ = "test" - self.f = relay.normalizeNick + self.f = lambda nick: relay.normalizeNick(self.irc, 'unittest', nick) + # Fake our protocol name to something that supports slashes in nicks. + # relay uses a whitelist for this to prevent accidentally introducing + # bad nicks: + self.irc.proto.__name__ = "inspircd" def testNormalizeNick(self): # Second argument simply states the suffix. - self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld/unittest') - self.assertEqual(self.f(self.irc, 'unittest', 'ObnoxiouslyLongNick'), 'Obnoxiously/unittest') - self.assertEqual(self.f(self.irc, 'unittest', '10XAAAAAA'), '_10XAAAAAA/unittest') + self.assertEqual(self.f('helloworld'), 'helloworld/unittest') + self.assertEqual(self.f('ObnoxiouslyLongNick'), 'Obnoxiously/unittest') + self.assertEqual(self.f('10XAAAAAA'), '_10XAAAAAA/unittest') def testNormalizeNickConflict(self): - self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld/unittest') + self.assertEqual(self.f('helloworld'), 'helloworld/unittest') self.irc.users['10XAAAAAA'] = classes.IrcUser('helloworld/unittest', 1234, '10XAAAAAA') # Increase amount of /'s by one - self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld//unittest') + self.assertEqual(self.f('helloworld'), 'helloworld//unittest') self.irc.users['10XAAAAAB'] = classes.IrcUser('helloworld//unittest', 1234, '10XAAAAAB') # Cut off the nick, not the suffix if the result is too long. - self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworl///unittest') + self.assertEqual(self.f('helloworld'), 'helloworl///unittest') def testNormalizeNickRemovesSlashes(self): self.irc.proto.__name__ = "charybdis" - self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld|unittest') - self.assertEqual(self.f(self.irc, 'unittest', 'abcde/eJanus'), 'abcde|eJanu|unittest') - self.assertEqual(self.f(self.irc, 'unittest', 'ObnoxiouslyLongNick'), 'Obnoxiously|unittest') + try: + self.assertEqual(self.f('helloworld'), 'helloworld|unittest') + self.assertEqual(self.f('abcde/eJanus'), 'abcde|eJanu|unittest') + self.assertEqual(self.f('ObnoxiouslyLongNick'), 'Obnoxiously|unittest') + finally: + self.irc.proto.__name__ = "inspircd" diff --git a/tests/test_utils.py b/tests/test_utils.py index 36c178f..2a6f904 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,11 +5,16 @@ import unittest import itertools import utils +import classes +import conf def dummyf(): pass class TestUtils(unittest.TestCase): + def setUp(self): + self.irc = classes.FakeIRC('fakeirc', classes.FakeProto(), conf.testconf) + def testTS6UIDGenerator(self): uidgen = utils.TS6UIDGenerator('9PY') self.assertEqual(uidgen.next_uid(), '9PYAAAAAA') @@ -96,5 +101,19 @@ class TestUtils(unittest.TestCase): ('+b', '*!*@*.badisp.net')]) self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net') + @unittest.skip('Wait, we need to work out the kinks first! (reversing changes of modes with arguments)') + def testReverseModes(self): + f = lambda x: utils.reverseModes(self.irc, '#test', x) + # Strings. + self.assertEqual(f("+nt-lk"), "-nt+lk") + self.assertEqual(f("nt-k"), "-nt+k") + # Lists. + self.assertEqual(f([('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')]), + [('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')]) + # Sets. + self.assertEqual(f({('s', None), ('+o', 'whoever')}), {('-s', None), ('-o', 'whoever')}) + # Combining modes with an initial + and those without + self.assertEqual(f({('s', None), ('+n', None)}), {('-s', None), ('-n', None)}) + if __name__ == '__main__': unittest.main() diff --git a/utils.py b/utils.py index 697eab7..f3f1574 100644 --- a/utils.py +++ b/utils.py @@ -165,7 +165,7 @@ def isServerName(s): return _isASCII(s) and '.' in s and not s.startswith('.') def parseModes(irc, target, args): - """Parses a mode string into a list of (mode, argument) tuples. + """Parses a modestring list into a list of (mode, argument) tuples. ['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')] """ # http://www.irc.org/tech_docs/005.html @@ -337,6 +337,34 @@ def joinModes(modes): modelist += ' %s' % ' '.join(args) return modelist +def reverseModes(irc, target, modes): + """ + + Reverses/Inverts the mode string or mode list given. + + "+nt-lk" => "-nt+lk" + "nt-k" => "-nt+k" + [('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')] => + [('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')] + [('s', None), ('+n', None)] => [('-s', None), ('-n', None)] + """ + origtype = type(modes) + # Operate on joined modestrings only; it's easier. + if origtype != str: + modes = joinModes(modes) + # Swap the +'s and -'s by replacing one with a dummy character, and then changing it back. + assert '\x00' not in modes, 'NUL cannot be in the mode list (it is a reserved character)!' + if not modes.startswith(('+', '-')): + modes = '+' + modes + newmodes = modes.replace('+', '\x00') + newmodes = newmodes.replace('-', '+') + newmodes = newmodes.replace('\x00', '-') + if origtype != str: + # If the original query isn't a string, send back the parseModes() output. + return parseModes(irc, target, newmodes.split(" ")) + else: + return newmodes + def isInternalClient(irc, numeric): """ diff --git a/world.py b/world.py new file mode 100644 index 0000000..e4450dc --- /dev/null +++ b/world.py @@ -0,0 +1,7 @@ +# world.py: global state variables go here + +# Global variable to indicate whether we're being ran directly, or imported +# for a testcase. +testing = True + +