1# -*- coding: utf8 -*-
2###
3# Copyright (c) 2002-2005, Jeremiah Fincher
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9#   * Redistributions of source code must retain the above copyright notice,
10#     this list of conditions, and the following disclaimer.
11#   * Redistributions in binary form must reproduce the above copyright notice,
12#     this list of conditions, and the following disclaimer in the
13#     documentation and/or other materials provided with the distribution.
14#   * Neither the name of the author of this software nor the name of
15#     contributors to this software may be used to endorse or promote products
16#     derived from this software without specific prior written consent.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29###
30
31from supybot.test import *
32
33import supybot.conf as conf
34import supybot.utils as utils
35import supybot.ircmsgs as ircmsgs
36import supybot.utils.minisix as minisix
37import supybot.callbacks as callbacks
38
39tokenize = callbacks.tokenize
40
41
42class TokenizerTestCase(SupyTestCase):
43    def testEmpty(self):
44        self.assertEqual(tokenize(''), [])
45
46    def testNullCharacter(self):
47        self.assertEqual(tokenize(utils.str.dqrepr('\0')), ['\0'])
48
49    def testSingleDQInDQString(self):
50        self.assertEqual(tokenize('"\\""'), ['"'])
51
52    def testDQsWithBackslash(self):
53        self.assertEqual(tokenize('"\\\\"'), ["\\"])
54
55    def testDoubleQuotes(self):
56        self.assertEqual(tokenize('"\\"foo\\""'), ['"foo"'])
57
58    def testSingleWord(self):
59        self.assertEqual(tokenize('foo'), ['foo'])
60
61    def testMultipleSimpleWords(self):
62        words = 'one two three four five six seven eight'.split()
63        for i in range(len(words)):
64            self.assertEqual(tokenize(' '.join(words[:i])), words[:i])
65
66    def testSingleQuotesNotQuotes(self):
67        self.assertEqual(tokenize("it's"), ["it's"])
68
69    def testQuotedWords(self):
70        self.assertEqual(tokenize('"foo bar"'), ['foo bar'])
71        self.assertEqual(tokenize('""'), [''])
72        self.assertEqual(tokenize('foo "" bar'), ['foo', '', 'bar'])
73        self.assertEqual(tokenize('foo "bar baz" quux'),
74                         ['foo', 'bar baz', 'quux'])
75
76    _testUnicode = """
77def testUnicode(self):
78    self.assertEqual(tokenize(u'好'), [u'好'])
79    self.assertEqual(tokenize(u'"好"'), [u'好'])"""
80    if minisix.PY3:
81        _testUnicode = _testUnicode.replace("u'", "'")
82    exec(_testUnicode)
83
84    def testNesting(self):
85        self.assertEqual(tokenize('[]'), [[]])
86        self.assertEqual(tokenize('[foo]'), [['foo']])
87        self.assertEqual(tokenize('[ foo ]'), [['foo']])
88        self.assertEqual(tokenize('foo [bar]'), ['foo', ['bar']])
89        self.assertEqual(tokenize('foo bar [baz quux]'),
90                         ['foo', 'bar', ['baz', 'quux']])
91        try:
92            orig = conf.supybot.commands.nested()
93            conf.supybot.commands.nested.setValue(False)
94            self.assertEqual(tokenize('[]'), ['[]'])
95            self.assertEqual(tokenize('[foo]'), ['[foo]'])
96            self.assertEqual(tokenize('foo [bar]'), ['foo', '[bar]'])
97            self.assertEqual(tokenize('foo bar [baz quux]'),
98                             ['foo', 'bar', '[baz', 'quux]'])
99        finally:
100            conf.supybot.commands.nested.setValue(orig)
101
102    def testError(self):
103        self.assertRaises(SyntaxError, tokenize, '[foo') #]
104        self.assertRaises(SyntaxError, tokenize, '"foo') #"
105
106    def testPipe(self):
107        try:
108            conf.supybot.commands.nested.pipeSyntax.setValue(True)
109            self.assertRaises(SyntaxError, tokenize, '| foo')
110            self.assertRaises(SyntaxError, tokenize, 'foo ||bar')
111            self.assertRaises(SyntaxError, tokenize, 'bar |')
112            self.assertEqual(tokenize('foo|bar'), ['bar', ['foo']])
113            self.assertEqual(tokenize('foo | bar'), ['bar', ['foo']])
114            self.assertEqual(tokenize('foo | bar | baz'),
115                             ['baz', ['bar',['foo']]])
116            self.assertEqual(tokenize('foo bar | baz'),
117                             ['baz', ['foo', 'bar']])
118            self.assertEqual(tokenize('foo | bar baz'),
119                             ['bar', 'baz', ['foo']])
120            self.assertEqual(tokenize('foo bar | baz quux'),
121                             ['baz', 'quux', ['foo', 'bar']])
122        finally:
123            conf.supybot.commands.nested.pipeSyntax.setValue(False)
124            self.assertEqual(tokenize('foo|bar'), ['foo|bar'])
125            self.assertEqual(tokenize('foo | bar'), ['foo', '|', 'bar'])
126            self.assertEqual(tokenize('foo | bar | baz'),
127                             ['foo', '|', 'bar', '|', 'baz'])
128            self.assertEqual(tokenize('foo bar | baz'),
129                             ['foo', 'bar', '|', 'baz'])
130
131    def testQuoteConfiguration(self):
132        f = callbacks.tokenize
133        self.assertEqual(f('[foo]'), [['foo']])
134        self.assertEqual(f('"[foo]"'), ['[foo]'])
135        try:
136            original = conf.supybot.commands.quotes()
137            conf.supybot.commands.quotes.setValue('`')
138            self.assertEqual(f('[foo]'), [['foo']])
139            self.assertEqual(f('`[foo]`'), ['[foo]'])
140            conf.supybot.commands.quotes.setValue('\'')
141            self.assertEqual(f('[foo]'), [['foo']])
142            self.assertEqual(f('\'[foo]\''), ['[foo]'])
143            conf.supybot.commands.quotes.setValue('`\'')
144            self.assertEqual(f('[foo]'), [['foo']])
145            self.assertEqual(f('`[foo]`'), ['[foo]'])
146            self.assertEqual(f('[foo]'), [['foo']])
147            self.assertEqual(f('\'[foo]\''), ['[foo]'])
148        finally:
149            conf.supybot.commands.quotes.setValue(original)
150
151    def testBold(self):
152        s = '\x02foo\x02'
153        self.assertEqual(tokenize(s), [s])
154        s = s[:-1] + '\x0f'
155        self.assertEqual(tokenize(s), [s])
156
157    def testColor(self):
158        s = '\x032,3foo\x03'
159        self.assertEqual(tokenize(s), [s])
160        s = s[:-1] + '\x0f'
161        self.assertEqual(tokenize(s), [s])
162
163
164class FunctionsTestCase(SupyTestCase):
165    def testCanonicalName(self):
166        self.assertEqual('foo', callbacks.canonicalName('foo'))
167        self.assertEqual('foobar', callbacks.canonicalName('foo-bar'))
168        self.assertEqual('foobar', callbacks.canonicalName('foo_bar'))
169        self.assertEqual('foobar', callbacks.canonicalName('FOO-bar'))
170        self.assertEqual('foobar', callbacks.canonicalName('FOOBAR'))
171        self.assertEqual('foobar', callbacks.canonicalName('foo___bar'))
172        self.assertEqual('foobar', callbacks.canonicalName('_f_o_o-b_a_r'))
173        # The following seems to be a hack for the Karma plugin; I'm not
174        # entirely sure that it's completely necessary anymore.
175        self.assertEqual('foobar--', callbacks.canonicalName('foobar--'))
176
177    def testAddressed(self):
178        oldprefixchars = str(conf.supybot.reply.whenAddressedBy.chars)
179        nick = 'supybot'
180        conf.supybot.reply.whenAddressedBy.chars.set('~!@')
181        inChannel = ['~foo', '@foo', '!foo',
182                     '%s: foo' % nick, '%s foo' % nick,
183                     '%s: foo' % nick.capitalize(), '%s: foo' % nick.upper()]
184        inChannel = [ircmsgs.privmsg('#foo', s) for s in inChannel]
185        badmsg = ircmsgs.privmsg('#foo', '%s:foo' % nick)
186        self.failIf(callbacks.addressed(nick, badmsg))
187        badmsg = ircmsgs.privmsg('#foo', '%s^: foo' % nick)
188        self.failIf(callbacks.addressed(nick, badmsg))
189        for msg in inChannel:
190            self.assertEqual('foo', callbacks.addressed(nick, msg), msg)
191        msg = ircmsgs.privmsg(nick, 'foo')
192        self.assertEqual('foo', callbacks.addressed(nick, msg))
193        conf.supybot.reply.whenAddressedBy.chars.set(oldprefixchars)
194        msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick)
195        self.assertEqual('bar', callbacks.addressed(nick, msg))
196        msg = ircmsgs.privmsg('#foo', '%s: foo' % nick.upper())
197        self.assertEqual('foo', callbacks.addressed(nick, msg))
198        badmsg = ircmsgs.privmsg('#foo', '%s`: foo' % nick)
199        self.failIf(callbacks.addressed(nick, badmsg))
200
201    def testAddressedReplyWhenNotAddressed(self):
202        msg1 = ircmsgs.privmsg('#foo', '@bar')
203        msg2 = ircmsgs.privmsg('#foo', 'bar')
204        self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
205        self.assertEqual(callbacks.addressed('blah', msg2), '')
206        try:
207            original = conf.supybot.reply.whenNotAddressed()
208            conf.supybot.reply.whenNotAddressed.setValue(True)
209            # need to recreate the msg objects since the old ones have already
210            # been tagged
211            msg1 = ircmsgs.privmsg('#foo', '@bar')
212            msg2 = ircmsgs.privmsg('#foo', 'bar')
213            self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
214            self.assertEqual(callbacks.addressed('blah', msg2), 'bar')
215        finally:
216            conf.supybot.reply.whenNotAddressed.setValue(original)
217
218    def testAddressedWithMultipleNicks(self):
219        msg = ircmsgs.privmsg('#foo', 'bar: baz')
220        self.assertEqual(callbacks.addressed('bar', msg), 'baz')
221        # need to recreate the msg objects since the old ones have already
222        # been tagged
223        msg = ircmsgs.privmsg('#foo', 'bar: baz')
224        self.assertEqual(callbacks.addressed('biff', msg, nicks=['bar']),
225                         'baz')
226
227    def testAddressedWithNickAtEnd(self):
228        msg = ircmsgs.privmsg('#foo', 'baz, bar')
229        self.assertEqual(callbacks.addressed('bar', msg,
230                                             whenAddressedByNickAtEnd=True),
231                         'baz')
232
233    def testAddressedPrefixCharsTakePrecedenceOverNickAtEnd(self):
234        msg = ircmsgs.privmsg('#foo', '@echo foo')
235        self.assertEqual(callbacks.addressed('foo', msg,
236                                             whenAddressedByNickAtEnd=True,
237                                             prefixChars='@'),
238                         'echo foo')
239
240
241    def testReply(self):
242        prefix = 'foo!bar@baz'
243        channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
244        nonChannelMsg = ircmsgs.privmsg('supybot', 'bar baz', prefix=prefix)
245        self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
246                         callbacks.reply(channelMsg, 'foo', private=True))
247        self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
248                         callbacks.reply(nonChannelMsg, 'foo'))
249        self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
250                                         '%s: foo' % channelMsg.nick),
251                         callbacks.reply(channelMsg, 'foo'))
252        self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
253                                         'foo'),
254                         callbacks.reply(channelMsg, 'foo', prefixNick=False))
255        self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
256                         callbacks.reply(channelMsg, 'foo',
257                                         notice=True, private=True))
258
259    def testReplyTo(self):
260        prefix = 'foo!bar@baz'
261        msg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
262        self.assertEqual(callbacks.reply(msg, 'blah', to='blah'),
263                         ircmsgs.privmsg('#foo', 'blah: blah'))
264        self.assertEqual(callbacks.reply(msg, 'blah', to='blah', private=True),
265                         ircmsgs.notice('blah', 'blah'))
266
267    def testTokenize(self):
268        self.assertEqual(callbacks.tokenize(''), [])
269        self.assertEqual(callbacks.tokenize('foo'), ['foo'])
270        self.assertEqual(callbacks.tokenize('foo'), ['foo'])
271        self.assertEqual(callbacks.tokenize('bar [baz]'), ['bar', ['baz']])
272
273
274class AmbiguityTestCase(PluginTestCase):
275    plugins = ('Misc',) # Something so it doesn't complain.
276    class Foo(callbacks.Plugin):
277        def bar(self, irc, msg, args):
278            irc.reply('foo.bar')
279    class Bar(callbacks.Plugin):
280        def bar(self, irc, msg, args):
281            irc.reply('bar.bar')
282
283    def testAmbiguityWithCommandSameNameAsPlugin(self):
284        self.irc.addCallback(self.Foo(self.irc))
285        self.assertResponse('bar', 'foo.bar')
286        self.irc.addCallback(self.Bar(self.irc))
287        self.assertResponse('bar', 'bar.bar')
288
289class ProperStringificationOfReplyArgs(PluginTestCase):
290    plugins = ('Misc',) # Same as above.
291    class NonString(callbacks.Plugin):
292        def int(self, irc, msg, args):
293            irc.reply(1)
294    class ExpectsString(callbacks.Plugin):
295        def lower(self, irc, msg, args):
296            irc.reply(args[0].lower())
297
298    def test(self):
299        self.irc.addCallback(self.NonString(self.irc))
300        self.irc.addCallback(self.ExpectsString(self.irc))
301        self.assertResponse('expectsstring lower [nonstring int]', '1')
302
303## class PrivmsgTestCaseWithKarma(ChannelPluginTestCase):
304##     plugins = ('Utilities', 'Misc', 'Web', 'Karma', 'String')
305##     conf.allowEval = True
306##     timeout = 2
307##     def testSecondInvalidCommandRespondsWithThreadedInvalidCommands(self):
308##         try:
309##             orig = conf.supybot.plugins.Karma.response()
310##             conf.supybot.plugins.Karma.response.setValue(True)
311##             self.assertNotRegexp('echo [foo++] [foo++]', 'not a valid')
312##             _ = self.irc.takeMsg()
313##         finally:
314##             conf.supybot.plugins.Karma.response.setValue(orig)
315
316
317class PrivmsgTestCase(ChannelPluginTestCase):
318    plugins = ('Utilities', 'Misc', 'Web', 'String')
319    conf.allowEval = True
320    timeout = 2
321    def testEmptySquareBrackets(self):
322        self.assertError('echo []')
323
324##     def testHelpNoNameError(self):
325##         # This will raise a NameError if some dynamic scoping isn't working
326##         self.assertNotError('load Http')
327##         self.assertHelp('extension')
328
329    def testMaximumNestingDepth(self):
330        original = conf.supybot.commands.nested.maximum()
331        try:
332            conf.supybot.commands.nested.maximum.setValue(3)
333            self.assertResponse('echo foo', 'foo')
334            self.assertResponse('echo [echo foo]', 'foo')
335            self.assertResponse('echo [echo [echo foo]]', 'foo')
336            self.assertResponse('echo [echo [echo [echo foo]]]', 'foo')
337            self.assertError('echo [echo [echo [echo [echo foo]]]]')
338        finally:
339            conf.supybot.commands.nested.maximum.setValue(original)
340
341    def testSimpleReply(self):
342        self.assertResponse("eval irc.reply('foo')", 'foo')
343
344    def testSimpleReplyAction(self):
345        self.assertResponse("eval irc.reply('foo', action=True)",
346                            '\x01ACTION foo\x01')
347
348    def testReplyWithNickPrefix(self):
349        self.feedMsg('@len foo')
350        m = self.irc.takeMsg()
351        self.failUnless(m is not None, 'm: %r' % m)
352        self.failUnless(m.args[1].startswith(self.nick))
353        try:
354            original = conf.supybot.reply.withNickPrefix()
355            conf.supybot.reply.withNickPrefix.setValue(False)
356            self.feedMsg('@len foobar')
357            m = self.irc.takeMsg()
358            self.failUnless(m is not None)
359            self.failIf(m.args[1].startswith(self.nick))
360        finally:
361            conf.supybot.reply.withNickPrefix.setValue(original)
362
363    def testErrorPrivateKwarg(self):
364        try:
365            original = conf.supybot.reply.error.inPrivate()
366            conf.supybot.reply.error.inPrivate.setValue(False)
367            m = self.getMsg("eval irc.error('foo', private=True)")
368            self.failUnless(m, 'No message returned.')
369            self.failIf(ircutils.isChannel(m.args[0]))
370        finally:
371            conf.supybot.reply.error.inPrivate.setValue(original)
372
373    def testErrorNoArgumentIsArgumentError(self):
374        self.assertHelp('eval irc.error()')
375
376    def testErrorWithNotice(self):
377        try:
378            original = conf.supybot.reply.error.withNotice()
379            conf.supybot.reply.error.withNotice.setValue(True)
380            m = self.getMsg("eval irc.error('foo')")
381            self.failUnless(m, 'No message returned.')
382            self.failUnless(m.command == 'NOTICE')
383        finally:
384            conf.supybot.reply.error.withNotice.setValue(original)
385
386    def testErrorReplyPrivate(self):
387        try:
388            original = str(conf.supybot.reply.error.inPrivate)
389            conf.supybot.reply.error.inPrivate.set('False')
390            # If this doesn't raise an error, we've got a problem, so the next
391            # two assertions shouldn't run.  So we first check that what we
392            # expect to error actually does so we don't go on a wild goose
393            # chase because our command never errored in the first place :)
394            s = 're s/foo/bar baz' # will error; should be "re s/foo/bar/ baz"
395            self.assertError(s)
396            m = self.getMsg(s)
397            self.failUnless(ircutils.isChannel(m.args[0]))
398            conf.supybot.reply.error.inPrivate.set('True')
399            m = self.getMsg(s)
400            self.failIf(ircutils.isChannel(m.args[0]))
401        finally:
402            conf.supybot.reply.error.inPrivate.set(original)
403
404    # Now for stuff not based on the plugins.
405    class First(callbacks.Plugin):
406        def firstcmd(self, irc, msg, args):
407            """First"""
408            irc.reply('foo')
409
410    class Second(callbacks.Plugin):
411        def secondcmd(self, irc, msg, args):
412            """Second"""
413            irc.reply('bar')
414
415    class FirstRepeat(callbacks.Plugin):
416        def firstcmd(self, irc, msg, args):
417            """FirstRepeat"""
418            irc.reply('baz')
419
420    class Third(callbacks.Plugin):
421        def third(self, irc, msg, args):
422            """Third"""
423            irc.reply(' '.join(args))
424
425    def tearDown(self):
426        if hasattr(self.First, 'first'):
427            del self.First.first
428        if hasattr(self.Second, 'second'):
429            del self.Second.second
430        if hasattr(self.FirstRepeat, 'firstrepeat'):
431            del self.FirstRepeat.firstrepeat
432        ChannelPluginTestCase.tearDown(self)
433
434    def testDispatching(self):
435        self.irc.addCallback(self.First(self.irc))
436        self.irc.addCallback(self.Second(self.irc))
437        self.assertResponse('firstcmd', 'foo')
438        self.assertResponse('secondcmd', 'bar')
439        self.assertResponse('first firstcmd', 'foo')
440        self.assertResponse('second secondcmd', 'bar')
441        self.assertRegexp('first first firstcmd',
442                'there is no command named "first" in it')
443
444    def testAmbiguousError(self):
445        self.irc.addCallback(self.First(self.irc))
446        self.assertNotError('firstcmd')
447        self.irc.addCallback(self.FirstRepeat(self.irc))
448        self.assertError('firstcmd')
449        self.assertError('firstcmd [firstcmd]')
450        self.assertNotRegexp('firstcmd', '(foo.*baz|baz.*foo)')
451        self.assertResponse('first firstcmd', 'foo')
452        self.assertResponse('firstrepeat firstcmd', 'baz')
453
454    def testAmbiguousHelpError(self):
455        self.irc.addCallback(self.First(self.irc))
456        self.irc.addCallback(self.FirstRepeat(self.irc))
457        self.assertError('help first')
458
459    def testHelpDispatching(self):
460        self.irc.addCallback(self.First(self.irc))
461        self.assertHelp('help firstcmd')
462        self.assertHelp('help first firstcmd')
463        self.irc.addCallback(self.FirstRepeat(self.irc))
464        self.assertError('help firstcmd')
465        self.assertRegexp('help first firstcmd', 'First', 0) # no re.I flag.
466        self.assertRegexp('help firstrepeat firstcmd', 'FirstRepeat', 0)
467
468    class TwoRepliesFirstAction(callbacks.Plugin):
469        def testactionreply(self, irc, msg, args):
470            irc.reply('foo', action=True)
471            irc.reply('bar') # We're going to check that this isn't an action.
472
473    def testNotActionSecondReply(self):
474        self.irc.addCallback(self.TwoRepliesFirstAction(self.irc))
475        self.assertAction('testactionreply', 'foo')
476        m = self.getMsg(' ')
477        self.failIf(m.args[1].startswith('\x01ACTION'))
478
479    def testEmptyNest(self):
480        try:
481            conf.supybot.reply.whenNotCommand.set('True')
482            self.assertError('echo []')
483            conf.supybot.reply.whenNotCommand.set('False')
484            self.assertResponse('echo []', '[]')
485        finally:
486            conf.supybot.reply.whenNotCommand.set('False')
487
488    def testDispatcherHelp(self):
489        self.assertNotRegexp('help first', r'\(dispatcher')
490        self.assertNotRegexp('help first', r'%s')
491
492    def testDefaultCommand(self):
493        self.irc.addCallback(self.First(self.irc))
494        self.irc.addCallback(self.Third(self.irc))
495        self.assertError('first blah')
496        self.assertResponse('third foo bar baz', 'foo bar baz')
497
498    def testSyntaxErrorNotEscaping(self):
499        self.assertError('load [foo')
500        self.assertError('load foo]')
501
502    def testNoEscapingAttributeErrorFromTokenizeWithFirstElementList(self):
503        self.assertError('[plugin list] list')
504
505    class InvalidCommand(callbacks.Plugin):
506        def invalidCommand(self, irc, msg, tokens):
507            irc.reply('foo')
508
509    def testInvalidCommandOneReplyOnly(self):
510        try:
511            original = str(conf.supybot.reply.whenNotCommand)
512            conf.supybot.reply.whenNotCommand.set('True')
513            self.assertRegexp('asdfjkl', 'not a valid command')
514            self.irc.addCallback(self.InvalidCommand(self.irc))
515            self.assertResponse('asdfjkl', 'foo')
516            self.assertNoResponse(' ', 2)
517        finally:
518            conf.supybot.reply.whenNotCommand.set(original)
519
520    class BadInvalidCommand(callbacks.Plugin):
521        def invalidCommand(self, irc, msg, tokens):
522            s = 'This shouldn\'t keep Misc.invalidCommand from being called'
523            raise Exception(s)
524
525    def testBadInvalidCommandDoesNotKillAll(self):
526        try:
527            original = str(conf.supybot.reply.whenNotCommand)
528            conf.supybot.reply.whenNotCommand.set('True')
529            self.irc.addCallback(self.BadInvalidCommand(self.irc))
530            self.assertRegexp('asdfjkl', 'not a valid command',
531                    expectException=True)
532        finally:
533            conf.supybot.reply.whenNotCommand.set(original)
534
535
536class PluginRegexpTestCase(PluginTestCase):
537    plugins = ()
538    class PCAR(callbacks.PluginRegexp):
539        def test(self, irc, msg, args):
540            "<foo>"
541            raise callbacks.ArgumentError
542    def testNoEscapingArgumentError(self):
543        self.irc.addCallback(self.PCAR(self.irc))
544        self.assertResponse('test', 'test <foo>')
545
546class RichReplyMethodsTestCase(PluginTestCase):
547    plugins = ('Config',)
548    class NoCapability(callbacks.Plugin):
549        def error(self, irc, msg, args):
550            irc.errorNoCapability('admin')
551    def testErrorNoCapability(self):
552        self.irc.addCallback(self.NoCapability(self.irc))
553        self.assertRegexp('error', 'You don\'t have the admin capability')
554        self.assertNotError('config capabilities.private admin')
555        self.assertRegexp('error', 'Error: You\'re missing some capability')
556        self.assertNotError('config capabilities.private ""')
557
558
559class SourceNestedPluginTestCase(PluginTestCase):
560    plugins = ('Utilities',)
561    class E(callbacks.Plugin):
562        def f(self, irc, msg, args):
563            """takes no arguments
564
565            F
566            """
567            irc.reply('f')
568
569        def empty(self, irc, msg, args):
570            pass
571
572        class g(callbacks.Commands):
573            def h(self, irc, msg, args):
574                """takes no arguments
575
576                H
577                """
578                irc.reply('h')
579
580            class i(callbacks.Commands):
581                def j(self, irc, msg, args):
582                    """takes no arguments
583
584                    J
585                    """
586                    irc.reply('j')
587
588        class same(callbacks.Commands):
589            def same(self, irc, msg, args):
590                """takes no arguments
591
592                same
593                """
594                irc.reply('same')
595
596    def test(self):
597        cb = self.E(self.irc)
598        self.irc.addCallback(cb)
599        self.assertEqual(cb.getCommand(['f']), ['f'])
600        self.assertEqual(cb.getCommand(['same']), ['same'])
601        self.assertEqual(cb.getCommand(['e', 'f']), ['e', 'f'])
602        self.assertEqual(cb.getCommand(['e', 'g', 'h']), ['e', 'g', 'h'])
603        self.assertEqual(cb.getCommand(['e', 'g', 'i', 'j']),
604                                       ['e', 'g', 'i', 'j'])
605        self.assertResponse('e f', 'f')
606        self.assertResponse('e same', 'same')
607        self.assertResponse('e g h', 'h')
608        self.assertResponse('e g i j', 'j')
609        self.assertHelp('help f')
610        self.assertHelp('help empty')
611        self.assertHelp('help same')
612        self.assertHelp('help e g h')
613        self.assertHelp('help e g i j')
614        self.assertRegexp('list e', 'f, g h, g i j, and same')
615
616    def testCommandSameNameAsNestedPlugin(self):
617        cb = self.E(self.irc)
618        self.irc.addCallback(cb)
619        self.assertResponse('e f', 'f') # Just to make sure it was loaded.
620        self.assertEqual(cb.getCommand(['e', 'same']), ['e', 'same'])
621        self.assertResponse('e same', 'same')
622
623
624class WithPrivateNoticeTestCase(ChannelPluginTestCase):
625    plugins = ('Utilities',)
626    class WithPrivateNotice(callbacks.Plugin):
627        def normal(self, irc, msg, args):
628            irc.reply('should be with private notice')
629        def explicit(self, irc, msg, args):
630            irc.reply('should not be with private notice',
631                      private=False, notice=False)
632        def implicit(self, irc, msg, args):
633            irc.reply('should be with notice due to private',
634                      private=True)
635    def test(self):
636        self.irc.addCallback(self.WithPrivateNotice(self.irc))
637        # Check normal behavior.
638        m = self.assertNotError('normal')
639        self.failIf(m.command == 'NOTICE')
640        self.failUnless(ircutils.isChannel(m.args[0]))
641        m = self.assertNotError('explicit')
642        self.failIf(m.command == 'NOTICE')
643        self.failUnless(ircutils.isChannel(m.args[0]))
644        # Check abnormal behavior.
645        originalInPrivate = conf.supybot.reply.inPrivate()
646        originalWithNotice = conf.supybot.reply.withNotice()
647        try:
648            conf.supybot.reply.inPrivate.setValue(True)
649            conf.supybot.reply.withNotice.setValue(True)
650            m = self.assertNotError('normal')
651            self.failUnless(m.command == 'NOTICE')
652            self.failIf(ircutils.isChannel(m.args[0]))
653            m = self.assertNotError('explicit')
654            self.failIf(m.command == 'NOTICE')
655            self.failUnless(ircutils.isChannel(m.args[0]))
656        finally:
657            conf.supybot.reply.inPrivate.setValue(originalInPrivate)
658            conf.supybot.reply.withNotice.setValue(originalWithNotice)
659        orig = conf.supybot.reply.withNoticeWhenPrivate()
660        try:
661            conf.supybot.reply.withNoticeWhenPrivate.setValue(True)
662            m = self.assertNotError('implicit')
663            self.failUnless(m.command == 'NOTICE')
664            self.failIf(ircutils.isChannel(m.args[0]))
665            m = self.assertNotError('normal')
666            self.failIf(m.command == 'NOTICE')
667            self.failUnless(ircutils.isChannel(m.args[0]))
668        finally:
669            conf.supybot.reply.withNoticeWhenPrivate.setValue(orig)
670
671    def testWithNoticeWhenPrivateNotChannel(self):
672        original = conf.supybot.reply.withNoticeWhenPrivate()
673        try:
674            conf.supybot.reply.withNoticeWhenPrivate.setValue(True)
675            m = self.assertNotError("eval irc.reply('y',to='x',private=True)")
676            self.failUnless(m.command == 'NOTICE')
677            m = self.getMsg(' ')
678            m = self.assertNotError("eval irc.reply('y',to='#x',private=True)")
679            self.failIf(m.command == 'NOTICE')
680        finally:
681            conf.supybot.reply.withNoticeWhenPrivate.setValue(original)
682
683class ProxyTestCase(SupyTestCase):
684    def testHashing(self):
685        msg = ircmsgs.ping('0')
686        irc = irclib.Irc('test')
687        proxy = callbacks.SimpleProxy(irc, msg)
688        # First one way...
689        self.failIf(proxy != irc)
690        self.failUnless(proxy == irc)
691        self.assertEqual(hash(proxy), hash(irc))
692        # Then the other!
693        self.failIf(irc != proxy)
694        self.failUnless(irc == proxy)
695        self.assertEqual(hash(irc), hash(proxy))
696
697        # And now dictionaries...
698        d = {}
699        d[irc] = 'foo'
700        self.failUnless(len(d) == 1)
701        self.failUnless(d[irc] == 'foo')
702        self.failUnless(d[proxy] == 'foo')
703        d[proxy] = 'bar'
704        self.failUnless(len(d) == 1)
705        self.failUnless(d[irc] == 'bar')
706        self.failUnless(d[proxy] == 'bar')
707        d[irc] = 'foo'
708        self.failUnless(len(d) == 1)
709        self.failUnless(d[irc] == 'foo')
710        self.failUnless(d[proxy] == 'foo')
711
712
713
714
715# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
716