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