diff --git a/.gitignore b/.gitignore index 31b0ee3..51fedb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,9 @@ -libs -*__pycache__* -docs/_build +__pycache__ .DS_Store -botenv -dist -build -pyircbot.egg-info -dev -docs/builder/build.sh -examples/config.test.json -osxenv + +/.cache +/docs/_build +/testenv +/dist +/build +/pyircbot.egg-info diff --git a/examples/data/data/ASCII/test.txt b/examples/data/data/ASCII/test.txt new file mode 100644 index 0000000..a042389 --- /dev/null +++ b/examples/data/data/ASCII/test.txt @@ -0,0 +1 @@ +hello world! diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..4bae34c --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,40 @@ +appdirs==1.4.3 +certifi==2017.4.17 +chardet==3.0.4 +cheroot==5.9.1 +CherryPy==12.0.1 +decorator==4.0.11 +idna==2.5 +ipdb==0.10.3 +ipython==6.0.0 +ipython-genutils==0.2.0 +jaraco.classes==1.4.3 +jedi==0.10.2 +mock==2.0.0 +msgbus==0.0.1 +packaging==16.8 +pbr==3.1.1 +pexpect==4.2.1 +pickleshare==0.7.4 +portend==2.2 +praw==5.0.1 +prawcore==0.11.0 +prompt-toolkit==1.0.14 +ptyprocess==0.5.1 +py==1.5.2 +Pygments==2.2.0 +PyJWT==1.5.3 +pyparsing==2.2.0 +PySocks==1.6.7 +pytest==3.2.5 +pytz==2017.3 +pyzmq==16.0.2 +requests==2.18.1 +simplegeneric==0.8.1 +six==1.11.0 +tempora==1.9 +traitlets==4.3.2 +twilio==6.9.0 +update-checker==0.16 +urllib3==1.21.1 +wcwidth==0.1.7 diff --git a/run-example.sh b/run-example.sh index 4ade652..82efc8b 100755 --- a/run-example.sh +++ b/run-example.sh @@ -1,4 +1,4 @@ -#!/bin/bash -x +#!/bin/bash CONFPATH=${1:-examples/config.json} diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..f7d45a2 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export PYTHONUNBUFFERED=1 +export PYTHONPATH=. + +py.test -s tests/ diff --git a/tests/lib.py b/tests/lib.py new file mode 100644 index 0000000..e932919 --- /dev/null +++ b/tests/lib.py @@ -0,0 +1,41 @@ +import os +import sys +import pytest +from pyircbot.pyircbot import PrimitiveBot +from pyircbot.irccore import IRCEvent, UserPrefix +from unittest.mock import MagicMock + + +sys.path.append(os.path.join(os.path.dirname(__file__), "../pyircbot/modules/")) + + +class FakeBaseBot(PrimitiveBot): + """ + Class that simulates a bot base class. You need to add mocks for any methods you expect called, beyond privmsg. + """ + + def __init__(self, config): + super().__init__(config) + self.act_PRIVMSG = MagicMock() + + def feed_line(self, trailing, cmd="PRIVMSG", args=["#test"], sender=("chatter", "root", "cia.gov")): + """ + Feed a message into the bot. + """ + msg = IRCEvent(cmd, + args, + UserPrefix(*sender), + trailing) + + for module_name, module in self.moduleInstances.items():# TODO dedupe this block across the various base classes + for hook in module.irchooks: + validation = hook.validator(msg, self) + if validation: + hook.method(msg, validation) + + +@pytest.fixture +def fakebot(): + bot = FakeBaseBot({"bot": {"datadir": "./examples/data/"}, + "module_configs": {}}) + return bot diff --git a/tests/modules/calc.py b/tests/modules/calc.py new file mode 100644 index 0000000..1813f6a --- /dev/null +++ b/tests/modules/calc.py @@ -0,0 +1,30 @@ +import pytest +from pyircbot.modules.Calc import Calc +from pyircbot.pyircbot import ModuleLoader + + +class FakeBaseBot(ModuleLoader): + + " IRC methods " + def act_PRIVMSG(self, towho, message): + """Use the `/msg` command + + :param towho: the target #channel or user's name + :type towho: str + :param message: the message to send + :type message: str""" + # self.sendRaw("PRIVMSG %s :%s" % (towho, message)) + print("act_PRIVMSG(towho={}, message={})".format(towho, message)) + + +@pytest.fixture +def fakebot(): + bot = FakeBaseBot() + bot.botconfig = {"bot": {"datadir": "./examples/data/"}} + bot.loadmodule("SQLite") + bot.loadmodule("Calc") + return bot + + +def test_foo(fakebot): + print(fakebot) diff --git a/tests/modules/test_ascii.py b/tests/modules/test_ascii.py new file mode 100644 index 0000000..6025329 --- /dev/null +++ b/tests/modules/test_ascii.py @@ -0,0 +1,13 @@ +import pytest +from tests.lib import * # NOQA - fixtures + + +@pytest.fixture +def bot(fakebot): + fakebot.loadmodule("ASCII") + return fakebot + + +def test_ascii(bot): + bot.feed_line(".ascii test") + bot.act_PRIVMSG.assert_called_once_with('#test', 'hello world!') diff --git a/tests/modules/test_calc.py b/tests/modules/test_calc.py new file mode 100644 index 0000000..95b8f22 --- /dev/null +++ b/tests/modules/test_calc.py @@ -0,0 +1,81 @@ +import pytest +from contextlib import closing +from tests.lib import * # NOQA - fixtures + + +@pytest.fixture +def calcbot(fakebot): + """ + Provide a bot loaded with the Calc module. Clear the database. + """ + fakebot.botconfig["module_configs"]["Calc"] = { + "allowDelete": True, + "delaySubmit": 0, + "delayCalc": 0, + "delayCalcSpecific": 0, + "delayMatch": 0} + fakebot.loadmodule("SQLite") + with closing(fakebot.moduleInstances["SQLite"].opendb("calc.db")) as db: + for q in ["DELETE FROM calc_addedby;", + "DELETE FROM calc_channels;", + "DELETE FROM calc_definitions;", + "DELETE FROM calc_words;"]: + db.query(q) + fakebot.loadmodule("Calc") + return fakebot + + +def test_calc_empty(calcbot): + calcbot.feed_line("calc") + calcbot.act_PRIVMSG.assert_called_once_with('#test', 'This channel has no calcs, chatter :(') + calcbot.act_PRIVMSG.reset_mock() + + +def test_calc_adds(calcbot): + _add_fact(calcbot, "foo", "bar") + _add_fact(calcbot, "foo2", "bar2") + + calcbot.feed_line(".quote foo2") + calcbot.act_PRIVMSG.assert_called_once_with('#test', 'foo2 \x03= bar2 \x0314[added by: chatter]') + calcbot.act_PRIVMSG.reset_mock() + + +def _add_fact(calcbot, fact, value): + calcbot.feed_line("calc {} = {}".format(fact, value)) + calcbot.act_PRIVMSG.assert_called_once_with('#test', 'Thanks for the info, chatter.') + calcbot.act_PRIVMSG.reset_mock() + + +def test_match(calcbot): + _add_fact(calcbot, "xxx", "bar") + _add_fact(calcbot, "yyy", "bar2") + calcbot.feed_line(".match x") + calcbot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: 1 match (xxx\x03)') + + +def test_delete(calcbot): + _add_fact(calcbot, "xxx", "bar") + _add_fact(calcbot, "yyy", "bar2") + calcbot.feed_line(".calc xxx =") + calcbot.act_PRIVMSG.assert_called_once_with('#test', 'Calc deleted, chatter.') + calcbot.act_PRIVMSG.reset_mock() + calcbot.feed_line(".calc xxx") + calcbot.act_PRIVMSG.assert_called_once_with('#test', "Sorry chatter, I don't know what 'xxx' is.") + + +def test_unicode(calcbot): + _add_fact(calcbot, "👌👌👌", "uncode keys") + _add_fact(calcbot, "uncode values", "👌👌👌") + calcbot.feed_line(".calc 👌👌👌") + calcbot.act_PRIVMSG.assert_called_once_with('#test', '👌👌👌 \x03= uncode keys \x0314[added by: chatter]') + calcbot.act_PRIVMSG.reset_mock() + calcbot.feed_line(".match 👌") + calcbot.act_PRIVMSG.assert_called_once_with('#test', "chatter: 1 match (👌👌👌\x03)") + + +def test_delete_disable(calcbot): + calcbot.botconfig["module_configs"]["Calc"]["allowDelete"] = False + _add_fact(calcbot, "xxx", "bar") + _add_fact(calcbot, "yyy", "bar2") + calcbot.feed_line("calc xxx =") + calcbot.act_PRIVMSG.assert_not_called() diff --git a/tests/modules/test_modinfo.py b/tests/modules/test_modinfo.py new file mode 100644 index 0000000..c76abd4 --- /dev/null +++ b/tests/modules/test_modinfo.py @@ -0,0 +1,19 @@ +import pytest +from tests.lib import * # NOQA - fixtures + + +@pytest.fixture +def bot(fakebot): + fakebot.loadmodule("ModInfo") + return fakebot + + +def test_help(bot): + bot.feed_line(".help") + bot.act_PRIVMSG.assert_called_once_with('#test', + 'ModInfo: .help [command] show the manual for all or [commands]') + + +def test_helpindex(bot): + bot.feed_line(".helpindex") + bot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: commands: .help') diff --git a/tests/modules/test_nickuser.py b/tests/modules/test_nickuser.py new file mode 100644 index 0000000..d6b014c --- /dev/null +++ b/tests/modules/test_nickuser.py @@ -0,0 +1,74 @@ +import pytest +from contextlib import closing +from tests.lib import * # NOQA - fixtures + +# TODO: +# - Responds to pms where the dest != the bot's nick + + +@pytest.fixture +def nickbot(fakebot): + """ + Provide a bot loaded with the Calc module. Clear the database. + """ + fakebot.botconfig["module_configs"]["Calc"] = { + "allowDelete": True, + "delaySubmit": 0, + "delayCalc": 0, + "delayCalcSpecific": 0, + "delayMatch": 0} + fakebot.loadmodule("SQLite") + with closing(fakebot.moduleInstances["SQLite"].opendb("attributes.db")) as db: + for q in ["DELETE FROM attribute;", + "DELETE FROM items;", + "DELETE FROM `values`;"]: + db.query(q) + fakebot.loadmodule("AttributeStorageLite") + fakebot.loadmodule("NickUser") + return fakebot + + +def pm(nickbot, line): + nickbot.feed_line(line, args=['bot']) + + +def test_blind_login(nickbot): + pm(nickbot, ".login foobar") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You must first set a password with .setpass') + + +def test_register(nickbot): + pm(nickbot, ".setpass foobar") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: Your password has been set to "foobar".') + nickbot.act_PRIVMSG.reset_mock() + + +def test_register_login(nickbot): + test_register(nickbot) + pm(nickbot, ".login foobar") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You have been logged in from: cia.gov') + nickbot.act_PRIVMSG.reset_mock() + + +def test_badpass(nickbot): + test_register(nickbot) + pm(nickbot, ".login oopsie") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: incorrect password.') + + +def test_logout(nickbot): + test_register_login(nickbot) + pm(nickbot, ".logout") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.logout: You have been logged out.') + nickbot.act_PRIVMSG.reset_mock() + pm(nickbot, ".logout") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.logout: You must first be logged in') + + +def test_changepass(nickbot): + test_register_login(nickbot) + pm(nickbot, ".setpass foobar newpass") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: Your password has been set to "newpass".') + nickbot.act_PRIVMSG.reset_mock() + pm(nickbot, ".setpass wrong newpass2") + nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: Old password incorrect.')