mirror of
https://github.com/evennia/evennia.git
synced 2026-03-18 22:06:30 +01:00
First version of moved contrib tests
This commit is contained in:
parent
a6cb94056c
commit
04a95297b5
37 changed files with 4135 additions and 3589 deletions
177
evennia/contrib/base_systems/building_menu/tests.py
Normal file
177
evennia/contrib/base_systems/building_menu/tests.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
"""
|
||||
Building menu tests.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . building_menu import BuildingMenu, CmdNoMatch
|
||||
|
||||
|
||||
class Submenu(BuildingMenu):
|
||||
def init(self, exit):
|
||||
self.add_choice("title", key="t", attr="key")
|
||||
|
||||
|
||||
class TestBuildingMenu(CommandTest):
|
||||
def setUp(self):
|
||||
super(TestBuildingMenu, self).setUp()
|
||||
self.menu = BuildingMenu(caller=self.char1, obj=self.room1, title="test")
|
||||
self.menu.add_choice("title", key="t", attr="key")
|
||||
|
||||
def test_quit(self):
|
||||
"""Try to quit the building menu."""
|
||||
self.assertFalse(self.char1.cmdset.has("building_menu"))
|
||||
self.menu.open()
|
||||
self.assertTrue(self.char1.cmdset.has("building_menu"))
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "q")
|
||||
# char1 tries to quit the editor
|
||||
self.assertFalse(self.char1.cmdset.has("building_menu"))
|
||||
|
||||
def test_setattr(self):
|
||||
"""Test the simple setattr provided by building menus."""
|
||||
self.menu.open()
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "t")
|
||||
self.assertIsNotNone(self.menu.current_choice)
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "some new title")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.assertIsNone(self.menu.current_choice)
|
||||
self.assertEqual(self.room1.key, "some new title")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "q")
|
||||
|
||||
def test_add_choice_without_key(self):
|
||||
"""Try to add choices without keys."""
|
||||
choices = []
|
||||
for i in range(20):
|
||||
choices.append(self.menu.add_choice("choice", attr="test"))
|
||||
self.menu._add_keys_choice()
|
||||
keys = [
|
||||
"c",
|
||||
"h",
|
||||
"o",
|
||||
"i",
|
||||
"e",
|
||||
"ch",
|
||||
"ho",
|
||||
"oi",
|
||||
"ic",
|
||||
"ce",
|
||||
"cho",
|
||||
"hoi",
|
||||
"oic",
|
||||
"ice",
|
||||
"choi",
|
||||
"hoic",
|
||||
"oice",
|
||||
"choic",
|
||||
"hoice",
|
||||
"choice",
|
||||
]
|
||||
for i in range(20):
|
||||
self.assertEqual(choices[i].key, keys[i])
|
||||
|
||||
# Adding another key of the same title would break, no more available shortcut
|
||||
self.menu.add_choice("choice", attr="test")
|
||||
with self.assertRaises(ValueError):
|
||||
self.menu._add_keys_choice()
|
||||
|
||||
def test_callbacks(self):
|
||||
"""Test callbacks in menus."""
|
||||
self.room1.key = "room1"
|
||||
|
||||
def on_enter(caller, menu):
|
||||
caller.msg("on_enter:{}".format(menu.title))
|
||||
|
||||
def on_nomatch(caller, string, choice):
|
||||
caller.msg("on_nomatch:{},{}".format(string, choice.key))
|
||||
|
||||
def on_leave(caller, obj):
|
||||
caller.msg("on_leave:{}".format(obj.key))
|
||||
|
||||
self.menu.add_choice(
|
||||
"test", key="e", on_enter=on_enter, on_nomatch=on_nomatch, on_leave=on_leave
|
||||
)
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "e", "on_enter:test")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "ok", "on_nomatch:ok,e")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@", "on_leave:room1")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "q")
|
||||
|
||||
def test_multi_level(self):
|
||||
"""Test multi-level choices."""
|
||||
# Creaste three succeeding menu (t2 is contained in t1, t3 is contained in t2)
|
||||
def on_nomatch_t1(caller, menu):
|
||||
menu.move("whatever") # this will be valid since after t1 is a joker
|
||||
|
||||
def on_nomatch_t2(caller, menu):
|
||||
menu.move("t3") # this time the key matters
|
||||
|
||||
t1 = self.menu.add_choice("what", key="t1", on_nomatch=on_nomatch_t1)
|
||||
t2 = self.menu.add_choice("and", key="t1.*", on_nomatch=on_nomatch_t2)
|
||||
t3 = self.menu.add_choice("why", key="t1.*.t3")
|
||||
self.menu.open()
|
||||
|
||||
# Move into t1
|
||||
self.assertIn(t1, self.menu.relevant_choices)
|
||||
self.assertNotIn(t2, self.menu.relevant_choices)
|
||||
self.assertNotIn(t3, self.menu.relevant_choices)
|
||||
self.assertIsNone(self.menu.current_choice)
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "t1")
|
||||
self.assertEqual(self.menu.current_choice, t1)
|
||||
self.assertNotIn(t1, self.menu.relevant_choices)
|
||||
self.assertIn(t2, self.menu.relevant_choices)
|
||||
self.assertNotIn(t3, self.menu.relevant_choices)
|
||||
|
||||
# Move into t2
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "t2")
|
||||
self.assertEqual(self.menu.current_choice, t2)
|
||||
self.assertNotIn(t1, self.menu.relevant_choices)
|
||||
self.assertNotIn(t2, self.menu.relevant_choices)
|
||||
self.assertIn(t3, self.menu.relevant_choices)
|
||||
|
||||
# Move into t3
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "t3")
|
||||
self.assertEqual(self.menu.current_choice, t3)
|
||||
self.assertNotIn(t1, self.menu.relevant_choices)
|
||||
self.assertNotIn(t2, self.menu.relevant_choices)
|
||||
self.assertNotIn(t3, self.menu.relevant_choices)
|
||||
|
||||
# Move back to t2
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.assertEqual(self.menu.current_choice, t2)
|
||||
self.assertNotIn(t1, self.menu.relevant_choices)
|
||||
self.assertNotIn(t2, self.menu.relevant_choices)
|
||||
self.assertIn(t3, self.menu.relevant_choices)
|
||||
|
||||
# Move back into t1
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.assertEqual(self.menu.current_choice, t1)
|
||||
self.assertNotIn(t1, self.menu.relevant_choices)
|
||||
self.assertIn(t2, self.menu.relevant_choices)
|
||||
self.assertNotIn(t3, self.menu.relevant_choices)
|
||||
|
||||
# Moves back to the main menu
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.assertIn(t1, self.menu.relevant_choices)
|
||||
self.assertNotIn(t2, self.menu.relevant_choices)
|
||||
self.assertNotIn(t3, self.menu.relevant_choices)
|
||||
self.assertIsNone(self.menu.current_choice)
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "q")
|
||||
|
||||
def test_submenu(self):
|
||||
"""Test to add sub-menus."""
|
||||
|
||||
def open_exit(menu):
|
||||
menu.open_submenu("evennia.contrib.tests.Submenu", self.exit)
|
||||
return False
|
||||
|
||||
self.menu.add_choice("exit", key="x", on_enter=open_exit)
|
||||
self.menu.open()
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "x")
|
||||
self.menu = self.char1.ndb._building_menu
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "t")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "in")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "@")
|
||||
self.menu = self.char1.ndb._building_menu
|
||||
self.assertEqual(self.char1.ndb._building_menu.obj, self.room1)
|
||||
self.call(CmdNoMatch(building_menu=self.menu), "q")
|
||||
self.assertEqual(self.exit.key, "in")
|
||||
66
evennia/contrib/base_systems/color_markups/tests.py
Normal file
66
evennia/contrib/base_systems/color_markups/tests.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Test Color markup.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from . import color_markups
|
||||
|
||||
|
||||
class TestColorMarkup(EvenniaTest):
|
||||
"""
|
||||
Note: Normally this would be tested by importing the ansi parser and run
|
||||
the mappings through it. This is not possible since the ansi module creates
|
||||
its mapping at the module/class level; since the ansi module is used by so
|
||||
many other modules it appears that trying to overload
|
||||
settings to test it causes issues with unrelated tests.
|
||||
"""
|
||||
|
||||
def test_curly_markup(self):
|
||||
ansi_map = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP
|
||||
self.assertIsNotNone(re.match(re.escape(ansi_map[7][0]), "{r"))
|
||||
self.assertIsNotNone(re.match(re.escape(ansi_map[-1][0]), "{[X"))
|
||||
xterm_fg = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "{001"))
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "{123"))
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "{455"))
|
||||
xterm_bg = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "{[001"))
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "{[123"))
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "{[455"))
|
||||
xterm_gfg = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "{=h"))
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "{=e"))
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "{=w"))
|
||||
xterm_gbg = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "{[=a"))
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "{[=k"))
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "{[=z"))
|
||||
bright_map = color_markups.CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
|
||||
self.assertEqual(bright_map[0][1], "{[500")
|
||||
self.assertEqual(bright_map[-1][1], "{[222")
|
||||
|
||||
def test_mux_markup(self):
|
||||
ansi_map = color_markups.MUX_COLOR_ANSI_EXTRA_MAP
|
||||
self.assertIsNotNone(re.match(re.escape(ansi_map[10][0]), "%cr"))
|
||||
self.assertIsNotNone(re.match(re.escape(ansi_map[-1][0]), "%cX"))
|
||||
xterm_fg = color_markups.MUX_COLOR_XTERM256_EXTRA_FG
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "%c001"))
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "%c123"))
|
||||
self.assertIsNotNone(re.match(xterm_fg[0], "%c455"))
|
||||
xterm_bg = color_markups.MUX_COLOR_XTERM256_EXTRA_BG
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "%c[001"))
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "%c[123"))
|
||||
self.assertIsNotNone(re.match(xterm_bg[0], "%c[455"))
|
||||
xterm_gfg = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "%c=h"))
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "%c=e"))
|
||||
self.assertIsNotNone(re.match(xterm_gfg[0], "%c=w"))
|
||||
xterm_gbg = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "%c[=a"))
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "%c[=k"))
|
||||
self.assertIsNotNone(re.match(xterm_gbg[0], "%c[=z"))
|
||||
bright_map = color_markups.MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
|
||||
self.assertEqual(bright_map[0][1], "%c[500")
|
||||
self.assertEqual(bright_map[-1][1], "%c[222")
|
||||
54
evennia/contrib/base_systems/custom_gametime/tests.py
Normal file
54
evennia/contrib/base_systems/custom_gametime/tests.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Testing custom game time
|
||||
|
||||
"""
|
||||
|
||||
# Testing custom_gametime
|
||||
from mock import Mock, patch
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from . import custom_gametime
|
||||
|
||||
|
||||
def _testcallback():
|
||||
pass
|
||||
|
||||
|
||||
@patch("evennia.utils.gametime.gametime", new=Mock(return_value=2975000898.46))
|
||||
class TestCustomGameTime(EvenniaTest):
|
||||
def tearDown(self):
|
||||
if hasattr(self, "timescript"):
|
||||
self.timescript.stop()
|
||||
|
||||
def test_time_to_tuple(self):
|
||||
self.assertEqual(custom_gametime.time_to_tuple(10000, 34, 2, 4, 6, 1), (294, 2, 0, 0, 0, 0))
|
||||
self.assertEqual(custom_gametime.time_to_tuple(10000, 3, 3, 4), (3333, 0, 0, 1))
|
||||
self.assertEqual(custom_gametime.time_to_tuple(100000, 239, 24, 3), (418, 4, 0, 2))
|
||||
|
||||
def test_gametime_to_realtime(self):
|
||||
self.assertEqual(custom_gametime.gametime_to_realtime(days=2, mins=4), 86520.0)
|
||||
self.assertEqual(
|
||||
custom_gametime.gametime_to_realtime(format=True, days=2), (0, 0, 0, 1, 0, 0, 0)
|
||||
)
|
||||
|
||||
def test_realtime_to_gametime(self):
|
||||
self.assertEqual(custom_gametime.realtime_to_gametime(days=3, mins=34), 349680.0)
|
||||
self.assertEqual(
|
||||
custom_gametime.realtime_to_gametime(days=3, mins=34, format=True),
|
||||
(0, 0, 0, 4, 1, 8, 0),
|
||||
)
|
||||
self.assertEqual(
|
||||
custom_gametime.realtime_to_gametime(format=True, days=3, mins=4), (0, 0, 0, 4, 0, 8, 0)
|
||||
)
|
||||
|
||||
def test_custom_gametime(self):
|
||||
self.assertEqual(custom_gametime.custom_gametime(), (102, 5, 2, 6, 21, 8, 18))
|
||||
self.assertEqual(custom_gametime.custom_gametime(absolute=True), (102, 5, 2, 6, 21, 8, 18))
|
||||
|
||||
def test_real_seconds_until(self):
|
||||
self.assertEqual(
|
||||
custom_gametime.real_seconds_until(year=2300, month=12, day=7), 31911667199.77
|
||||
)
|
||||
|
||||
def test_schedule(self):
|
||||
self.timescript = custom_gametime.schedule(_testcallback, repeat=True, min=5, sec=0)
|
||||
self.assertEqual(self.timescript.interval, 1700.7699999809265)
|
||||
36
evennia/contrib/base_systems/email_login/tests.py
Normal file
36
evennia/contrib/base_systems/email_login/tests.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Test email login.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import email_login
|
||||
|
||||
|
||||
class TestEmailLogin(CommandTest):
|
||||
def test_connect(self):
|
||||
self.call(
|
||||
email_login.CmdUnconnectedConnect(),
|
||||
"mytest@test.com test",
|
||||
"The email 'mytest@test.com' does not match any accounts.",
|
||||
)
|
||||
self.call(
|
||||
email_login.CmdUnconnectedCreate(),
|
||||
'"mytest" mytest@test.com test11111',
|
||||
"A new account 'mytest' was created. Welcome!",
|
||||
)
|
||||
self.call(
|
||||
email_login.CmdUnconnectedConnect(),
|
||||
"mytest@test.com test11111",
|
||||
"",
|
||||
caller=self.account.sessions.get()[0],
|
||||
)
|
||||
|
||||
def test_quit(self):
|
||||
self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.account.sessions.get()[0])
|
||||
|
||||
def test_unconnectedlook(self):
|
||||
self.call(email_login.CmdUnconnectedLook(), "", "==========")
|
||||
|
||||
def test_unconnectedhelp(self):
|
||||
self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.")
|
||||
|
|
@ -12,8 +12,8 @@ from evennia.objects.objects import ExitCommand
|
|||
from evennia.utils import ansi, utils
|
||||
from evennia.utils.create import create_object, create_script
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.contrib.base_systems.ingame_python.commands import CmdCallback
|
||||
from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler
|
||||
from .commands import CmdCallback
|
||||
from .callbackhandler import CallbackHandler
|
||||
|
||||
# Force settings
|
||||
settings.EVENTS_CALENDAR = "standard"
|
||||
|
|
|
|||
12
evennia/contrib/base_systems/menu_login/tests.py
Normal file
12
evennia/contrib/base_systems/menu_login/tests.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
Test menu_login
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import menu_login
|
||||
|
||||
|
||||
class TestMenuLogin(CommandTest):
|
||||
def test_cmdunloggedlook(self):
|
||||
self.call(menu_login.CmdUnloggedinLook(), "", "======")
|
||||
86
evennia/contrib/base_systems/mux_comms_cmds/tests.py
Normal file
86
evennia/contrib/base_systems/mux_comms_cmds/tests.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Legacy Mux comms tests (extracted from 0.9.5)
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import mux_comms_cmds as comms
|
||||
|
||||
|
||||
class TestLegacyMuxComms(CommandTest):
|
||||
"""
|
||||
Test the legacy comms contrib.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CommandTest, self).setUp()
|
||||
self.call(
|
||||
comms.CmdChannelCreate(),
|
||||
"testchan;test=Test Channel",
|
||||
"Created channel testchan and connected to it.",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_toggle_com(self):
|
||||
self.call(
|
||||
comms.CmdAddCom(),
|
||||
"tc = testchan",
|
||||
"You are already connected to channel testchan.| You can now",
|
||||
receiver=self.account,
|
||||
)
|
||||
self.call(
|
||||
comms.CmdDelCom(),
|
||||
"tc",
|
||||
"Any alias 'tc' for channel testchan was cleared.",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_all_com(self):
|
||||
self.call(
|
||||
comms.CmdAllCom(),
|
||||
"",
|
||||
"Available channels:",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_clock(self):
|
||||
self.call(
|
||||
comms.CmdClock(),
|
||||
"testchan=send:all()",
|
||||
"Lock(s) applied. Current locks on testchan:",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_cdesc(self):
|
||||
self.call(
|
||||
comms.CmdCdesc(),
|
||||
"testchan = Test Channel",
|
||||
"Description of channel 'testchan' set to 'Test Channel'.",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_cwho(self):
|
||||
self.call(
|
||||
comms.CmdCWho(),
|
||||
"testchan",
|
||||
"Channel subscriptions\ntestchan:\n TestAccount",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_cboot(self):
|
||||
# No one else connected to boot
|
||||
self.call(
|
||||
comms.CmdCBoot(),
|
||||
"",
|
||||
"Usage: cboot[/quiet] <channel> = <account> [:reason]",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_cdestroy(self):
|
||||
self.call(
|
||||
comms.CmdCdestroy(),
|
||||
"testchan",
|
||||
"[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases."
|
||||
"|Channel 'testchan' was destroyed.",
|
||||
receiver=self.account,
|
||||
)
|
||||
50
evennia/contrib/base_systems/unixcommand/tests.py
Normal file
50
evennia/contrib/base_systems/unixcommand/tests.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""
|
||||
Test of the Unixcommand.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from .unixcommand import UnixCommand
|
||||
|
||||
|
||||
class CmdDummy(UnixCommand):
|
||||
|
||||
"""A dummy UnixCommand."""
|
||||
|
||||
key = "dummy"
|
||||
|
||||
def init_parser(self):
|
||||
"""Fill out options."""
|
||||
self.parser.add_argument("nb1", type=int, help="the first number")
|
||||
self.parser.add_argument("nb2", type=int, help="the second number")
|
||||
self.parser.add_argument("-v", "--verbose", action="store_true")
|
||||
|
||||
def func(self):
|
||||
nb1 = self.opts.nb1
|
||||
nb2 = self.opts.nb2
|
||||
result = nb1 * nb2
|
||||
verbose = self.opts.verbose
|
||||
if verbose:
|
||||
self.msg("{} times {} is {}".format(nb1, nb2, result))
|
||||
else:
|
||||
self.msg("{} * {} = {}".format(nb1, nb2, result))
|
||||
|
||||
|
||||
class TestUnixCommand(CommandTest):
|
||||
def test_success(self):
|
||||
"""See the command parsing succeed."""
|
||||
self.call(CmdDummy(), "5 10", "5 * 10 = 50")
|
||||
self.call(CmdDummy(), "5 10 -v", "5 times 10 is 50")
|
||||
|
||||
def test_failure(self):
|
||||
"""If not provided with the right info, should fail."""
|
||||
ret = self.call(CmdDummy(), "5")
|
||||
lines = ret.splitlines()
|
||||
self.assertTrue(any(lin.startswith("usage:") for lin in lines))
|
||||
self.assertTrue(any(lin.startswith("dummy: error:") for lin in lines))
|
||||
|
||||
# If we specify an incorrect number as parameter
|
||||
ret = self.call(CmdDummy(), "five ten")
|
||||
lines = ret.splitlines()
|
||||
self.assertTrue(any(lin.startswith("usage:") for lin in lines))
|
||||
self.assertTrue(any(lin.startswith("dummy: error:") for lin in lines))
|
||||
147
evennia/contrib/game_systems/barter/tests.py
Normal file
147
evennia/contrib/game_systems/barter/tests.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Test the contrib barter system
|
||||
"""
|
||||
|
||||
from mock import Mock
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import barter
|
||||
|
||||
|
||||
class TestBarter(CommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.tradeitem1 = create_object(key="TradeItem1", location=self.char1)
|
||||
self.tradeitem2 = create_object(key="TradeItem2", location=self.char1)
|
||||
self.tradeitem3 = create_object(key="TradeItem3", location=self.char2)
|
||||
|
||||
def test_tradehandler_base(self):
|
||||
self.char1.msg = Mock()
|
||||
self.char2.msg = Mock()
|
||||
# test all methods of the tradehandler
|
||||
handler = barter.TradeHandler(self.char1, self.char2)
|
||||
self.assertEqual(handler.part_a, self.char1)
|
||||
self.assertEqual(handler.part_b, self.char2)
|
||||
handler.msg_other(self.char1, "Want to trade?")
|
||||
handler.msg_other(self.char2, "Yes!")
|
||||
handler.msg_other(None, "Talking to myself...")
|
||||
self.assertEqual(self.char2.msg.mock_calls[0][1][0], "Want to trade?")
|
||||
self.assertEqual(self.char1.msg.mock_calls[0][1][0], "Yes!")
|
||||
self.assertEqual(self.char1.msg.mock_calls[1][1][0], "Talking to myself...")
|
||||
self.assertEqual(handler.get_other(self.char1), self.char2)
|
||||
handler.finish(force=True)
|
||||
|
||||
def test_tradehandler_joins(self):
|
||||
handler = barter.TradeHandler(self.char1, self.char2)
|
||||
self.assertTrue(handler.join(self.char2))
|
||||
self.assertTrue(handler.unjoin(self.char2))
|
||||
self.assertFalse(handler.join(self.char1))
|
||||
self.assertFalse(handler.unjoin(self.char1))
|
||||
handler.finish(force=True)
|
||||
|
||||
def test_tradehandler_offers(self):
|
||||
handler = barter.TradeHandler(self.char1, self.char2)
|
||||
handler.join(self.char2)
|
||||
handler.offer(self.char1, self.tradeitem1, self.tradeitem2)
|
||||
self.assertEqual(handler.part_a_offers, [self.tradeitem1, self.tradeitem2])
|
||||
self.assertFalse(handler.part_a_accepted)
|
||||
self.assertFalse(handler.part_b_accepted)
|
||||
handler.offer(self.char2, self.tradeitem3)
|
||||
self.assertEqual(handler.list(), ([self.tradeitem1, self.tradeitem2], [self.tradeitem3]))
|
||||
self.assertEqual(handler.search("TradeItem2"), self.tradeitem2)
|
||||
self.assertEqual(handler.search("TradeItem3"), self.tradeitem3)
|
||||
self.assertEqual(handler.search("nonexisting"), None)
|
||||
self.assertFalse(handler.finish()) # should fail since offer not yet accepted
|
||||
handler.accept(self.char1)
|
||||
handler.decline(self.char1)
|
||||
handler.accept(self.char2)
|
||||
handler.accept(self.char1) # should trigger handler.finish() automatically
|
||||
self.assertEqual(self.tradeitem1.location, self.char2)
|
||||
self.assertEqual(self.tradeitem2.location, self.char2)
|
||||
self.assertEqual(self.tradeitem3.location, self.char1)
|
||||
|
||||
def test_cmdtrade(self):
|
||||
self.call(
|
||||
barter.CmdTrade(),
|
||||
"Char2 : Hey wanna trade?",
|
||||
'You say, "Hey wanna trade?"',
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(barter.CmdTrade(), "Char decline : Nope!", 'You say, "Nope!"', caller=self.char2)
|
||||
self.call(
|
||||
barter.CmdTrade(),
|
||||
"Char2 : Hey wanna trade?",
|
||||
'You say, "Hey wanna trade?"',
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(barter.CmdTrade(), "Char accept : Sure!", 'You say, "Sure!"', caller=self.char2)
|
||||
self.call(
|
||||
barter.CmdOffer(),
|
||||
"TradeItem3",
|
||||
"Your trade action: You offer TradeItem3",
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(
|
||||
barter.CmdOffer(),
|
||||
"TradeItem1 : Here's my offer.",
|
||||
'You say, "Here\'s my offer."\n [You offer TradeItem1]',
|
||||
)
|
||||
self.call(
|
||||
barter.CmdAccept(),
|
||||
"",
|
||||
"Your trade action: You accept the offer. Char2 must now also accept",
|
||||
)
|
||||
self.call(
|
||||
barter.CmdDecline(),
|
||||
"",
|
||||
"Your trade action: You change your mind, declining the current offer.",
|
||||
)
|
||||
self.call(
|
||||
barter.CmdAccept(),
|
||||
": Sounds good.",
|
||||
'You say, "Sounds good."\n' " [You accept the offer. Char must now also accept.",
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(
|
||||
barter.CmdDecline(),
|
||||
":No way!",
|
||||
'You say, "No way!"\n [You change your mind, declining the current offer.]',
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(
|
||||
barter.CmdOffer(),
|
||||
"TradeItem1, TradeItem2 : My final offer!",
|
||||
'You say, "My final offer!"\n [You offer TradeItem1 and TradeItem2]',
|
||||
)
|
||||
self.call(
|
||||
barter.CmdAccept(),
|
||||
"",
|
||||
"Your trade action: You accept the offer. Char2 must now also accept.",
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(barter.CmdStatus(), "", "Offered by Char:", caller=self.char2)
|
||||
self.tradeitem1.db.desc = "A great offer."
|
||||
self.call(barter.CmdEvaluate(), "TradeItem1", "A great offer.")
|
||||
self.call(
|
||||
barter.CmdAccept(),
|
||||
":Ok then.",
|
||||
'You say, "Ok then."\n [You accept the deal.',
|
||||
caller=self.char2,
|
||||
)
|
||||
self.assertEqual(self.tradeitem1.location, self.char2)
|
||||
self.assertEqual(self.tradeitem2.location, self.char2)
|
||||
self.assertEqual(self.tradeitem3.location, self.char1)
|
||||
|
||||
def test_cmdtradehelp(self):
|
||||
self.call(
|
||||
barter.CmdTrade(),
|
||||
"Char2 : Hey wanna trade?",
|
||||
'You say, "Hey wanna trade?"',
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(barter.CmdTradeHelp(), "", "Trading commands\n", caller=self.char1)
|
||||
self.call(
|
||||
barter.CmdFinish(),
|
||||
": Ending.",
|
||||
'You say, "Ending."\n [You aborted trade. No deal was made.]',
|
||||
)
|
||||
133
evennia/contrib/game_systems/clothing/tests.py
Normal file
133
evennia/contrib/game_systems/clothing/tests.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
Testing clothing contrib
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from . import clothing
|
||||
|
||||
|
||||
class TestClothingCmd(CommandTest):
|
||||
def test_clothingcommands(self):
|
||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||
friend = create_object(clothing.ClothedCharacter, key="Friend")
|
||||
room = create_object(DefaultRoom, key="room")
|
||||
wearer.location = room
|
||||
friend.location = room
|
||||
# Make a test hat
|
||||
test_hat = create_object(clothing.Clothing, key="test hat")
|
||||
test_hat.db.clothing_type = "hat"
|
||||
test_hat.location = wearer
|
||||
# Make a test scarf
|
||||
test_scarf = create_object(clothing.Clothing, key="test scarf")
|
||||
test_scarf.db.clothing_type = "accessory"
|
||||
test_scarf.location = wearer
|
||||
# Test wear command
|
||||
self.call(clothing.CmdWear(), "", "Usage: wear <obj> [wear style]", caller=wearer)
|
||||
self.call(clothing.CmdWear(), "hat", "Wearer puts on test hat.", caller=wearer)
|
||||
self.call(
|
||||
clothing.CmdWear(),
|
||||
"scarf stylishly",
|
||||
"Wearer wears test scarf stylishly.",
|
||||
caller=wearer,
|
||||
)
|
||||
# Test cover command.
|
||||
self.call(
|
||||
clothing.CmdCover(),
|
||||
"",
|
||||
"Usage: cover <worn clothing> [with] <clothing object>",
|
||||
caller=wearer,
|
||||
)
|
||||
self.call(
|
||||
clothing.CmdCover(),
|
||||
"hat with scarf",
|
||||
"Wearer covers test hat with test scarf.",
|
||||
caller=wearer,
|
||||
)
|
||||
# Test remove command.
|
||||
self.call(clothing.CmdRemove(), "", "Could not find ''.", caller=wearer)
|
||||
self.call(
|
||||
clothing.CmdRemove(), "hat", "You have to take off test scarf first.", caller=wearer
|
||||
)
|
||||
self.call(
|
||||
clothing.CmdRemove(),
|
||||
"scarf",
|
||||
"Wearer removes test scarf, revealing test hat.",
|
||||
caller=wearer,
|
||||
)
|
||||
# Test uncover command.
|
||||
test_scarf.wear(wearer, True)
|
||||
test_hat.db.covered_by = test_scarf
|
||||
self.call(clothing.CmdUncover(), "", "Usage: uncover <worn clothing object>", caller=wearer)
|
||||
self.call(clothing.CmdUncover(), "hat", "Wearer uncovers test hat.", caller=wearer)
|
||||
# Test drop command.
|
||||
test_hat.db.covered_by = test_scarf
|
||||
self.call(clothing.CmdDrop(), "", "Drop what?", caller=wearer)
|
||||
self.call(
|
||||
clothing.CmdDrop(),
|
||||
"hat",
|
||||
"You can't drop that because it's covered by test scarf.",
|
||||
caller=wearer,
|
||||
)
|
||||
self.call(clothing.CmdDrop(), "scarf", "You drop test scarf.", caller=wearer)
|
||||
# Test give command.
|
||||
self.call(
|
||||
clothing.CmdGive(), "", "Usage: give <inventory object> = <target>", caller=wearer
|
||||
)
|
||||
self.call(
|
||||
clothing.CmdGive(),
|
||||
"hat = Friend",
|
||||
"Wearer removes test hat.|You give test hat to Friend.",
|
||||
caller=wearer,
|
||||
)
|
||||
# Test inventory command.
|
||||
self.call(
|
||||
clothing.CmdInventory(), "", "You are not carrying or wearing anything.", caller=wearer
|
||||
)
|
||||
|
||||
|
||||
class TestClothingFunc(EvenniaTest):
|
||||
def test_clothingfunctions(self):
|
||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||
room = create_object(DefaultRoom, key="room")
|
||||
wearer.location = room
|
||||
# Make a test hat
|
||||
test_hat = create_object(clothing.Clothing, key="test hat")
|
||||
test_hat.db.clothing_type = "hat"
|
||||
test_hat.location = wearer
|
||||
# Make a test shirt
|
||||
test_shirt = create_object(clothing.Clothing, key="test shirt")
|
||||
test_shirt.db.clothing_type = "top"
|
||||
test_shirt.location = wearer
|
||||
# Make a test pants
|
||||
test_pants = create_object(clothing.Clothing, key="test pants")
|
||||
test_pants.db.clothing_type = "bottom"
|
||||
test_pants.location = wearer
|
||||
|
||||
test_hat.wear(wearer, "on the head")
|
||||
self.assertEqual(test_hat.db.worn, "on the head")
|
||||
|
||||
test_hat.remove(wearer)
|
||||
self.assertEqual(test_hat.db.worn, False)
|
||||
|
||||
test_hat.worn = True
|
||||
test_hat.at_get(wearer)
|
||||
self.assertEqual(test_hat.db.worn, False)
|
||||
|
||||
clothes_list = [test_shirt, test_hat, test_pants]
|
||||
self.assertEqual(
|
||||
clothing.order_clothes_list(clothes_list), [test_hat, test_shirt, test_pants]
|
||||
)
|
||||
|
||||
test_hat.wear(wearer, True)
|
||||
test_pants.wear(wearer, True)
|
||||
self.assertEqual(clothing.get_worn_clothes(wearer), [test_hat, test_pants])
|
||||
|
||||
self.assertEqual(
|
||||
clothing.clothing_type_count(clothes_list), {"hat": 1, "top": 1, "bottom": 1}
|
||||
)
|
||||
|
||||
self.assertEqual(clothing.single_type_count(clothes_list, "hat"), 1)
|
||||
147
evennia/contrib/game_systems/cooldowns/tests.py
Normal file
147
evennia/contrib/game_systems/cooldowns/tests.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Cooldowns tests.
|
||||
|
||||
"""
|
||||
|
||||
from mock import patch
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from . import cooldowns
|
||||
|
||||
|
||||
@patch("evennia.contrib.cooldowns.time.time", return_value=0.0)
|
||||
class TestCooldowns(EvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.handler = cooldowns.CooldownHandler(self.char1)
|
||||
|
||||
def test_empty(self, mock_time):
|
||||
self.assertEqual(self.handler.all, [])
|
||||
self.assertTrue(self.handler.ready("a", "b", "c"))
|
||||
self.assertEqual(self.handler.time_left("a", "b", "c"), 0)
|
||||
|
||||
def test_add(self, mock_time):
|
||||
self.assertEqual(self.handler.add, self.handler.set)
|
||||
self.handler.add("a", 10)
|
||||
self.assertFalse(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 10)
|
||||
mock_time.return_value = 9.0
|
||||
self.assertFalse(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 1)
|
||||
mock_time.return_value = 10.0
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
|
||||
def test_add_float(self, mock_time):
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=False), 0)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=True), 0)
|
||||
self.handler.add("a", 5.5)
|
||||
self.assertEqual(self.handler.time_left("a"), 5.5)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=False), 5.5)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=True), 6)
|
||||
|
||||
def test_add_multi(self, mock_time):
|
||||
self.handler.add("a", 10)
|
||||
self.handler.add("b", 5)
|
||||
self.handler.add("c", 3)
|
||||
self.assertFalse(self.handler.ready("a", "b", "c"))
|
||||
self.assertEqual(self.handler.time_left("a", "b", "c"), 10)
|
||||
self.assertEqual(self.handler.time_left("a", "b"), 10)
|
||||
self.assertEqual(self.handler.time_left("a", "c"), 10)
|
||||
self.assertEqual(self.handler.time_left("b", "c"), 5)
|
||||
self.assertEqual(self.handler.time_left("c", "c"), 3)
|
||||
|
||||
def test_add_none(self, mock_time):
|
||||
self.handler.add("a", None)
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
|
||||
def test_add_negative(self, mock_time):
|
||||
self.handler.add("a", -5)
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
|
||||
def test_add_overwrite(self, mock_time):
|
||||
self.handler.add("a", 5)
|
||||
self.handler.add("a", 10)
|
||||
self.handler.add("a", 3)
|
||||
self.assertFalse(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 3)
|
||||
|
||||
def test_extend(self, mock_time):
|
||||
self.assertEqual(self.handler.extend("a", 10), 10)
|
||||
self.assertFalse(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 10)
|
||||
self.assertEqual(self.handler.extend("a", 10), 20)
|
||||
self.assertFalse(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 20)
|
||||
|
||||
def test_extend_none(self, mock_time):
|
||||
self.assertEqual(self.handler.extend("a", None), 0)
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
self.handler.add("a", 10)
|
||||
self.assertEqual(self.handler.extend("a", None), 10)
|
||||
self.assertEqual(self.handler.time_left("a"), 10)
|
||||
|
||||
def test_extend_negative(self, mock_time):
|
||||
self.assertEqual(self.handler.extend("a", -5), 0)
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
self.handler.add("a", 10)
|
||||
self.assertEqual(self.handler.extend("a", -5), 5)
|
||||
self.assertEqual(self.handler.time_left("a"), 5)
|
||||
|
||||
def test_extend_float(self, mock_time):
|
||||
self.assertEqual(self.handler.extend("a", -5.5), 0)
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0.0)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=False), 0.0)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=True), 0)
|
||||
self.handler.add("a", 10.5)
|
||||
self.assertEqual(self.handler.extend("a", -5.25), 5.25)
|
||||
self.assertEqual(self.handler.time_left("a"), 5.25)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=False), 5.25)
|
||||
self.assertEqual(self.handler.time_left("a", use_int=True), 6)
|
||||
|
||||
def test_reset_non_existent(self, mock_time):
|
||||
self.handler.reset("a")
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
|
||||
def test_reset(self, mock_time):
|
||||
self.handler.set("a", 10)
|
||||
self.handler.reset("a")
|
||||
self.assertTrue(self.handler.ready("a"))
|
||||
self.assertEqual(self.handler.time_left("a"), 0)
|
||||
|
||||
def test_clear(self, mock_time):
|
||||
self.handler.add("a", 10)
|
||||
self.handler.add("b", 10)
|
||||
self.handler.add("c", 10)
|
||||
self.handler.clear()
|
||||
self.assertTrue(self.handler.ready("a", "b", "c"))
|
||||
self.assertEqual(self.handler.time_left("a", "b", "c"), 0)
|
||||
|
||||
def test_cleanup(self, mock_time):
|
||||
self.handler.add("a", 10)
|
||||
self.handler.add("b", 5)
|
||||
self.handler.add("c", 5)
|
||||
self.handler.add("d", 3.5)
|
||||
mock_time.return_value = 6.0
|
||||
self.handler.cleanup()
|
||||
self.assertEqual(self.handler.time_left("b", "c", "d"), 0)
|
||||
self.assertEqual(self.handler.time_left("a"), 4)
|
||||
self.assertEqual(list(self.handler.data.keys()), ["a"])
|
||||
|
||||
def test_cleanup_doesnt_delete_anything(self, mock_time):
|
||||
self.handler.add("a", 10)
|
||||
self.handler.add("b", 5)
|
||||
self.handler.add("c", 5)
|
||||
self.handler.add("d", 3.5)
|
||||
mock_time.return_value = 1.0
|
||||
self.handler.cleanup()
|
||||
self.assertEqual(self.handler.time_left("d"), 2.5)
|
||||
self.assertEqual(self.handler.time_left("b", "c"), 4)
|
||||
self.assertEqual(self.handler.time_left("a"), 9)
|
||||
self.assertEqual(list(self.handler.data.keys()), ["a", "b", "c", "d"])
|
||||
28
evennia/contrib/game_systems/gendersub/tests.py
Normal file
28
evennia/contrib/game_systems/gendersub/tests.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""
|
||||
Test gendersub contrib.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from mock import patch
|
||||
from . import gendersub
|
||||
|
||||
|
||||
class TestGenderSub(CommandTest):
|
||||
def test_setgender(self):
|
||||
self.call(gendersub.SetGender(), "male", "Your gender was set to male.")
|
||||
self.call(gendersub.SetGender(), "ambiguous", "Your gender was set to ambiguous.")
|
||||
self.call(gendersub.SetGender(), "Foo", "Usage: @gender")
|
||||
|
||||
def test_gendercharacter(self):
|
||||
char = create_object(gendersub.GenderCharacter, key="Gendered", location=self.room1)
|
||||
txt = "Test |p gender"
|
||||
self.assertEqual(
|
||||
gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender"
|
||||
)
|
||||
with patch("evennia.contrib.gendersub.DefaultCharacter.msg") as mock_msg:
|
||||
char.db.gender = "female"
|
||||
char.msg("Test |p gender")
|
||||
mock_msg.assert_called_with("Test her gender", from_obj=None, session=None)
|
||||
47
evennia/contrib/game_systems/mail/tests.py
Normal file
47
evennia/contrib/game_systems/mail/tests.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Test mail contrib
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import mail
|
||||
|
||||
|
||||
class TestMail(CommandTest):
|
||||
def test_mail(self):
|
||||
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.account)
|
||||
self.call(mail.CmdMail(), "test", "'test' is not a valid mail id.", caller=self.account)
|
||||
self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.account)
|
||||
self.call(
|
||||
mail.CmdMailCharacter(),
|
||||
"Char=Message 1",
|
||||
"You have received a new @mail from Char|You sent your message.",
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(
|
||||
mail.CmdMailCharacter(), "Char=Message 2", "You sent your message.", caller=self.char2
|
||||
)
|
||||
self.call(
|
||||
mail.CmdMail(),
|
||||
"TestAccount2=Message 2",
|
||||
"You have received a new @mail from TestAccount2",
|
||||
caller=self.account2,
|
||||
)
|
||||
self.call(
|
||||
mail.CmdMail(), "TestAccount=Message 1", "You sent your message.", caller=self.account2
|
||||
)
|
||||
self.call(
|
||||
mail.CmdMail(), "TestAccount=Message 2", "You sent your message.", caller=self.account2
|
||||
)
|
||||
self.call(mail.CmdMail(), "", "| ID From Subject", caller=self.account)
|
||||
self.call(mail.CmdMail(), "2", "From: TestAccount2", caller=self.account)
|
||||
self.call(
|
||||
mail.CmdMail(),
|
||||
"/forward TestAccount2 = 1/Forward message",
|
||||
"You sent your message.|Message forwarded.",
|
||||
caller=self.account,
|
||||
)
|
||||
self.call(
|
||||
mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.account
|
||||
)
|
||||
self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.account)
|
||||
40
evennia/contrib/game_systems/multidescer/tests.py
Normal file
40
evennia/contrib/game_systems/multidescer/tests.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
Test multidescer contrib.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import multidescer
|
||||
|
||||
|
||||
class TestMultidescer(CommandTest):
|
||||
def test_cmdmultidesc(self):
|
||||
self.call(multidescer.CmdMultiDesc(), "/list", "Stored descs:\ncaller:")
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(), "test = Desc 1", "Stored description 'test': \"Desc 1\""
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(), "test2 = Desc 2", "Stored description 'test2': \"Desc 2\""
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(), "/swap test-test2", "Swapped descs 'test' and 'test2'."
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(),
|
||||
"test3 = Desc 3init",
|
||||
"Stored description 'test3': \"Desc 3init\"",
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(),
|
||||
"/list",
|
||||
"Stored descs:\ntest3: Desc 3init\ntest: Desc 1\ntest2: Desc 2\ncaller:",
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(), "test3 = Desc 3", "Stored description 'test3': \"Desc 3\""
|
||||
)
|
||||
self.call(
|
||||
multidescer.CmdMultiDesc(),
|
||||
"/set test1 + test2 + + test3",
|
||||
"test1 Desc 2 Desc 3\n\n" "The above was set as the current description.",
|
||||
)
|
||||
self.assertEqual(self.char1.db.desc, "test1 Desc 2 Desc 3")
|
||||
975
evennia/contrib/game_systems/puzzles/tests.py
Normal file
975
evennia/contrib/game_systems/puzzles/tests.py
Normal file
|
|
@ -0,0 +1,975 @@
|
|||
"""
|
||||
Testing puzzles.
|
||||
|
||||
"""
|
||||
|
||||
# Test of the Puzzles module
|
||||
|
||||
import re
|
||||
import itertools
|
||||
from mock import Mock
|
||||
from evennia.utils import search
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import puzzles
|
||||
|
||||
|
||||
class TestPuzzles(CommandTest):
|
||||
def setUp(self):
|
||||
super(TestPuzzles, self).setUp()
|
||||
self.steel = create_object(self.object_typeclass, key="steel", location=self.char1.location)
|
||||
self.flint = create_object(self.object_typeclass, key="flint", location=self.char1.location)
|
||||
self.fire = create_object(self.object_typeclass, key="fire", location=self.char1.location)
|
||||
self.steel.tags.add("tag-steel")
|
||||
self.steel.tags.add("tag-steel", category="tagcat")
|
||||
self.flint.tags.add("tag-flint")
|
||||
self.flint.tags.add("tag-flint", category="tagcat")
|
||||
self.fire.tags.add("tag-fire")
|
||||
self.fire.tags.add("tag-fire", category="tagcat")
|
||||
|
||||
def _assert_msg_matched(self, msg, regexs, re_flags=0):
|
||||
matches = []
|
||||
for regex in regexs:
|
||||
m = re.search(regex, msg, re_flags)
|
||||
self.assertIsNotNone(m, "%r didn't match %r" % (regex, msg))
|
||||
matches.append(m)
|
||||
return matches
|
||||
|
||||
def _assert_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1):
|
||||
def _keys(items):
|
||||
return [item["key"] for item in items]
|
||||
|
||||
recipes = search.search_script_tag("", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
self.assertEqual(expected_count, len(recipes))
|
||||
self.assertEqual(name, recipes[expected_count - 1].db.puzzle_name)
|
||||
self.assertEqual(parts, _keys(recipes[expected_count - 1].db.parts))
|
||||
self.assertEqual(results, _keys(recipes[expected_count - 1].db.results))
|
||||
self.assertEqual(
|
||||
puzzles._PUZZLES_TAG_RECIPE,
|
||||
recipes[expected_count - 1].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY),
|
||||
)
|
||||
recipe_dbref = recipes[expected_count - 1].dbref
|
||||
if and_destroy_it:
|
||||
recipes[expected_count - 1].delete()
|
||||
return recipe_dbref if not and_destroy_it else None
|
||||
|
||||
def _assert_no_recipes(self):
|
||||
self.assertEqual(
|
||||
0, len(search.search_script_tag("", category=puzzles._PUZZLES_TAG_CATEGORY))
|
||||
)
|
||||
|
||||
# good recipes
|
||||
def _good_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1):
|
||||
regexs = []
|
||||
for p in parts:
|
||||
regexs.append(r"^Part %s\(#\d+\)$" % (p))
|
||||
for r in results:
|
||||
regexs.append(r"^Result %s\(#\d+\)$" % (r))
|
||||
regexs.append(r"^Puzzle '%s' %s\(#\d+\) has been created successfully.$" % (name, name))
|
||||
lhs = [name] + parts
|
||||
cmdstr = ",".join(lhs) + "=" + ",".join(results)
|
||||
msg = self.call(puzzles.CmdCreatePuzzleRecipe(), cmdstr, caller=self.char1)
|
||||
recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it, expected_count)
|
||||
self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL)
|
||||
return recipe_dbref
|
||||
|
||||
def _check_room_contents(self, expected, check_test_tags=False):
|
||||
by_obj_key = lambda o: o.key
|
||||
room1_contents = sorted(self.room1.contents, key=by_obj_key)
|
||||
for key, grp in itertools.groupby(room1_contents, by_obj_key):
|
||||
if key in expected:
|
||||
grp = list(grp)
|
||||
self.assertEqual(
|
||||
expected[key],
|
||||
len(grp),
|
||||
"Expected %d but got %d for %s" % (expected[key], len(grp), key),
|
||||
)
|
||||
if check_test_tags:
|
||||
for gi in grp:
|
||||
tags = gi.tags.all(return_key_and_category=True)
|
||||
self.assertIn(("tag-" + gi.key, "tagcat"), tags)
|
||||
|
||||
def _arm(self, recipe_dbref, name, parts):
|
||||
regexs = [
|
||||
r"^Puzzle Recipe %s\(#\d+\) '%s' found.$" % (name, name),
|
||||
r"^Spawning %d parts ...$" % (len(parts)),
|
||||
]
|
||||
for p in parts:
|
||||
regexs.append(r"^Part %s\(#\d+\) spawned .*$" % (p))
|
||||
regexs.append(r"^Puzzle armed successfully.$")
|
||||
msg = self.call(puzzles.CmdArmPuzzle(), recipe_dbref, caller=self.char1)
|
||||
self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL)
|
||||
|
||||
def test_cmdset_puzzle(self):
|
||||
self.char1.cmdset.add("evennia.contrib.puzzles.PuzzleSystemCmdSet")
|
||||
# FIXME: testing nothing, this is just to bump up coverage
|
||||
|
||||
def test_cmd_puzzle(self):
|
||||
self._assert_no_recipes()
|
||||
|
||||
# bad syntax
|
||||
def _bad_syntax(cmdstr):
|
||||
self.call(
|
||||
puzzles.CmdCreatePuzzleRecipe(),
|
||||
cmdstr,
|
||||
"Usage: @puzzle name,<part1[,...]> = <result1[,...]>",
|
||||
caller=self.char1,
|
||||
)
|
||||
|
||||
_bad_syntax("")
|
||||
_bad_syntax("=")
|
||||
_bad_syntax("nothing =")
|
||||
_bad_syntax("= nothing")
|
||||
_bad_syntax("nothing")
|
||||
_bad_syntax(",nothing")
|
||||
_bad_syntax("name, nothing")
|
||||
_bad_syntax("name, nothing =")
|
||||
|
||||
self._assert_no_recipes()
|
||||
|
||||
self._good_recipe("makefire", ["steel", "flint"], ["fire", "steel", "flint"])
|
||||
self._good_recipe("hot steels", ["steel", "fire"], ["steel", "fire"])
|
||||
self._good_recipe(
|
||||
"furnace",
|
||||
["steel", "steel", "fire"],
|
||||
["steel", "steel", "fire", "fire", "fire", "fire"],
|
||||
)
|
||||
|
||||
# bad recipes
|
||||
def _bad_recipe(name, parts, results, fail_regex):
|
||||
cmdstr = ",".join([name] + parts) + "=" + ",".join(results)
|
||||
msg = self.call(puzzles.CmdCreatePuzzleRecipe(), cmdstr, caller=self.char1)
|
||||
self._assert_no_recipes()
|
||||
self.assertIsNotNone(re.match(fail_regex, msg), msg)
|
||||
|
||||
_bad_recipe("name", ["nothing"], ["neither"], r"Could not find 'nothing'.")
|
||||
_bad_recipe("name", ["steel"], ["nothing"], r"Could not find 'nothing'.")
|
||||
_bad_recipe("", ["steel", "fire"], ["steel", "fire"], r"^Invalid puzzle name ''.")
|
||||
self.steel.location = self.char1
|
||||
_bad_recipe("name", ["steel"], ["fire"], r"^Invalid location for steel$")
|
||||
_bad_recipe("name", ["flint"], ["steel"], r"^Invalid location for steel$")
|
||||
_bad_recipe("name", ["self"], ["fire"], r"^Invalid typeclass for Char$")
|
||||
_bad_recipe("name", ["here"], ["fire"], r"^Invalid typeclass for Room$")
|
||||
|
||||
self._assert_no_recipes()
|
||||
|
||||
def test_cmd_armpuzzle(self):
|
||||
# bad arms
|
||||
self.call(
|
||||
puzzles.CmdArmPuzzle(),
|
||||
"1",
|
||||
"A puzzle recipe's #dbref must be specified",
|
||||
caller=self.char1,
|
||||
)
|
||||
self.call(puzzles.CmdArmPuzzle(), "#1", "Invalid puzzle '#1'", caller=self.char1)
|
||||
|
||||
recipe_dbref = self._good_recipe(
|
||||
"makefire", ["steel", "flint"], ["fire", "steel", "flint"], and_destroy_it=False
|
||||
)
|
||||
|
||||
# delete proto parts and proto result
|
||||
self.steel.delete()
|
||||
self.flint.delete()
|
||||
self.fire.delete()
|
||||
|
||||
# good arm
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
self._check_room_contents({"steel": 1, "flint": 1}, check_test_tags=True)
|
||||
|
||||
def _use(self, cmdstr, expmsg):
|
||||
msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, expmsg, caller=self.char1)
|
||||
return msg
|
||||
|
||||
def test_cmd_use(self):
|
||||
|
||||
self._use("", "Use what?")
|
||||
self._use("something", "There is no something around.")
|
||||
self._use("steel", "You have no idea how this can be used")
|
||||
self._use("steel flint", "There is no steel flint around.")
|
||||
self._use("steel, flint", "You have no idea how these can be used")
|
||||
|
||||
recipe_dbref = self._good_recipe(
|
||||
"makefire", ["steel", "flint"], ["fire"], and_destroy_it=False
|
||||
)
|
||||
recipe2_dbref = self._good_recipe(
|
||||
"makefire2", ["steel", "flint"], ["fire"], and_destroy_it=False, expected_count=2
|
||||
)
|
||||
|
||||
# although there is steel and flint
|
||||
# those aren't valid puzzle parts because
|
||||
# the puzzle hasn't been armed
|
||||
self._use("steel", "You have no idea how this can be used")
|
||||
self._use("steel, flint", "You have no idea how these can be used")
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
self._check_room_contents({"steel": 2, "flint": 2}, check_test_tags=True)
|
||||
|
||||
# there are duplicated objects now
|
||||
self._use("steel", "Which steel. There are many")
|
||||
self._use("flint", "Which flint. There are many")
|
||||
|
||||
# delete proto parts and proto results
|
||||
self.steel.delete()
|
||||
self.flint.delete()
|
||||
self.fire.delete()
|
||||
|
||||
# solve puzzle
|
||||
self._use("steel, flint", "You are a Genius")
|
||||
self.assertEqual(
|
||||
1,
|
||||
len(
|
||||
list(
|
||||
filter(
|
||||
lambda o: o.key == "fire"
|
||||
and ("makefire", puzzles._PUZZLES_TAG_CATEGORY)
|
||||
in o.tags.all(return_key_and_category=True)
|
||||
and (puzzles._PUZZLES_TAG_MEMBER, puzzles._PUZZLES_TAG_CATEGORY)
|
||||
in o.tags.all(return_key_and_category=True),
|
||||
self.room1.contents,
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
self._check_room_contents({"steel": 0, "flint": 0, "fire": 1}, check_test_tags=True)
|
||||
|
||||
# trying again will fail as it was resolved already
|
||||
# and the parts were destroyed
|
||||
self._use("steel, flint", "There is no steel around")
|
||||
self._use("flint, steel", "There is no flint around")
|
||||
|
||||
# arm same puzzle twice so there are duplicated parts
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
self._check_room_contents({"steel": 2, "flint": 2, "fire": 1}, check_test_tags=True)
|
||||
|
||||
# try solving with multiple parts but incomplete set
|
||||
self._use(
|
||||
"steel-1, steel-2", "You try to utilize these but nothing happens ... something amiss?"
|
||||
)
|
||||
|
||||
# arm the other puzzle. Their parts are identical
|
||||
self._arm(recipe2_dbref, "makefire2", ["steel", "flint"])
|
||||
self._check_room_contents({"steel": 3, "flint": 3, "fire": 1}, check_test_tags=True)
|
||||
|
||||
# solve with multiple parts for
|
||||
# multiple puzzles. Both can be solved but
|
||||
# only one is.
|
||||
self._use(
|
||||
"steel-1, flint-2, steel-3, flint-3",
|
||||
"Your gears start turning and 2 different ideas come to your mind ... ",
|
||||
)
|
||||
self._check_room_contents({"steel": 2, "flint": 2, "fire": 2}, check_test_tags=True)
|
||||
|
||||
self.room1.msg_contents = Mock()
|
||||
|
||||
# solve all
|
||||
self._use("steel-1, flint-1", "You are a Genius")
|
||||
self.room1.msg_contents.assert_called_once_with(
|
||||
"|cChar|n performs some kind of tribal dance and |yfire|n seems to appear from thin air",
|
||||
exclude=(self.char1,),
|
||||
)
|
||||
self._use("steel, flint", "You are a Genius")
|
||||
self._check_room_contents({"steel": 0, "flint": 0, "fire": 4}, check_test_tags=True)
|
||||
|
||||
def test_puzzleedit(self):
|
||||
recipe_dbref = self._good_recipe(
|
||||
"makefire", ["steel", "flint"], ["fire"], and_destroy_it=False
|
||||
)
|
||||
|
||||
def _puzzleedit(swt, dbref, args, expmsg):
|
||||
if (swt is None) and (dbref is None) and (args is None):
|
||||
cmdstr = ""
|
||||
else:
|
||||
cmdstr = "%s %s%s" % (swt, dbref, args)
|
||||
self.call(puzzles.CmdEditPuzzle(), cmdstr, expmsg, caller=self.char1)
|
||||
|
||||
# delete proto parts and proto results
|
||||
self.steel.delete()
|
||||
self.flint.delete()
|
||||
self.fire.delete()
|
||||
|
||||
sid = self.script.id
|
||||
# bad syntax
|
||||
_puzzleedit(
|
||||
None, None, None, "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit"
|
||||
)
|
||||
_puzzleedit("", "1", "", "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit")
|
||||
_puzzleedit("", "", "", "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit")
|
||||
_puzzleedit(
|
||||
"",
|
||||
recipe_dbref,
|
||||
"dummy",
|
||||
"A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit",
|
||||
)
|
||||
_puzzleedit("", self.script.dbref, "", "Script(#{}) is not a puzzle".format(sid))
|
||||
|
||||
# edit use_success_message and use_success_location_message
|
||||
_puzzleedit(
|
||||
"",
|
||||
recipe_dbref,
|
||||
"/use_success_message = Yes!",
|
||||
"makefire(%s) use_success_message = Yes!" % recipe_dbref,
|
||||
)
|
||||
_puzzleedit(
|
||||
"",
|
||||
recipe_dbref,
|
||||
"/use_success_location_message = {result_names} Yeah baby! {caller}",
|
||||
"makefire(%s) use_success_location_message = {result_names} Yeah baby! {caller}"
|
||||
% recipe_dbref,
|
||||
)
|
||||
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
self.room1.msg_contents = Mock()
|
||||
self._use("steel, flint", "Yes!")
|
||||
self.room1.msg_contents.assert_called_once_with(
|
||||
"fire Yeah baby! Char", exclude=(self.char1,)
|
||||
)
|
||||
self.room1.msg_contents.reset_mock()
|
||||
|
||||
# edit mask: exclude location and desc during matching
|
||||
_puzzleedit(
|
||||
"",
|
||||
recipe_dbref,
|
||||
"/mask = location,desc",
|
||||
"makefire(%s) mask = ('location', 'desc')" % recipe_dbref,
|
||||
)
|
||||
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
# change location and desc
|
||||
self.char1.search("steel").db.desc = "A solid bar of steel"
|
||||
self.char1.search("steel").location = self.char1
|
||||
self.char1.search("flint").db.desc = "A flint steel"
|
||||
self.char1.search("flint").location = self.char1
|
||||
self._use("steel, flint", "Yes!")
|
||||
self.room1.msg_contents.assert_called_once_with(
|
||||
"fire Yeah baby! Char", exclude=(self.char1,)
|
||||
)
|
||||
|
||||
# delete
|
||||
_puzzleedit("/delete", recipe_dbref, "", "makefire(%s) was deleted" % recipe_dbref)
|
||||
self._assert_no_recipes()
|
||||
|
||||
def test_puzzleedit_add_remove_parts_results(self):
|
||||
recipe_dbref = self._good_recipe(
|
||||
"makefire", ["steel", "flint"], ["fire"], and_destroy_it=False
|
||||
)
|
||||
|
||||
def _puzzleedit(swt, dbref, rhslist, expmsg):
|
||||
cmdstr = "%s %s = %s" % (swt, dbref, ", ".join(rhslist))
|
||||
self.call(puzzles.CmdEditPuzzle(), cmdstr, expmsg, caller=self.char1)
|
||||
|
||||
red_steel = create_object(
|
||||
self.object_typeclass, key="red steel", location=self.char1.location
|
||||
)
|
||||
smoke = create_object(self.object_typeclass, key="smoke", location=self.char1.location)
|
||||
|
||||
_puzzleedit("/addresult", recipe_dbref, ["smoke"], "smoke were added to results")
|
||||
_puzzleedit(
|
||||
"/addpart", recipe_dbref, ["red steel", "steel"], "red steel, steel were added to parts"
|
||||
)
|
||||
|
||||
# create a box so we can put all objects in
|
||||
# so that they can't be found during puzzle resolution
|
||||
self.box = create_object(self.object_typeclass, key="box", location=self.char1.location)
|
||||
|
||||
def _box_all():
|
||||
for o in self.room1.contents:
|
||||
if o not in [self.char1, self.char2, self.exit, self.obj1, self.obj2, self.box]:
|
||||
o.location = self.box
|
||||
|
||||
_box_all()
|
||||
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint", "red steel", "steel"])
|
||||
self._check_room_contents({"steel": 2, "red steel": 1, "flint": 1})
|
||||
self._use(
|
||||
"steel-1, flint", "You try to utilize these but nothing happens ... something amiss?"
|
||||
)
|
||||
self._use("steel-1, flint, red steel, steel-3", "You are a Genius")
|
||||
self._check_room_contents({"smoke": 1, "fire": 1})
|
||||
_box_all()
|
||||
|
||||
self.fire.location = self.room1
|
||||
self.steel.location = self.room1
|
||||
|
||||
_puzzleedit("/delresult", recipe_dbref, ["fire"], "fire were removed from results")
|
||||
_puzzleedit(
|
||||
"/delpart", recipe_dbref, ["steel", "steel"], "steel, steel were removed from parts"
|
||||
)
|
||||
|
||||
_box_all()
|
||||
|
||||
self._arm(recipe_dbref, "makefire", ["flint", "red steel"])
|
||||
self._check_room_contents({"red steel": 1, "flint": 1})
|
||||
self._use("red steel, flint", "You are a Genius")
|
||||
self._check_room_contents({"smoke": 1, "fire": 0})
|
||||
|
||||
def test_lspuzzlerecipes_lsarmedpuzzles(self):
|
||||
msg = self.call(puzzles.CmdListPuzzleRecipes(), "", caller=self.char1)
|
||||
self._assert_msg_matched(
|
||||
msg, [r"^-+$", r"^Found 0 puzzle\(s\)\.$", r"-+$"], re.MULTILINE | re.DOTALL
|
||||
)
|
||||
|
||||
recipe_dbref = self._good_recipe(
|
||||
"makefire", ["steel", "flint"], ["fire"], and_destroy_it=False
|
||||
)
|
||||
|
||||
msg = self.call(puzzles.CmdListPuzzleRecipes(), "", caller=self.char1)
|
||||
self._assert_msg_matched(
|
||||
msg,
|
||||
[
|
||||
r"^-+$",
|
||||
r"^Puzzle 'makefire'.*$",
|
||||
r"^Success Caller message:$",
|
||||
r"^Success Location message:$",
|
||||
r"^Mask:$",
|
||||
r"^Parts$",
|
||||
r"^.*key: steel$",
|
||||
r"^.*key: flint$",
|
||||
r"^Results$",
|
||||
r"^.*key: fire$",
|
||||
r"^.*key: steel$",
|
||||
r"^.*key: flint$",
|
||||
r"^-+$",
|
||||
r"^Found 1 puzzle\(s\)\.$",
|
||||
r"^-+$",
|
||||
],
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
|
||||
msg = self.call(puzzles.CmdListArmedPuzzles(), "", caller=self.char1)
|
||||
self._assert_msg_matched(
|
||||
msg,
|
||||
[r"^-+$", r"^-+$", r"^Found 0 armed puzzle\(s\)\.$", r"^-+$"],
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
|
||||
self._arm(recipe_dbref, "makefire", ["steel", "flint"])
|
||||
|
||||
msg = self.call(puzzles.CmdListArmedPuzzles(), "", caller=self.char1)
|
||||
self._assert_msg_matched(
|
||||
msg,
|
||||
[
|
||||
r"^-+$",
|
||||
r"^Puzzle name: makefire$",
|
||||
r"^.*steel.* at \s+ Room.*$",
|
||||
r"^.*flint.* at \s+ Room.*$",
|
||||
r"^Found 1 armed puzzle\(s\)\.$",
|
||||
r"^-+$",
|
||||
],
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
|
||||
def test_e2e(self):
|
||||
def _destroy_objs_in_room(keys):
|
||||
for obj in self.room1.contents:
|
||||
if obj.key in keys:
|
||||
obj.delete()
|
||||
|
||||
# parts don't survive resolution
|
||||
# but produce a large result set
|
||||
tree = create_object(self.object_typeclass, key="tree", location=self.char1.location)
|
||||
axe = create_object(self.object_typeclass, key="axe", location=self.char1.location)
|
||||
sweat = create_object(self.object_typeclass, key="sweat", location=self.char1.location)
|
||||
dull_axe = create_object(
|
||||
self.object_typeclass, key="dull axe", location=self.char1.location
|
||||
)
|
||||
timber = create_object(self.object_typeclass, key="timber", location=self.char1.location)
|
||||
log = create_object(self.object_typeclass, key="log", location=self.char1.location)
|
||||
parts = ["tree", "axe"]
|
||||
results = (["sweat"] * 10) + ["dull axe"] + (["timber"] * 20) + (["log"] * 50)
|
||||
recipe_dbref = self._good_recipe("lumberjack", parts, results, and_destroy_it=False)
|
||||
|
||||
_destroy_objs_in_room(set(parts + results))
|
||||
|
||||
sps = sorted(parts)
|
||||
expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)}
|
||||
expected.update({r: 0 for r in set(results)})
|
||||
|
||||
self._arm(recipe_dbref, "lumberjack", parts)
|
||||
self._check_room_contents(expected)
|
||||
|
||||
self._use(",".join(parts), "You are a Genius")
|
||||
srs = sorted(set(results))
|
||||
expected = {(key, len(list(grp))) for key, grp in itertools.groupby(srs)}
|
||||
expected.update({p: 0 for p in set(parts)})
|
||||
self._check_room_contents(expected)
|
||||
|
||||
# parts also appear in results
|
||||
# causing a new puzzle to be armed 'automatically'
|
||||
# i.e. the puzzle is self-sustaining
|
||||
hole = create_object(self.object_typeclass, key="hole", location=self.char1.location)
|
||||
shovel = create_object(self.object_typeclass, key="shovel", location=self.char1.location)
|
||||
dirt = create_object(self.object_typeclass, key="dirt", location=self.char1.location)
|
||||
|
||||
parts = ["shovel", "hole"]
|
||||
results = ["dirt", "hole", "shovel"]
|
||||
recipe_dbref = self._good_recipe(
|
||||
"digger", parts, results, and_destroy_it=False, expected_count=2
|
||||
)
|
||||
|
||||
_destroy_objs_in_room(set(parts + results))
|
||||
|
||||
nresolutions = 0
|
||||
|
||||
sps = sorted(set(parts))
|
||||
expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)}
|
||||
expected.update({"dirt": nresolutions})
|
||||
|
||||
self._arm(recipe_dbref, "digger", parts)
|
||||
self._check_room_contents(expected)
|
||||
|
||||
for i in range(10):
|
||||
self._use(",".join(parts), "You are a Genius")
|
||||
nresolutions += 1
|
||||
expected.update({"dirt": nresolutions})
|
||||
self._check_room_contents(expected)
|
||||
|
||||
# Uppercase puzzle name
|
||||
balloon = create_object(self.object_typeclass, key="Balloon", location=self.char1.location)
|
||||
parts = ["Balloon"]
|
||||
results = ["Balloon"]
|
||||
recipe_dbref = self._good_recipe(
|
||||
"boom!!!", parts, results, and_destroy_it=False, expected_count=3
|
||||
)
|
||||
|
||||
_destroy_objs_in_room(set(parts + results))
|
||||
|
||||
sps = sorted(parts)
|
||||
expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)}
|
||||
|
||||
self._arm(recipe_dbref, "boom!!!", parts)
|
||||
self._check_room_contents(expected)
|
||||
|
||||
self._use(",".join(parts), "You are a Genius")
|
||||
srs = sorted(set(results))
|
||||
expected = {(key, len(list(grp))) for key, grp in itertools.groupby(srs)}
|
||||
self._check_room_contents(expected)
|
||||
|
||||
def test_e2e_accumulative(self):
|
||||
flashlight = create_object(
|
||||
self.object_typeclass, key="flashlight", location=self.char1.location
|
||||
)
|
||||
flashlight_w_1 = create_object(
|
||||
self.object_typeclass, key="flashlight-w-1", location=self.char1.location
|
||||
)
|
||||
flashlight_w_2 = create_object(
|
||||
self.object_typeclass, key="flashlight-w-2", location=self.char1.location
|
||||
)
|
||||
flashlight_w_3 = create_object(
|
||||
self.object_typeclass, key="flashlight-w-3", location=self.char1.location
|
||||
)
|
||||
battery = create_object(self.object_typeclass, key="battery", location=self.char1.location)
|
||||
|
||||
battery.tags.add("flashlight-1", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
battery.tags.add("flashlight-2", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
battery.tags.add("flashlight-3", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
|
||||
# TODO: instead of tagging each flashlight,
|
||||
# arm and resolve each puzzle in order so they all
|
||||
# are tagged correctly
|
||||
# it will be necessary to add/remove parts/results because
|
||||
# each battery is supposed to be consumed during resolution
|
||||
# as the new flashlight has one more battery than before
|
||||
flashlight_w_1.tags.add("flashlight-2", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
flashlight_w_2.tags.add("flashlight-3", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
|
||||
recipe_fl1_dbref = self._good_recipe(
|
||||
"flashlight-1",
|
||||
["flashlight", "battery"],
|
||||
["flashlight-w-1"],
|
||||
and_destroy_it=False,
|
||||
expected_count=1,
|
||||
)
|
||||
recipe_fl2_dbref = self._good_recipe(
|
||||
"flashlight-2",
|
||||
["flashlight-w-1", "battery"],
|
||||
["flashlight-w-2"],
|
||||
and_destroy_it=False,
|
||||
expected_count=2,
|
||||
)
|
||||
recipe_fl3_dbref = self._good_recipe(
|
||||
"flashlight-3",
|
||||
["flashlight-w-2", "battery"],
|
||||
["flashlight-w-3"],
|
||||
and_destroy_it=False,
|
||||
expected_count=3,
|
||||
)
|
||||
|
||||
# delete protoparts
|
||||
for obj in [battery, flashlight, flashlight_w_1, flashlight_w_2, flashlight_w_3]:
|
||||
obj.delete()
|
||||
|
||||
def _group_parts(parts, excluding=set()):
|
||||
group = dict()
|
||||
dbrefs = dict()
|
||||
for o in self.room1.contents:
|
||||
if o.key in parts and o.dbref not in excluding:
|
||||
if o.key not in group:
|
||||
group[o.key] = []
|
||||
group[o.key].append(o.dbref)
|
||||
dbrefs[o.dbref] = o
|
||||
return group, dbrefs
|
||||
|
||||
# arm each puzzle and group its parts
|
||||
self._arm(recipe_fl1_dbref, "flashlight-1", ["battery", "flashlight"])
|
||||
fl1_parts, fl1_dbrefs = _group_parts(["battery", "flashlight"])
|
||||
self._arm(recipe_fl2_dbref, "flashlight-2", ["battery", "flashlight-w-1"])
|
||||
fl2_parts, fl2_dbrefs = _group_parts(
|
||||
["battery", "flashlight-w-1"], excluding=list(fl1_dbrefs.keys())
|
||||
)
|
||||
self._arm(recipe_fl3_dbref, "flashlight-3", ["battery", "flashlight-w-2"])
|
||||
fl3_parts, fl3_dbrefs = _group_parts(
|
||||
["battery", "flashlight-w-2"],
|
||||
excluding=set(list(fl1_dbrefs.keys()) + list(fl2_dbrefs.keys())),
|
||||
)
|
||||
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 3,
|
||||
"flashlight": 1,
|
||||
"flashlight-w-1": 1,
|
||||
"flashlight-w-2": 1,
|
||||
"flashlight-w-3": 0,
|
||||
}
|
||||
)
|
||||
|
||||
# all batteries have identical protodefs
|
||||
battery_1 = fl1_dbrefs[fl1_parts["battery"][0]]
|
||||
battery_2 = fl2_dbrefs[fl2_parts["battery"][0]]
|
||||
battery_3 = fl3_dbrefs[fl3_parts["battery"][0]]
|
||||
protodef_battery_1 = puzzles.proto_def(battery_1, with_tags=False)
|
||||
del protodef_battery_1["prototype_key"]
|
||||
protodef_battery_2 = puzzles.proto_def(battery_2, with_tags=False)
|
||||
del protodef_battery_2["prototype_key"]
|
||||
protodef_battery_3 = puzzles.proto_def(battery_3, with_tags=False)
|
||||
del protodef_battery_3["prototype_key"]
|
||||
assert protodef_battery_1 == protodef_battery_2 == protodef_battery_3
|
||||
|
||||
# each battery can be used in every other puzzle
|
||||
|
||||
b1_parts_dict, b1_puzzlenames, b1_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[battery_1]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(b1_puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, b1_puzzlenames, b1_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
|
||||
b2_parts_dict, b2_puzzlenames, b2_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[battery_2]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(b2_puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, b2_puzzlenames, b2_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
b3_parts_dict, b3_puzzlenames, b3_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[battery_3]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(b3_puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, b3_puzzlenames, b3_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
|
||||
assert battery_1 == list(b1_parts_dict.values())[0]
|
||||
assert battery_2 == list(b2_parts_dict.values())[0]
|
||||
assert battery_3 == list(b3_parts_dict.values())[0]
|
||||
assert b1_puzzlenames.keys() == b2_puzzlenames.keys() == b3_puzzlenames.keys()
|
||||
for puzzle_name in ["flashlight-1", "flashlight-2", "flashlight-3"]:
|
||||
assert puzzle_name in b1_puzzlenames
|
||||
assert puzzle_name in b2_puzzlenames
|
||||
assert puzzle_name in b3_puzzlenames
|
||||
assert (
|
||||
list(b1_protodefs.values())[0]
|
||||
== list(b2_protodefs.values())[0]
|
||||
== list(b3_protodefs.values())[0]
|
||||
== protodef_battery_1
|
||||
== protodef_battery_2
|
||||
== protodef_battery_3
|
||||
)
|
||||
|
||||
# all flashlights have similar protodefs except their key
|
||||
flashlight_1 = fl1_dbrefs[fl1_parts["flashlight"][0]]
|
||||
flashlight_2 = fl2_dbrefs[fl2_parts["flashlight-w-1"][0]]
|
||||
flashlight_3 = fl3_dbrefs[fl3_parts["flashlight-w-2"][0]]
|
||||
protodef_flashlight_1 = puzzles.proto_def(flashlight_1, with_tags=False)
|
||||
del protodef_flashlight_1["prototype_key"]
|
||||
assert protodef_flashlight_1["key"] == "flashlight"
|
||||
del protodef_flashlight_1["key"]
|
||||
protodef_flashlight_2 = puzzles.proto_def(flashlight_2, with_tags=False)
|
||||
del protodef_flashlight_2["prototype_key"]
|
||||
assert protodef_flashlight_2["key"] == "flashlight-w-1"
|
||||
del protodef_flashlight_2["key"]
|
||||
protodef_flashlight_3 = puzzles.proto_def(flashlight_3, with_tags=False)
|
||||
del protodef_flashlight_3["prototype_key"]
|
||||
assert protodef_flashlight_3["key"] == "flashlight-w-2"
|
||||
del protodef_flashlight_3["key"]
|
||||
assert protodef_flashlight_1 == protodef_flashlight_2 == protodef_flashlight_3
|
||||
|
||||
# each flashlight can only be used in its own puzzle
|
||||
|
||||
f1_parts_dict, f1_puzzlenames, f1_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[flashlight_1]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(f1_puzzlenames.keys())
|
||||
assert set(["flashlight-1"]) == set([p.db.puzzle_name for p in _puzzles])
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, f1_puzzlenames, f1_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
f2_parts_dict, f2_puzzlenames, f2_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[flashlight_2]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(f2_puzzlenames.keys())
|
||||
assert set(["flashlight-2"]) == set([p.db.puzzle_name for p in _puzzles])
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, f2_puzzlenames, f2_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
f3_parts_dict, f3_puzzlenames, f3_protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[flashlight_3]
|
||||
)
|
||||
_puzzles = puzzles._puzzles_by_names(f3_puzzlenames.keys())
|
||||
assert set(["flashlight-3"]) == set([p.db.puzzle_name for p in _puzzles])
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, f3_puzzlenames, f3_protodefs)
|
||||
assert 0 == len(matched_puzzles)
|
||||
|
||||
assert flashlight_1 == list(f1_parts_dict.values())[0]
|
||||
assert flashlight_2 == list(f2_parts_dict.values())[0]
|
||||
assert flashlight_3 == list(f3_parts_dict.values())[0]
|
||||
for puzzle_name in set(
|
||||
list(f1_puzzlenames.keys()) + list(f2_puzzlenames.keys()) + list(f3_puzzlenames.keys())
|
||||
):
|
||||
assert puzzle_name in ["flashlight-1", "flashlight-2", "flashlight-3", "puzzle_member"]
|
||||
protodef_flashlight_1["key"] = "flashlight"
|
||||
assert list(f1_protodefs.values())[0] == protodef_flashlight_1
|
||||
protodef_flashlight_2["key"] = "flashlight-w-1"
|
||||
assert list(f2_protodefs.values())[0] == protodef_flashlight_2
|
||||
protodef_flashlight_3["key"] = "flashlight-w-2"
|
||||
assert list(f3_protodefs.values())[0] == protodef_flashlight_3
|
||||
|
||||
# each battery can be matched with every other flashlight
|
||||
# to potentially resolve each puzzle
|
||||
for batt in [battery_1, battery_2, battery_3]:
|
||||
parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[batt, flashlight_1]
|
||||
)
|
||||
assert set([batt.dbref, flashlight_1.dbref]) == set(puzzlenames["flashlight-1"])
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-2"])
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-3"])
|
||||
_puzzles = puzzles._puzzles_by_names(puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs)
|
||||
assert 1 == len(matched_puzzles)
|
||||
parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[batt, flashlight_2]
|
||||
)
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-1"])
|
||||
assert set([batt.dbref, flashlight_2.dbref]) == set(puzzlenames["flashlight-2"])
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-3"])
|
||||
_puzzles = puzzles._puzzles_by_names(puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs)
|
||||
assert 1 == len(matched_puzzles)
|
||||
parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs(
|
||||
[batt, flashlight_3]
|
||||
)
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-1"])
|
||||
assert set([batt.dbref]) == set(puzzlenames["flashlight-2"])
|
||||
assert set([batt.dbref, flashlight_3.dbref]) == set(puzzlenames["flashlight-3"])
|
||||
_puzzles = puzzles._puzzles_by_names(puzzlenames.keys())
|
||||
assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set(
|
||||
[p.db.puzzle_name for p in _puzzles]
|
||||
)
|
||||
matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs)
|
||||
assert 1 == len(matched_puzzles)
|
||||
|
||||
# delete all parts
|
||||
for part in (
|
||||
list(fl1_dbrefs.values()) + list(fl2_dbrefs.values()) + list(fl3_dbrefs.values())
|
||||
):
|
||||
part.delete()
|
||||
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 0,
|
||||
"flashlight": 0,
|
||||
"flashlight-w-1": 0,
|
||||
"flashlight-w-2": 0,
|
||||
"flashlight-w-3": 0,
|
||||
}
|
||||
)
|
||||
|
||||
# arm first puzzle 3 times and group its parts so we can solve
|
||||
# all puzzles with the parts from the 1st armed
|
||||
for i in range(3):
|
||||
self._arm(recipe_fl1_dbref, "flashlight-1", ["battery", "flashlight"])
|
||||
fl1_parts, fl1_dbrefs = _group_parts(["battery", "flashlight"])
|
||||
|
||||
# delete the 2 extra flashlights so we can start solving
|
||||
for flashlight_dbref in fl1_parts["flashlight"][1:]:
|
||||
fl1_dbrefs[flashlight_dbref].delete()
|
||||
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 3,
|
||||
"flashlight": 1,
|
||||
"flashlight-w-1": 0,
|
||||
"flashlight-w-2": 0,
|
||||
"flashlight-w-3": 0,
|
||||
}
|
||||
)
|
||||
|
||||
self._use("battery-1, flashlight", "You are a Genius")
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 2,
|
||||
"flashlight": 0,
|
||||
"flashlight-w-1": 1,
|
||||
"flashlight-w-2": 0,
|
||||
"flashlight-w-3": 0,
|
||||
}
|
||||
)
|
||||
|
||||
self._use("battery-1, flashlight-w-1", "You are a Genius")
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 1,
|
||||
"flashlight": 0,
|
||||
"flashlight-w-1": 0,
|
||||
"flashlight-w-2": 1,
|
||||
"flashlight-w-3": 0,
|
||||
}
|
||||
)
|
||||
|
||||
self._use("battery, flashlight-w-2", "You are a Genius")
|
||||
self._check_room_contents(
|
||||
{
|
||||
"battery": 0,
|
||||
"flashlight": 0,
|
||||
"flashlight-w-1": 0,
|
||||
"flashlight-w-2": 0,
|
||||
"flashlight-w-3": 1,
|
||||
}
|
||||
)
|
||||
|
||||
def test_e2e_interchangeable_parts_and_results(self):
|
||||
# Parts and Results can be used in multiple puzzles
|
||||
egg = create_object(self.object_typeclass, key="egg", location=self.char1.location)
|
||||
flour = create_object(self.object_typeclass, key="flour", location=self.char1.location)
|
||||
boiling_water = create_object(
|
||||
self.object_typeclass, key="boiling water", location=self.char1.location
|
||||
)
|
||||
boiled_egg = create_object(
|
||||
self.object_typeclass, key="boiled egg", location=self.char1.location
|
||||
)
|
||||
dough = create_object(self.object_typeclass, key="dough", location=self.char1.location)
|
||||
pasta = create_object(self.object_typeclass, key="pasta", location=self.char1.location)
|
||||
|
||||
# Three recipes:
|
||||
# 1. breakfast: egg + boiling water = boiled egg & boiling water
|
||||
# 2. dough: egg + flour = dough
|
||||
# 3. entree: dough + boiling water = pasta & boiling water
|
||||
# tag interchangeable parts according to their puzzles' name
|
||||
egg.tags.add("breakfast", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
egg.tags.add("dough", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
dough.tags.add("entree", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
boiling_water.tags.add("breakfast", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
boiling_water.tags.add("entree", category=puzzles._PUZZLES_TAG_CATEGORY)
|
||||
|
||||
# create recipes
|
||||
recipe1_dbref = self._good_recipe(
|
||||
"breakfast",
|
||||
["egg", "boiling water"],
|
||||
["boiled egg", "boiling water"],
|
||||
and_destroy_it=False,
|
||||
)
|
||||
recipe2_dbref = self._good_recipe(
|
||||
"dough", ["egg", "flour"], ["dough"], and_destroy_it=False, expected_count=2
|
||||
)
|
||||
recipe3_dbref = self._good_recipe(
|
||||
"entree",
|
||||
["dough", "boiling water"],
|
||||
["pasta", "boiling water"],
|
||||
and_destroy_it=False,
|
||||
expected_count=3,
|
||||
)
|
||||
|
||||
# delete protoparts
|
||||
for obj in [egg, flour, boiling_water, boiled_egg, dough, pasta]:
|
||||
obj.delete()
|
||||
|
||||
# arm each puzzle and group its parts
|
||||
def _group_parts(parts, excluding=set()):
|
||||
group = dict()
|
||||
dbrefs = dict()
|
||||
for o in self.room1.contents:
|
||||
if o.key in parts and o.dbref not in excluding:
|
||||
if o.key not in group:
|
||||
group[o.key] = []
|
||||
group[o.key].append(o.dbref)
|
||||
dbrefs[o.dbref] = o
|
||||
return group, dbrefs
|
||||
|
||||
self._arm(recipe1_dbref, "breakfast", ["egg", "boiling water"])
|
||||
breakfast_parts, breakfast_dbrefs = _group_parts(["egg", "boiling water"])
|
||||
self._arm(recipe2_dbref, "dough", ["egg", "flour"])
|
||||
dough_parts, dough_dbrefs = _group_parts(
|
||||
["egg", "flour"], excluding=list(breakfast_dbrefs.keys())
|
||||
)
|
||||
self._arm(recipe3_dbref, "entree", ["dough", "boiling water"])
|
||||
entree_parts, entree_dbrefs = _group_parts(
|
||||
["dough", "boiling water"],
|
||||
excluding=set(list(breakfast_dbrefs.keys()) + list(dough_dbrefs.keys())),
|
||||
)
|
||||
|
||||
# create a box so we can put all objects in
|
||||
# so that they can't be found during puzzle resolution
|
||||
self.box = create_object(self.object_typeclass, key="box", location=self.char1.location)
|
||||
|
||||
def _box_all():
|
||||
# print "boxing all\n", "-"*20
|
||||
for o in self.room1.contents:
|
||||
if o not in [self.char1, self.char2, self.exit, self.obj1, self.obj2, self.box]:
|
||||
o.location = self.box
|
||||
# print o.key, o.dbref, "boxed"
|
||||
else:
|
||||
# print "skipped", o.key, o.dbref
|
||||
pass
|
||||
|
||||
def _unbox(dbrefs):
|
||||
# print "unboxing", dbrefs, "\n", "-"*20
|
||||
for o in self.box.contents:
|
||||
if o.dbref in dbrefs:
|
||||
o.location = self.room1
|
||||
# print "unboxed", o.key, o.dbref
|
||||
|
||||
# solve dough puzzle using breakfast's egg
|
||||
# and dough's flour. A new dough will be created
|
||||
_box_all()
|
||||
_unbox(breakfast_parts.pop("egg") + dough_parts.pop("flour"))
|
||||
self._use("egg, flour", "You are a Genius")
|
||||
|
||||
# solve entree puzzle with newly created dough
|
||||
# and breakfast's boiling water. A new
|
||||
# boiling water and pasta will be created
|
||||
_unbox(breakfast_parts.pop("boiling water"))
|
||||
self._use("boiling water, dough", "You are a Genius")
|
||||
|
||||
# solve breakfast puzzle with dough's egg
|
||||
# and newly created boiling water. A new
|
||||
# boiling water and boiled egg will be created
|
||||
_unbox(dough_parts.pop("egg"))
|
||||
self._use("boiling water, egg", "You are a Genius")
|
||||
|
||||
# solve entree puzzle using entree's dough
|
||||
# and newly created boiling water. A new
|
||||
# boiling water and pasta will be created
|
||||
_unbox(entree_parts.pop("dough"))
|
||||
self._use("boiling water, dough", "You are a Genius")
|
||||
|
||||
self._check_room_contents({"boiling water": 1, "pasta": 2, "boiled egg": 1})
|
||||
603
evennia/contrib/game_systems/turnbattle/tests.py
Normal file
603
evennia/contrib/game_systems/turnbattle/tests.py
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
"""
|
||||
Turnbattle tests.
|
||||
|
||||
"""
|
||||
|
||||
from mock import patch, MagicMock
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from . import tb_basic, tb_equip, tb_range, tb_items, tb_magic
|
||||
|
||||
|
||||
class TestTurnBattleBasicCmd(CommandTest):
|
||||
|
||||
# Test basic combat commands
|
||||
def test_turnbattlecmd(self):
|
||||
self.call(tb_basic.CmdFight(), "", "You can't start a fight if you've been defeated!")
|
||||
self.call(tb_basic.CmdAttack(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_basic.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_basic.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_basic.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleEquipCmd(CommandTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleEquipCmd, self).setUp()
|
||||
self.testweapon = create_object(tb_equip.TBEWeapon, key="test weapon")
|
||||
self.testarmor = create_object(tb_equip.TBEArmor, key="test armor")
|
||||
self.testweapon.move_to(self.char1)
|
||||
self.testarmor.move_to(self.char1)
|
||||
|
||||
# Test equipment commands
|
||||
def test_turnbattleequipcmd(self):
|
||||
# Start with equip module specific commands.
|
||||
self.call(tb_equip.CmdWield(), "weapon", "Char wields test weapon.")
|
||||
self.call(tb_equip.CmdUnwield(), "", "Char lowers test weapon.")
|
||||
self.call(tb_equip.CmdDon(), "armor", "Char dons test armor.")
|
||||
self.call(tb_equip.CmdDoff(), "", "Char removes test armor.")
|
||||
# Also test the commands that are the same in the basic module
|
||||
self.call(tb_equip.CmdFight(), "", "You can't start a fight if you've been defeated!")
|
||||
self.call(tb_equip.CmdAttack(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_equip.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_equip.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_equip.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleRangeCmd(CommandTest):
|
||||
# Test range commands
|
||||
def test_turnbattlerangecmd(self):
|
||||
# Start with range module specific commands.
|
||||
self.call(tb_range.CmdShoot(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdApproach(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdWithdraw(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdStatus(), "", "HP Remaining: 100 / 100")
|
||||
# Also test the commands that are the same in the basic module
|
||||
self.call(tb_range.CmdFight(), "", "There's nobody here to fight!")
|
||||
self.call(tb_range.CmdAttack(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_range.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleItemsCmd(CommandTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleItemsCmd, self).setUp()
|
||||
self.testitem = create_object(key="test item")
|
||||
self.testitem.move_to(self.char1)
|
||||
|
||||
# Test item commands
|
||||
def test_turnbattleitemcmd(self):
|
||||
self.call(tb_items.CmdUse(), "item", "'Test item' is not a usable item.")
|
||||
# Also test the commands that are the same in the basic module
|
||||
self.call(tb_items.CmdFight(), "", "You can't start a fight if you've been defeated!")
|
||||
self.call(tb_items.CmdAttack(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_items.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_items.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_items.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleMagicCmd(CommandTest):
|
||||
|
||||
# Test magic commands
|
||||
def test_turnbattlemagiccmd(self):
|
||||
self.call(tb_magic.CmdStatus(), "", "You have 100 / 100 HP and 20 / 20 MP.")
|
||||
self.call(tb_magic.CmdLearnSpell(), "test spell", "There is no spell with that name.")
|
||||
self.call(tb_magic.CmdCast(), "", "Usage: cast <spell name> = <target>, <target2>")
|
||||
# Also test the commands that are the same in the basic module
|
||||
self.call(tb_magic.CmdFight(), "", "There's nobody here to fight!")
|
||||
self.call(tb_magic.CmdAttack(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_magic.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_magic.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(tb_magic.CmdRest(), "", "Char rests to recover HP and MP.")
|
||||
|
||||
|
||||
class TestTurnBattleBasicFunc(EvenniaTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleBasicFunc, self).setUp()
|
||||
self.testroom = create_object(DefaultRoom, key="Test Room")
|
||||
self.attacker = create_object(
|
||||
tb_basic.TBBasicCharacter, key="Attacker", location=self.testroom
|
||||
)
|
||||
self.defender = create_object(
|
||||
tb_basic.TBBasicCharacter, key="Defender", location=self.testroom
|
||||
)
|
||||
self.joiner = create_object(tb_basic.TBBasicCharacter, key="Joiner", location=None)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTurnBattleBasicFunc, self).tearDown()
|
||||
self.turnhandler.stop()
|
||||
self.testroom.delete()
|
||||
self.attacker.delete()
|
||||
self.defender.delete()
|
||||
self.joiner.delete()
|
||||
|
||||
# Test combat functions
|
||||
def test_tbbasicfunc(self):
|
||||
# Initiative roll
|
||||
initiative = tb_basic.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_basic.get_attack(self.attacker, self.defender)
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_basic.get_defense(self.attacker, self.defender)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_basic.get_damage(self.attacker, self.defender)
|
||||
self.assertTrue(damage_roll >= 15 and damage_roll <= 25)
|
||||
# Apply damage
|
||||
self.defender.db.hp = 10
|
||||
tb_basic.apply_damage(self.defender, 3)
|
||||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_basic.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
tb_basic.combat_cleanup(self.attacker)
|
||||
self.assertFalse(self.attacker.db.combat_attribute)
|
||||
# Is in combat
|
||||
self.assertFalse(tb_basic.is_in_combat(self.attacker))
|
||||
# Set up turn handler script for further tests
|
||||
self.attacker.location.scripts.add(tb_basic.TBBasicTurnHandler)
|
||||
self.turnhandler = self.attacker.db.combat_TurnHandler
|
||||
self.assertTrue(self.attacker.db.combat_TurnHandler)
|
||||
# Set the turn handler's interval very high to keep it from repeating during tests.
|
||||
self.turnhandler.interval = 10000
|
||||
# Force turn order
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
# Test is turn
|
||||
self.assertTrue(tb_basic.is_turn(self.attacker))
|
||||
# Spend actions
|
||||
self.attacker.db.Combat_ActionsLeft = 1
|
||||
tb_basic.spend_action(self.attacker, 1, action_name="Test")
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "Test")
|
||||
# Initialize for combat
|
||||
self.attacker.db.Combat_ActionsLeft = 983
|
||||
self.turnhandler.initialize_for_combat(self.attacker)
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "null")
|
||||
# Start turn
|
||||
self.defender.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.start_turn(self.defender)
|
||||
self.assertTrue(self.defender.db.Combat_ActionsLeft == 1)
|
||||
# Next turn
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.next_turn()
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Turn end check
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.attacker.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.turn_end_check(self.attacker)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Join fight
|
||||
self.joiner.location = self.testroom
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.join_fight(self.joiner)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender])
|
||||
|
||||
|
||||
class TestTurnBattleEquipFunc(EvenniaTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleEquipFunc, self).setUp()
|
||||
self.testroom = create_object(DefaultRoom, key="Test Room")
|
||||
self.attacker = create_object(
|
||||
tb_equip.TBEquipCharacter, key="Attacker", location=self.testroom
|
||||
)
|
||||
self.defender = create_object(
|
||||
tb_equip.TBEquipCharacter, key="Defender", location=self.testroom
|
||||
)
|
||||
self.joiner = create_object(tb_equip.TBEquipCharacter, key="Joiner", location=None)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTurnBattleEquipFunc, self).tearDown()
|
||||
self.turnhandler.stop()
|
||||
self.testroom.delete()
|
||||
self.attacker.delete()
|
||||
self.defender.delete()
|
||||
self.joiner.delete()
|
||||
|
||||
# Test the combat functions in tb_equip too. They work mostly the same.
|
||||
def test_tbequipfunc(self):
|
||||
# Initiative roll
|
||||
initiative = tb_equip.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_equip.get_attack(self.attacker, self.defender)
|
||||
self.assertTrue(attack_roll >= -50 and attack_roll <= 150)
|
||||
# Defense roll
|
||||
defense_roll = tb_equip.get_defense(self.attacker, self.defender)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_equip.get_damage(self.attacker, self.defender)
|
||||
self.assertTrue(damage_roll >= 0 and damage_roll <= 50)
|
||||
# Apply damage
|
||||
self.defender.db.hp = 10
|
||||
tb_equip.apply_damage(self.defender, 3)
|
||||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_equip.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
tb_equip.combat_cleanup(self.attacker)
|
||||
self.assertFalse(self.attacker.db.combat_attribute)
|
||||
# Is in combat
|
||||
self.assertFalse(tb_equip.is_in_combat(self.attacker))
|
||||
# Set up turn handler script for further tests
|
||||
self.attacker.location.scripts.add(tb_equip.TBEquipTurnHandler)
|
||||
self.turnhandler = self.attacker.db.combat_TurnHandler
|
||||
self.assertTrue(self.attacker.db.combat_TurnHandler)
|
||||
# Set the turn handler's interval very high to keep it from repeating during tests.
|
||||
self.turnhandler.interval = 10000
|
||||
# Force turn order
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
# Test is turn
|
||||
self.assertTrue(tb_equip.is_turn(self.attacker))
|
||||
# Spend actions
|
||||
self.attacker.db.Combat_ActionsLeft = 1
|
||||
tb_equip.spend_action(self.attacker, 1, action_name="Test")
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "Test")
|
||||
# Initialize for combat
|
||||
self.attacker.db.Combat_ActionsLeft = 983
|
||||
self.turnhandler.initialize_for_combat(self.attacker)
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "null")
|
||||
# Start turn
|
||||
self.defender.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.start_turn(self.defender)
|
||||
self.assertTrue(self.defender.db.Combat_ActionsLeft == 1)
|
||||
# Next turn
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.next_turn()
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Turn end check
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.attacker.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.turn_end_check(self.attacker)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Join fight
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.join_fight(self.joiner)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender])
|
||||
|
||||
|
||||
class TestTurnBattleRangeFunc(EvenniaTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleRangeFunc, self).setUp()
|
||||
self.testroom = create_object(DefaultRoom, key="Test Room")
|
||||
self.attacker = create_object(
|
||||
tb_range.TBRangeCharacter, key="Attacker", location=self.testroom
|
||||
)
|
||||
self.defender = create_object(
|
||||
tb_range.TBRangeCharacter, key="Defender", location=self.testroom
|
||||
)
|
||||
self.joiner = create_object(tb_range.TBRangeCharacter, key="Joiner", location=self.testroom)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTurnBattleRangeFunc, self).tearDown()
|
||||
self.turnhandler.stop()
|
||||
self.testroom.delete()
|
||||
self.attacker.delete()
|
||||
self.defender.delete()
|
||||
self.joiner.delete()
|
||||
|
||||
# Test combat functions in tb_range too.
|
||||
def test_tbrangefunc(self):
|
||||
# Initiative roll
|
||||
initiative = tb_range.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_range.get_attack(self.attacker, self.defender, "test")
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_range.get_defense(self.attacker, self.defender, "test")
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_range.get_damage(self.attacker, self.defender)
|
||||
self.assertTrue(damage_roll >= 15 and damage_roll <= 25)
|
||||
# Apply damage
|
||||
self.defender.db.hp = 10
|
||||
tb_range.apply_damage(self.defender, 3)
|
||||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_range.resolve_attack(
|
||||
self.attacker, self.defender, "test", attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
tb_range.combat_cleanup(self.attacker)
|
||||
self.assertFalse(self.attacker.db.combat_attribute)
|
||||
# Is in combat
|
||||
self.assertFalse(tb_range.is_in_combat(self.attacker))
|
||||
# Set up turn handler script for further tests
|
||||
self.attacker.location.scripts.add(tb_range.TBRangeTurnHandler)
|
||||
self.turnhandler = self.attacker.db.combat_TurnHandler
|
||||
self.assertTrue(self.attacker.db.combat_TurnHandler)
|
||||
# Set the turn handler's interval very high to keep it from repeating during tests.
|
||||
self.turnhandler.interval = 10000
|
||||
# Force turn order
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
# Test is turn
|
||||
self.assertTrue(tb_range.is_turn(self.attacker))
|
||||
# Spend actions
|
||||
self.attacker.db.Combat_ActionsLeft = 1
|
||||
tb_range.spend_action(self.attacker, 1, action_name="Test")
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "Test")
|
||||
# Initialize for combat
|
||||
self.attacker.db.Combat_ActionsLeft = 983
|
||||
self.turnhandler.initialize_for_combat(self.attacker)
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "null")
|
||||
# Set up ranges again, since initialize_for_combat clears them
|
||||
self.attacker.db.combat_range = {}
|
||||
self.attacker.db.combat_range[self.attacker] = 0
|
||||
self.attacker.db.combat_range[self.defender] = 1
|
||||
self.defender.db.combat_range = {}
|
||||
self.defender.db.combat_range[self.defender] = 0
|
||||
self.defender.db.combat_range[self.attacker] = 1
|
||||
# Start turn
|
||||
self.defender.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.start_turn(self.defender)
|
||||
self.assertTrue(self.defender.db.Combat_ActionsLeft == 2)
|
||||
# Next turn
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.next_turn()
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Turn end check
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.attacker.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.turn_end_check(self.attacker)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Join fight
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.join_fight(self.joiner)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender])
|
||||
# Now, test for approach/withdraw functions
|
||||
self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 1)
|
||||
# Approach
|
||||
tb_range.approach(self.attacker, self.defender)
|
||||
self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 0)
|
||||
# Withdraw
|
||||
tb_range.withdraw(self.attacker, self.defender)
|
||||
self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 1)
|
||||
|
||||
|
||||
class TestTurnBattleItemsFunc(EvenniaTest):
|
||||
@patch("evennia.contrib.turnbattle.tb_items.tickerhandler", new=MagicMock())
|
||||
def setUp(self):
|
||||
super(TestTurnBattleItemsFunc, self).setUp()
|
||||
self.testroom = create_object(DefaultRoom, key="Test Room")
|
||||
self.attacker = create_object(
|
||||
tb_items.TBItemsCharacter, key="Attacker", location=self.testroom
|
||||
)
|
||||
self.defender = create_object(
|
||||
tb_items.TBItemsCharacter, key="Defender", location=self.testroom
|
||||
)
|
||||
self.joiner = create_object(tb_items.TBItemsCharacter, key="Joiner", location=self.testroom)
|
||||
self.user = create_object(tb_items.TBItemsCharacter, key="User", location=self.testroom)
|
||||
self.test_healpotion = create_object(key="healing potion")
|
||||
self.test_healpotion.db.item_func = "heal"
|
||||
self.test_healpotion.db.item_uses = 3
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTurnBattleItemsFunc, self).tearDown()
|
||||
self.turnhandler.stop()
|
||||
self.testroom.delete()
|
||||
self.attacker.delete()
|
||||
self.defender.delete()
|
||||
self.joiner.delete()
|
||||
self.user.delete()
|
||||
|
||||
# Test functions in tb_items.
|
||||
def test_tbitemsfunc(self):
|
||||
# Initiative roll
|
||||
initiative = tb_items.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_items.get_attack(self.attacker, self.defender)
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_items.get_defense(self.attacker, self.defender)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_items.get_damage(self.attacker, self.defender)
|
||||
self.assertTrue(damage_roll >= 15 and damage_roll <= 25)
|
||||
# Apply damage
|
||||
self.defender.db.hp = 10
|
||||
tb_items.apply_damage(self.defender, 3)
|
||||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_items.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
tb_items.combat_cleanup(self.attacker)
|
||||
self.assertFalse(self.attacker.db.combat_attribute)
|
||||
# Is in combat
|
||||
self.assertFalse(tb_items.is_in_combat(self.attacker))
|
||||
# Set up turn handler script for further tests
|
||||
self.attacker.location.scripts.add(tb_items.TBItemsTurnHandler)
|
||||
self.turnhandler = self.attacker.db.combat_TurnHandler
|
||||
self.assertTrue(self.attacker.db.combat_TurnHandler)
|
||||
# Set the turn handler's interval very high to keep it from repeating during tests.
|
||||
self.turnhandler.interval = 10000
|
||||
# Force turn order
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
# Test is turn
|
||||
self.assertTrue(tb_items.is_turn(self.attacker))
|
||||
# Spend actions
|
||||
self.attacker.db.Combat_ActionsLeft = 1
|
||||
tb_items.spend_action(self.attacker, 1, action_name="Test")
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "Test")
|
||||
# Initialize for combat
|
||||
self.attacker.db.Combat_ActionsLeft = 983
|
||||
self.turnhandler.initialize_for_combat(self.attacker)
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "null")
|
||||
# Start turn
|
||||
self.defender.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.start_turn(self.defender)
|
||||
self.assertTrue(self.defender.db.Combat_ActionsLeft == 1)
|
||||
# Next turn
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.next_turn()
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Turn end check
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.attacker.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.turn_end_check(self.attacker)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Join fight
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.join_fight(self.joiner)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender])
|
||||
# Now time to test item stuff.
|
||||
# Spend item use
|
||||
tb_items.spend_item_use(self.test_healpotion, self.user)
|
||||
self.assertTrue(self.test_healpotion.db.item_uses == 2)
|
||||
# Use item
|
||||
self.user.db.hp = 2
|
||||
tb_items.use_item(self.user, self.test_healpotion, self.user)
|
||||
self.assertTrue(self.user.db.hp > 2)
|
||||
# Add contition
|
||||
tb_items.add_condition(self.user, self.user, "Test", 5)
|
||||
self.assertTrue(self.user.db.conditions == {"Test": [5, self.user]})
|
||||
# Condition tickdown
|
||||
tb_items.condition_tickdown(self.user, self.user)
|
||||
self.assertTrue(self.user.db.conditions == {"Test": [4, self.user]})
|
||||
# Test item functions now!
|
||||
# Item heal
|
||||
self.user.db.hp = 2
|
||||
tb_items.itemfunc_heal(self.test_healpotion, self.user, self.user)
|
||||
# Item add condition
|
||||
self.user.db.conditions = {}
|
||||
tb_items.itemfunc_add_condition(self.test_healpotion, self.user, self.user)
|
||||
self.assertTrue(self.user.db.conditions == {"Regeneration": [5, self.user]})
|
||||
# Item cure condition
|
||||
self.user.db.conditions = {"Poisoned": [5, self.user]}
|
||||
tb_items.itemfunc_cure_condition(self.test_healpotion, self.user, self.user)
|
||||
self.assertTrue(self.user.db.conditions == {})
|
||||
|
||||
|
||||
class TestTurnBattleMagicFunc(EvenniaTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleMagicFunc, self).setUp()
|
||||
self.testroom = create_object(DefaultRoom, key="Test Room")
|
||||
self.attacker = create_object(
|
||||
tb_magic.TBMagicCharacter, key="Attacker", location=self.testroom
|
||||
)
|
||||
self.defender = create_object(
|
||||
tb_magic.TBMagicCharacter, key="Defender", location=self.testroom
|
||||
)
|
||||
self.joiner = create_object(tb_magic.TBMagicCharacter, key="Joiner", location=self.testroom)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTurnBattleMagicFunc, self).tearDown()
|
||||
self.turnhandler.stop()
|
||||
self.testroom.delete()
|
||||
self.attacker.delete()
|
||||
self.defender.delete()
|
||||
self.joiner.delete()
|
||||
|
||||
# Test combat functions in tb_magic.
|
||||
def test_tbbasicfunc(self):
|
||||
# Initiative roll
|
||||
initiative = tb_magic.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_magic.get_attack(self.attacker, self.defender)
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_magic.get_defense(self.attacker, self.defender)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_magic.get_damage(self.attacker, self.defender)
|
||||
self.assertTrue(damage_roll >= 15 and damage_roll <= 25)
|
||||
# Apply damage
|
||||
self.defender.db.hp = 10
|
||||
tb_magic.apply_damage(self.defender, 3)
|
||||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_magic.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
tb_magic.combat_cleanup(self.attacker)
|
||||
self.assertFalse(self.attacker.db.combat_attribute)
|
||||
# Is in combat
|
||||
self.assertFalse(tb_magic.is_in_combat(self.attacker))
|
||||
# Set up turn handler script for further tests
|
||||
self.attacker.location.scripts.add(tb_magic.TBMagicTurnHandler)
|
||||
self.turnhandler = self.attacker.db.combat_TurnHandler
|
||||
self.assertTrue(self.attacker.db.combat_TurnHandler)
|
||||
# Set the turn handler's interval very high to keep it from repeating during tests.
|
||||
self.turnhandler.interval = 10000
|
||||
# Force turn order
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
# Test is turn
|
||||
self.assertTrue(tb_magic.is_turn(self.attacker))
|
||||
# Spend actions
|
||||
self.attacker.db.Combat_ActionsLeft = 1
|
||||
tb_magic.spend_action(self.attacker, 1, action_name="Test")
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "Test")
|
||||
# Initialize for combat
|
||||
self.attacker.db.Combat_ActionsLeft = 983
|
||||
self.turnhandler.initialize_for_combat(self.attacker)
|
||||
self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0)
|
||||
self.assertTrue(self.attacker.db.Combat_LastAction == "null")
|
||||
# Start turn
|
||||
self.defender.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.start_turn(self.defender)
|
||||
self.assertTrue(self.defender.db.Combat_ActionsLeft == 1)
|
||||
# Next turn
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.next_turn()
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Turn end check
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.attacker.db.Combat_ActionsLeft = 0
|
||||
self.turnhandler.turn_end_check(self.attacker)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
# Join fight
|
||||
self.turnhandler.db.fighters = [self.attacker, self.defender]
|
||||
self.turnhandler.db.turn = 0
|
||||
self.turnhandler.join_fight(self.joiner)
|
||||
self.assertTrue(self.turnhandler.db.turn == 1)
|
||||
self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender])
|
||||
103
evennia/contrib/grid/extended_room/tests.py
Normal file
103
evennia/contrib/grid/extended_room/tests.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
Testing of ExtendedRoom contrib
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from mock import patch, Mock
|
||||
from django.conf import settings
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from . import extended_room
|
||||
|
||||
|
||||
class ForceUTCDatetime(datetime.datetime):
|
||||
|
||||
"""Force UTC datetime."""
|
||||
|
||||
@classmethod
|
||||
def fromtimestamp(cls, timestamp):
|
||||
"""Force fromtimestamp to run with naive datetimes."""
|
||||
return datetime.datetime.utcfromtimestamp(timestamp)
|
||||
|
||||
|
||||
@patch("evennia.contrib.extended_room.datetime.datetime", ForceUTCDatetime)
|
||||
# mock gametime to return April 9, 2064, at 21:06 (spring evening)
|
||||
@patch("evennia.utils.gametime.gametime", new=Mock(return_value=2975000766))
|
||||
class TestExtendedRoom(CommandTest):
|
||||
room_typeclass = extended_room.ExtendedRoom
|
||||
DETAIL_DESC = "A test detail."
|
||||
SPRING_DESC = "A spring description."
|
||||
OLD_DESC = "Old description."
|
||||
settings.TIME_ZONE = "UTC"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.room1.ndb.last_timeslot = "afternoon"
|
||||
self.room1.ndb.last_season = "winter"
|
||||
self.room1.db.details = {"testdetail": self.DETAIL_DESC}
|
||||
self.room1.db.spring_desc = self.SPRING_DESC
|
||||
self.room1.db.desc = self.OLD_DESC
|
||||
|
||||
def test_return_appearance(self):
|
||||
# get the appearance of a non-extended room for contrast purposes
|
||||
old_desc = DefaultRoom.return_appearance(self.room1, self.char1)
|
||||
# the new appearance should be the old one, but with the desc switched
|
||||
self.assertEqual(
|
||||
old_desc.replace(self.OLD_DESC, self.SPRING_DESC),
|
||||
self.room1.return_appearance(self.char1),
|
||||
)
|
||||
self.assertEqual("spring", self.room1.ndb.last_season)
|
||||
self.assertEqual("evening", self.room1.ndb.last_timeslot)
|
||||
|
||||
def test_return_detail(self):
|
||||
self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail"))
|
||||
|
||||
def test_cmdextendedlook(self):
|
||||
rid = self.room1.id
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomLook(),
|
||||
"here",
|
||||
"Room(#{})\n{}".format(rid, self.SPRING_DESC),
|
||||
)
|
||||
self.call(extended_room.CmdExtendedRoomLook(), "testdetail", self.DETAIL_DESC)
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomLook(), "nonexistent", "Could not find 'nonexistent'."
|
||||
)
|
||||
|
||||
def test_cmdsetdetail(self):
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "", "Details on Room")
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomDetail(),
|
||||
"thingie = newdetail with spaces",
|
||||
"Detail set 'thingie': 'newdetail with spaces'",
|
||||
)
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "thingie", "Detail 'thingie' on Room:\n")
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomDetail(),
|
||||
"/del thingie",
|
||||
"Detail thingie deleted, if it existed.",
|
||||
cmdstring="detail",
|
||||
)
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "thingie", "Detail 'thingie' not found.")
|
||||
|
||||
# Test with aliases
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "", "Details on Room")
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomDetail(),
|
||||
"thingie;other;stuff = newdetail with spaces",
|
||||
"Detail set 'thingie;other;stuff': 'newdetail with spaces'",
|
||||
)
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "thingie", "Detail 'thingie' on Room:\n")
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "other", "Detail 'other' on Room:\n")
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "stuff", "Detail 'stuff' on Room:\n")
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomDetail(),
|
||||
"/del other;stuff",
|
||||
"Detail other;stuff deleted, if it existed.",
|
||||
)
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "other", "Detail 'other' not found.")
|
||||
self.call(extended_room.CmdExtendedRoomDetail(), "stuff", "Detail 'stuff' not found.")
|
||||
|
||||
def test_cmdgametime(self):
|
||||
self.call(extended_room.CmdExtendedRoomGameTime(), "", "It's a spring day, in the evening.")
|
||||
33
evennia/contrib/grid/mapbuilder/tests.py
Normal file
33
evennia/contrib/grid/mapbuilder/tests.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
Test map builder.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import mapbuilder
|
||||
|
||||
|
||||
class TestMapBuilder(CommandTest):
|
||||
def test_cmdmapbuilder(self):
|
||||
self.call(
|
||||
mapbuilder.CmdMapBuilder(),
|
||||
"evennia.contrib.mapbuilder.EXAMPLE1_MAP evennia.contrib.mapbuilder.EXAMPLE1_LEGEND",
|
||||
"""Creating Map...|≈≈≈≈≈
|
||||
≈♣n♣≈
|
||||
≈∩▲∩≈
|
||||
≈♠n♠≈
|
||||
≈≈≈≈≈
|
||||
|Creating Landmass...|""",
|
||||
)
|
||||
self.call(
|
||||
mapbuilder.CmdMapBuilder(),
|
||||
"evennia.contrib.mapbuilder.EXAMPLE2_MAP evennia.contrib.mapbuilder.EXAMPLE2_LEGEND",
|
||||
"""Creating Map...|≈ ≈ ≈ ≈ ≈
|
||||
|
||||
≈ ♣-♣-♣ ≈
|
||||
≈ ♣ ♣ ♣ ≈
|
||||
≈ ♣-♣-♣ ≈
|
||||
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
|Creating Landmass...|""",
|
||||
)
|
||||
29
evennia/contrib/grid/simpledoor/tests.py
Normal file
29
evennia/contrib/grid/simpledoor/tests.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""
|
||||
Tests of simpledoor.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from . import simpledoor
|
||||
|
||||
|
||||
class TestSimpleDoor(CommandTest):
|
||||
def test_cmdopen(self):
|
||||
self.call(
|
||||
simpledoor.CmdOpen(),
|
||||
"newdoor;door:contrib.simpledoor.SimpleDoor,backdoor;door = Room2",
|
||||
"Created new Exit 'newdoor' from Room to Room2 (aliases: door).|Note: A door-type exit was "
|
||||
"created - ignored eventual custom return-exit type.|Created new Exit 'newdoor' from Room2 to Room (aliases: door).",
|
||||
)
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You close newdoor.", cmdstring="close")
|
||||
self.call(
|
||||
simpledoor.CmdOpenCloseDoor(),
|
||||
"newdoor",
|
||||
"newdoor is already closed.",
|
||||
cmdstring="close",
|
||||
)
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You open newdoor.", cmdstring="open")
|
||||
self.call(
|
||||
simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already open.", cmdstring="open"
|
||||
)
|
||||
27
evennia/contrib/grid/slow_exit/tests.py
Normal file
27
evennia/contrib/grid/slow_exit/tests.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
Slow exit tests.
|
||||
|
||||
"""
|
||||
|
||||
from mock import Mock, patch
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import slow_exit
|
||||
|
||||
slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0}
|
||||
|
||||
|
||||
def _cancellable_mockdelay(time, callback, *args, **kwargs):
|
||||
callback(*args, **kwargs)
|
||||
return Mock()
|
||||
|
||||
|
||||
class TestSlowExit(CommandTest):
|
||||
@patch("evennia.utils.delay", _cancellable_mockdelay)
|
||||
def test_exit(self):
|
||||
exi = create_object(
|
||||
slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2
|
||||
)
|
||||
exi.at_traverse(self.char1, self.room2)
|
||||
self.call(slow_exit.CmdSetSpeed(), "walk", "You are now walking.")
|
||||
self.call(slow_exit.CmdStop(), "", "You stop moving.")
|
||||
137
evennia/contrib/grid/wilderness/tests.py
Normal file
137
evennia/contrib/grid/wilderness/tests.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Test wilderness
|
||||
|
||||
"""
|
||||
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia import DefaultCharacter
|
||||
from evennia.utils.create import create_object
|
||||
from . import wilderness
|
||||
|
||||
|
||||
class TestWilderness(EvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1 = create_object(DefaultCharacter, key="char1")
|
||||
self.char2 = create_object(DefaultCharacter, key="char2")
|
||||
|
||||
def get_wilderness_script(self, name="default"):
|
||||
w = wilderness.WildernessScript.objects.get("default")
|
||||
return w
|
||||
|
||||
def test_create_wilderness_default_name(self):
|
||||
wilderness.create_wilderness()
|
||||
w = self.get_wilderness_script()
|
||||
self.assertIsNotNone(w)
|
||||
|
||||
def test_create_wilderness_custom_name(self):
|
||||
name = "customname"
|
||||
wilderness.create_wilderness(name)
|
||||
w = self.get_wilderness_script(name)
|
||||
self.assertIsNotNone(w)
|
||||
|
||||
def test_enter_wilderness(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1)
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
w = self.get_wilderness_script()
|
||||
self.assertEqual(w.db.itemcoordinates[self.char1], (0, 0))
|
||||
|
||||
def test_enter_wilderness_custom_coordinates(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1, coordinates=(1, 2))
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
w = self.get_wilderness_script()
|
||||
self.assertEqual(w.db.itemcoordinates[self.char1], (1, 2))
|
||||
|
||||
def test_enter_wilderness_custom_name(self):
|
||||
name = "customnname"
|
||||
wilderness.create_wilderness(name)
|
||||
wilderness.enter_wilderness(self.char1, name=name)
|
||||
self.assertIsInstance(self.char1.location, wilderness.WildernessRoom)
|
||||
|
||||
def test_wilderness_correct_exits(self):
|
||||
wilderness.create_wilderness()
|
||||
wilderness.enter_wilderness(self.char1)
|
||||
|
||||
# By default we enter at a corner (0, 0), so only a few exits should
|
||||
# be visible / traversable
|
||||
exits = [
|
||||
i
|
||||
for i in self.char1.location.contents
|
||||
if i.destination and (i.access(self.char1, "view") or i.access(self.char1, "traverse"))
|
||||
]
|
||||
|
||||
self.assertEqual(len(exits), 3)
|
||||
exitsok = ["north", "northeast", "east"]
|
||||
for each_exit in exitsok:
|
||||
self.assertTrue(any([e for e in exits if e.key == each_exit]))
|
||||
|
||||
# If we move to another location not on an edge, then all directions
|
||||
# should be visible / traversable
|
||||
wilderness.enter_wilderness(self.char1, coordinates=(1, 1))
|
||||
exits = [
|
||||
i
|
||||
for i in self.char1.location.contents
|
||||
if i.destination and (i.access(self.char1, "view") or i.access(self.char1, "traverse"))
|
||||
]
|
||||
self.assertEqual(len(exits), 8)
|
||||
exitsok = [
|
||||
"north",
|
||||
"northeast",
|
||||
"east",
|
||||
"southeast",
|
||||
"south",
|
||||
"southwest",
|
||||
"west",
|
||||
"northwest",
|
||||
]
|
||||
for each_exit in exitsok:
|
||||
self.assertTrue(any([e for e in exits if e.key == each_exit]))
|
||||
|
||||
def test_room_creation(self):
|
||||
# Pretend that both char1 and char2 are connected...
|
||||
self.char1.sessions.add(1)
|
||||
self.char2.sessions.add(1)
|
||||
self.assertTrue(self.char1.has_account)
|
||||
self.assertTrue(self.char2.has_account)
|
||||
|
||||
wilderness.create_wilderness()
|
||||
w = self.get_wilderness_script()
|
||||
|
||||
# We should have no unused room after moving the first account in.
|
||||
self.assertEqual(len(w.db.unused_rooms), 0)
|
||||
w.move_obj(self.char1, (0, 0))
|
||||
self.assertEqual(len(w.db.unused_rooms), 0)
|
||||
|
||||
# And also no unused room after moving the second one in.
|
||||
w.move_obj(self.char2, (1, 1))
|
||||
self.assertEqual(len(w.db.unused_rooms), 0)
|
||||
|
||||
# But if char2 moves into char1's room, we should have one unused room
|
||||
# Which should be char2's old room that got created.
|
||||
w.move_obj(self.char2, (0, 0))
|
||||
self.assertEqual(len(w.db.unused_rooms), 1)
|
||||
self.assertEqual(self.char1.location, self.char2.location)
|
||||
|
||||
# And if char2 moves back out, that unused room should be put back to
|
||||
# use again.
|
||||
w.move_obj(self.char2, (1, 1))
|
||||
self.assertNotEqual(self.char1.location, self.char2.location)
|
||||
self.assertEqual(len(w.db.unused_rooms), 0)
|
||||
|
||||
def test_get_new_coordinates(self):
|
||||
loc = (1, 1)
|
||||
directions = {
|
||||
"north": (1, 2),
|
||||
"northeast": (2, 2),
|
||||
"east": (2, 1),
|
||||
"southeast": (2, 0),
|
||||
"south": (1, 0),
|
||||
"southwest": (0, 0),
|
||||
"west": (0, 1),
|
||||
"northwest": (0, 2),
|
||||
}
|
||||
for (direction, correct_loc) in directions.items(): # Not compatible with Python 3
|
||||
new_loc = wilderness.get_new_coordinates(loc, direction)
|
||||
self.assertEqual(new_loc, correct_loc, direction)
|
||||
23
evennia/contrib/rpg/dice/tests.py
Normal file
23
evennia/contrib/rpg/dice/tests.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
Testing of TestDice.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from mock import patch
|
||||
from . import dice
|
||||
|
||||
|
||||
@patch("evennia.contrib.dice.randint", return_value=5)
|
||||
class TestDice(CommandTest):
|
||||
def test_roll_dice(self, mocked_randint):
|
||||
self.assertEqual(dice.roll_dice(6, 6, modifier=("+", 4)), mocked_randint() * 6 + 4)
|
||||
self.assertEqual(dice.roll_dice(6, 6, conditional=("<", 35)), True)
|
||||
self.assertEqual(dice.roll_dice(6, 6, conditional=(">", 33)), False)
|
||||
|
||||
def test_cmddice(self, mocked_randint):
|
||||
self.call(
|
||||
dice.CmdDice(), "3d6 + 4", "You roll 3d6 + 4.| Roll(s): 5, 5 and 5. Total result is 19."
|
||||
)
|
||||
self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.")
|
||||
self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).")
|
||||
46
evennia/contrib/rpg/health_bar/tests.py
Normal file
46
evennia/contrib/rpg/health_bar/tests.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
Test health bar contrib
|
||||
|
||||
"""
|
||||
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from . import health_bar
|
||||
|
||||
|
||||
class TestHealthBar(EvenniaTest):
|
||||
def test_healthbar(self):
|
||||
expected_bar_str = "|[R|w|n|[B|w test0 / 200test |n"
|
||||
self.assertEqual(
|
||||
health_bar.display_meter(
|
||||
0, 200, length=40, pre_text="test", post_text="test", align="center"
|
||||
),
|
||||
expected_bar_str,
|
||||
)
|
||||
expected_bar_str = "|[R|w |n|[B|w test24 / 200test |n"
|
||||
self.assertEqual(
|
||||
health_bar.display_meter(
|
||||
24, 200, length=40, pre_text="test", post_text="test", align="center"
|
||||
),
|
||||
expected_bar_str,
|
||||
)
|
||||
expected_bar_str = "|[Y|w test100 /|n|[B|w 200test |n"
|
||||
self.assertEqual(
|
||||
health_bar.display_meter(
|
||||
100, 200, length=40, pre_text="test", post_text="test", align="center"
|
||||
),
|
||||
expected_bar_str,
|
||||
)
|
||||
expected_bar_str = "|[G|w test180 / 200test |n|[B|w |n"
|
||||
self.assertEqual(
|
||||
health_bar.display_meter(
|
||||
180, 200, length=40, pre_text="test", post_text="test", align="center"
|
||||
),
|
||||
expected_bar_str,
|
||||
)
|
||||
expected_bar_str = "|[G|w test200 / 200test |n|[B|w|n"
|
||||
self.assertEqual(
|
||||
health_bar.display_meter(
|
||||
200, 200, length=40, pre_text="test", post_text="test", align="center"
|
||||
),
|
||||
expected_bar_str,
|
||||
)
|
||||
321
evennia/contrib/rpg/rpsystem/tests.py
Normal file
321
evennia/contrib/rpg/rpsystem/tests.py
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
"""
|
||||
Tests for RP system
|
||||
|
||||
"""
|
||||
import time
|
||||
from anything import Anything
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia import create_object
|
||||
|
||||
from . import rpsystem
|
||||
from . import rplanguage
|
||||
|
||||
mtrans = {"testing": "1", "is": "2", "a": "3", "human": "4"}
|
||||
atrans = ["An", "automated", "advantageous", "repeatable", "faster"]
|
||||
|
||||
text = (
|
||||
"Automated testing is advantageous for a number of reasons: "
|
||||
"tests may be executed Continuously without the need for human "
|
||||
"intervention, They are easily repeatable, and often faster."
|
||||
)
|
||||
|
||||
|
||||
class TestLanguage(EvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
rplanguage.add_language(
|
||||
key="testlang",
|
||||
word_length_variance=1,
|
||||
noun_prefix="bara",
|
||||
noun_postfix="'y",
|
||||
manual_translations=mtrans,
|
||||
auto_translations=atrans,
|
||||
force=True,
|
||||
)
|
||||
rplanguage.add_language(
|
||||
key="binary",
|
||||
phonemes="oo ii a ck w b d t",
|
||||
grammar="cvvv cvv cvvcv cvvcvv cvvvc cvvvcvv cvvc",
|
||||
noun_prefix="beep-",
|
||||
word_length_variance=4,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
rplanguage._LANGUAGE_HANDLER.delete()
|
||||
rplanguage._LANGUAGE_HANDLER = None
|
||||
|
||||
def test_obfuscate_language(self):
|
||||
result0 = rplanguage.obfuscate_language(text, level=0.0, language="testlang")
|
||||
self.assertEqual(result0, text)
|
||||
result1 = rplanguage.obfuscate_language(text, level=1.0, language="testlang")
|
||||
result2 = rplanguage.obfuscate_language(text, level=1.0, language="testlang")
|
||||
result3 = rplanguage.obfuscate_language(text, level=1.0, language="binary")
|
||||
|
||||
self.assertNotEqual(result1, text)
|
||||
self.assertNotEqual(result3, text)
|
||||
result1, result2 = result1.split(), result2.split()
|
||||
self.assertEqual(result1[:4], result2[:4])
|
||||
self.assertEqual(result1[1], "1")
|
||||
self.assertEqual(result1[2], "2")
|
||||
self.assertEqual(result2[-1], result2[-1])
|
||||
|
||||
def test_faulty_language(self):
|
||||
self.assertRaises(
|
||||
rplanguage.LanguageError,
|
||||
rplanguage.add_language,
|
||||
key="binary2",
|
||||
phonemes="w b d t oe ee, oo e o a wh dw bw", # erroneous comma
|
||||
grammar="cvvv cvv cvvcv cvvcvvo cvvvc cvvvcvv cvvc c v cc vv ccvvc ccvvccvv ",
|
||||
vowels="oea",
|
||||
word_length_variance=4,
|
||||
)
|
||||
|
||||
def test_available_languages(self):
|
||||
self.assertEqual(list(sorted(rplanguage.available_languages())), ["binary", "testlang"])
|
||||
|
||||
def test_obfuscate_whisper(self):
|
||||
self.assertEqual(rplanguage.obfuscate_whisper(text, level=0.0), text)
|
||||
assert rplanguage.obfuscate_whisper(text, level=0.1).startswith(
|
||||
"-utom-t-d t-sting is -dv-nt-g-ous for - numb-r of r--sons: t-sts m-y b- -x-cut-d Continuously"
|
||||
)
|
||||
assert rplanguage.obfuscate_whisper(text, level=0.5).startswith(
|
||||
"--------- --s---- -s -----------s f-- - ------ -f ---s--s: --s-s "
|
||||
)
|
||||
self.assertEqual(rplanguage.obfuscate_whisper(text, level=1.0), "...")
|
||||
|
||||
|
||||
# Testing of emoting / sdesc / recog system
|
||||
|
||||
|
||||
sdesc0 = "A nice sender of emotes"
|
||||
sdesc1 = "The first receiver of emotes."
|
||||
sdesc2 = "Another nice colliding sdesc-guy for tests"
|
||||
recog01 = "Mr Receiver"
|
||||
recog02 = "Mr Receiver2"
|
||||
recog10 = "Mr Sender"
|
||||
emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."'
|
||||
case_emote = "/me looks at /first, then /FIRST, /First and /Colliding twice."
|
||||
|
||||
|
||||
class TestRPSystem(EvenniaTest):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.room = create_object(rpsystem.ContribRPRoom, key="Location")
|
||||
self.speaker = create_object(rpsystem.ContribRPCharacter, key="Sender", location=self.room)
|
||||
self.receiver1 = create_object(
|
||||
rpsystem.ContribRPCharacter, key="Receiver1", location=self.room
|
||||
)
|
||||
self.receiver2 = create_object(
|
||||
rpsystem.ContribRPCharacter, key="Receiver2", location=self.room
|
||||
)
|
||||
|
||||
def test_ordered_permutation_regex(self):
|
||||
self.assertEqual(
|
||||
rpsystem.ordered_permutation_regex(sdesc0),
|
||||
"/[0-9]*-*A\\ nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice\\ sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice\\ sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice(?=\\W|$)+|"
|
||||
"/[0-9]*-*emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice(?=\\W|$)+|"
|
||||
"/[0-9]*-*of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A(?=\\W|$)+",
|
||||
)
|
||||
|
||||
def test_sdesc_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.assertEqual(self.speaker.sdesc.get(), sdesc0)
|
||||
self.speaker.sdesc.add("This is {#324} ignored")
|
||||
self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored")
|
||||
self.speaker.sdesc.add("Testing three words")
|
||||
self.assertEqual(
|
||||
self.speaker.sdesc.get_regex_tuple()[0].pattern,
|
||||
"/[0-9]*-*Testing\ three\ words(?=\W|$)+|"
|
||||
"/[0-9]*-*Testing\ three(?=\W|$)+|"
|
||||
"/[0-9]*-*three\ words(?=\W|$)+|"
|
||||
"/[0-9]*-*Testing(?=\W|$)+|"
|
||||
"/[0-9]*-*three(?=\W|$)+|"
|
||||
"/[0-9]*-*words(?=\W|$)+",
|
||||
)
|
||||
|
||||
def test_recog_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.speaker.recog.add(self.receiver1, recog01)
|
||||
self.speaker.recog.add(self.receiver2, recog02)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), recog01)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver2), recog02)
|
||||
self.assertEqual(
|
||||
self.speaker.recog.get_regex_tuple(self.receiver1)[0].pattern,
|
||||
"/[0-9]*-*Mr\\ Receiver(?=\\W|$)+|/[0-9]*-*Receiver(?=\\W|$)+|/[0-9]*-*Mr(?=\\W|$)+",
|
||||
)
|
||||
self.speaker.recog.remove(self.receiver1)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
||||
|
||||
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||
|
||||
def test_parse_language(self):
|
||||
self.assertEqual(
|
||||
rpsystem.parse_language(self.speaker, emote),
|
||||
(
|
||||
"With a flair, /me looks at /first and /colliding sdesc-guy. She says {##0}",
|
||||
{"##0": (None, '"This is a test."')},
|
||||
),
|
||||
)
|
||||
|
||||
def parse_sdescs_and_recogs(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
candidates = (self.receiver1, self.receiver2)
|
||||
result = (
|
||||
'With a flair, {#9} looks at {#10} and {#11}. She says "This is a test."',
|
||||
{
|
||||
"#11": "Another nice colliding sdesc-guy for tests",
|
||||
"#10": "The first receiver of emotes.",
|
||||
"#9": "A nice sender of emotes",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
self.speaker.recog.add(self.receiver1, recog01)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
|
||||
def test_send_emote(self):
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
receiver2 = self.receiver2
|
||||
receivers = [speaker, receiver1, receiver2]
|
||||
speaker.sdesc.add(sdesc0)
|
||||
receiver1.sdesc.add(sdesc1)
|
||||
receiver2.sdesc.add(sdesc2)
|
||||
speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
receiver1.msg = lambda text, **kwargs: setattr(self, "out1", text)
|
||||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
||||
self.assertEqual(
|
||||
self.out0,
|
||||
"With a flair, |bSender|n looks at |bThe first receiver of emotes.|n "
|
||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1,
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bReceiver1|n and "
|
||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2,
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||
'receiver of emotes.|n and |bReceiver2|n. She says |w"This is a test."|n',
|
||||
)
|
||||
|
||||
def test_send_case_sensitive_emote(self):
|
||||
"""Test new case-sensitive rp-parsing"""
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
receiver2 = self.receiver2
|
||||
receivers = [speaker, receiver1, receiver2]
|
||||
speaker.sdesc.add(sdesc0)
|
||||
receiver1.sdesc.add(sdesc1)
|
||||
receiver2.sdesc.add(sdesc2)
|
||||
speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
receiver1.msg = lambda text, **kwargs: setattr(self, "out1", text)
|
||||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, case_emote)
|
||||
self.assertEqual(
|
||||
self.out0,
|
||||
"|bSender|n looks at |bthe first receiver of emotes.|n, then "
|
||||
"|bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n and "
|
||||
"|bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1,
|
||||
"|bA nice sender of emotes|n looks at |bReceiver1|n, then |bReceiver1|n, "
|
||||
"|bReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2,
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n, "
|
||||
"then |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of "
|
||||
"emotes.|n and |bReceiver2|n twice.",
|
||||
)
|
||||
|
||||
def test_rpsearch(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
self.speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1)
|
||||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
||||
|
||||
def test_regex_tuple_from_key_alias(self):
|
||||
self.speaker.aliases.add("foo bar")
|
||||
self.speaker.aliases.add("this thing is a long thing")
|
||||
t0 = time.time()
|
||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t1 = time.time()
|
||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t2 = time.time()
|
||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
||||
self.assertLess(t2 - t1, 10 ** -4)
|
||||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||
|
||||
|
||||
class TestRPSystemCommands(CommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
self.char2.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
|
||||
def test_commands(self):
|
||||
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(), "Foobar Character", "Char's sdesc was set to 'Foobar Character'."
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(),
|
||||
"BarFoo Character",
|
||||
"Char2's sdesc was set to 'BarFoo Character'.",
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(rpsystem.CmdSay(), "Hello!", 'Char says, "Hello!"')
|
||||
self.call(rpsystem.CmdEmote(), "/me smiles to /BarFoo.", "Char smiles to BarFoo Character")
|
||||
self.call(
|
||||
rpsystem.CmdPose(),
|
||||
"stands by the bar",
|
||||
"Pose will read 'Foobar Character stands by the bar.'.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"barfoo as friend",
|
||||
"Char will now remember BarFoo Character as friend.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"",
|
||||
"Currently recognized (use 'recog <sdesc> as <alias>' to add new "
|
||||
"and 'forget <alias>' to remove):\n friend (BarFoo Character)",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"friend",
|
||||
"Char will now know them only as 'BarFoo Character'",
|
||||
cmdstring="forget",
|
||||
)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,9 @@
|
|||
"""
|
||||
Tests for the bodyfunctions.
|
||||
|
||||
"""
|
||||
from mock import Mock, patch
|
||||
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
|
||||
from .bodyfunctions import BodyFunctions
|
||||
|
||||
|
||||
17
evennia/contrib/tutorials/mirror/README.md
Normal file
17
evennia/contrib/tutorials/mirror/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# TutorialMirror
|
||||
|
||||
A simple mirror object to experiment with.
|
||||
|
||||
A simple mirror object that
|
||||
|
||||
- echoes back the description of the object looking at it
|
||||
- echoes back whatever is being sent to its .msg - to the
|
||||
sender, if given, otherwise to the location of the mirror.
|
||||
|
||||
## Installation
|
||||
|
||||
Create the mirror with
|
||||
|
||||
create/drop mirror:contrib.tutorials.mirror.TutorialMirror
|
||||
|
||||
Then look at it.
|
||||
6
evennia/contrib/tutorials/mirror/__init__.py
Normal file
6
evennia/contrib/tutorials/mirror/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
Mirror object - Griatch 2015.
|
||||
|
||||
"""
|
||||
|
||||
from . import TutorialMirror # noqa
|
||||
14
evennia/contrib/tutorials/talking_npc/tests.py
Normal file
14
evennia/contrib/tutorials/talking_npc/tests.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
Tutorial - talking NPC tests.
|
||||
|
||||
"""
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import talking_npc
|
||||
|
||||
|
||||
class TestTalkingNPC(CommandTest):
|
||||
def test_talkingnpc(self):
|
||||
npc = create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1)
|
||||
self.call(talking_npc.CmdTalk(), "", "(You walk up and talk to Char.)")
|
||||
npc.delete()
|
||||
192
evennia/contrib/tutorials/tutorial_world/tests.py
Normal file
192
evennia/contrib/tutorials/tutorial_world/tests.py
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
"""
|
||||
Test tutorial_world/mob
|
||||
|
||||
"""
|
||||
|
||||
from mock import patch
|
||||
from twisted.trial.unittest import TestCase as TwistedTestCase
|
||||
from twisted.internet.base import DelayedCall
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.contrib.tutorial_world import mob, objects as tutobjects
|
||||
from evennia.utils.test_resources import EvenniaTest, mockdelay, mockdeferLater
|
||||
from evennia.contrib.tutorial_world import rooms as tutrooms
|
||||
|
||||
|
||||
class TestTutorialWorldMob(EvenniaTest):
|
||||
def test_mob(self):
|
||||
mobobj = create_object(mob.Mob, key="mob")
|
||||
self.assertEqual(mobobj.db.is_dead, True)
|
||||
mobobj.set_alive()
|
||||
self.assertEqual(mobobj.db.is_dead, False)
|
||||
mobobj.set_dead()
|
||||
self.assertEqual(mobobj.db.is_dead, True)
|
||||
mobobj._set_ticker(0, "foo", stop=True)
|
||||
# TODO should be expanded with further tests of the modes and damage etc.
|
||||
|
||||
|
||||
# test tutorial_world/objects
|
||||
|
||||
|
||||
DelayedCall.debug = True
|
||||
|
||||
|
||||
class TestTutorialWorldObjects(TwistedTestCase, CommandTest):
|
||||
def test_tutorialobj(self):
|
||||
obj1 = create_object(tutobjects.TutorialObject, key="tutobj")
|
||||
obj1.reset()
|
||||
self.assertEqual(obj1.location, obj1.home)
|
||||
|
||||
def test_readable(self):
|
||||
readable = create_object(tutobjects.TutorialReadable, key="book", location=self.room1)
|
||||
readable.db.readable_text = "Text to read"
|
||||
self.call(tutobjects.CmdRead(), "book", "You read book:\n Text to read", obj=readable)
|
||||
|
||||
def test_climbable(self):
|
||||
climbable = create_object(tutobjects.TutorialClimbable, key="tree", location=self.room1)
|
||||
self.call(
|
||||
tutobjects.CmdClimb(),
|
||||
"tree",
|
||||
"You climb tree. Having looked around, you climb down again.",
|
||||
obj=climbable,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.char1.tags.get("tutorial_climbed_tree", category="tutorial_world"),
|
||||
"tutorial_climbed_tree",
|
||||
)
|
||||
|
||||
def test_obelisk(self):
|
||||
obelisk = create_object(tutobjects.Obelisk, key="obelisk", location=self.room1)
|
||||
self.assertEqual(obelisk.return_appearance(self.char1).startswith("|cobelisk("), True)
|
||||
|
||||
@patch("evennia.contrib.tutorial_world.objects.delay", mockdelay)
|
||||
@patch("evennia.scripts.taskhandler.deferLater", mockdeferLater)
|
||||
def test_lightsource(self):
|
||||
light = create_object(tutobjects.LightSource, key="torch", location=self.room1)
|
||||
self.call(
|
||||
tutobjects.CmdLight(),
|
||||
"",
|
||||
"A torch on the floor flickers and dies.|You light torch.",
|
||||
obj=light,
|
||||
)
|
||||
self.assertFalse(light.pk)
|
||||
|
||||
@patch("evennia.contrib.tutorial_world.objects.delay", mockdelay)
|
||||
@patch("evennia.scripts.taskhandler.deferLater", mockdeferLater)
|
||||
def test_crumblingwall(self):
|
||||
wall = create_object(tutobjects.CrumblingWall, key="wall", location=self.room1)
|
||||
wall.db.destination = self.room2.dbref
|
||||
self.assertFalse(wall.db.button_exposed)
|
||||
self.assertFalse(wall.db.exit_open)
|
||||
wall.db.root_pos = {"yellow": 0, "green": 0, "red": 0, "blue": 0}
|
||||
self.call(
|
||||
tutobjects.CmdShiftRoot(),
|
||||
"blue root right",
|
||||
"You shove the root adorned with small blue flowers to the right.",
|
||||
obj=wall,
|
||||
)
|
||||
self.call(
|
||||
tutobjects.CmdShiftRoot(),
|
||||
"red root left",
|
||||
"You shift the reddish root to the left.",
|
||||
obj=wall,
|
||||
)
|
||||
self.call(
|
||||
tutobjects.CmdShiftRoot(),
|
||||
"yellow root down",
|
||||
"You shove the root adorned with small yellow flowers downwards.",
|
||||
obj=wall,
|
||||
)
|
||||
self.call(
|
||||
tutobjects.CmdShiftRoot(),
|
||||
"green root up",
|
||||
"You shift the weedy green root upwards.|Holding aside the root you "
|
||||
"think you notice something behind it ...",
|
||||
obj=wall,
|
||||
)
|
||||
self.call(
|
||||
tutobjects.CmdPressButton(),
|
||||
"",
|
||||
"You move your fingers over the suspicious depression, then gives it a "
|
||||
"decisive push. First",
|
||||
obj=wall,
|
||||
)
|
||||
# we patch out the delay, so these are closed immediately
|
||||
self.assertFalse(wall.db.button_exposed)
|
||||
self.assertFalse(wall.db.exit_open)
|
||||
|
||||
def test_weapon(self):
|
||||
weapon = create_object(tutobjects.TutorialWeapon, key="sword", location=self.char1)
|
||||
self.call(
|
||||
tutobjects.CmdAttack(), "Char", "You stab with sword.", obj=weapon, cmdstring="stab"
|
||||
)
|
||||
self.call(
|
||||
tutobjects.CmdAttack(), "Char", "You slash with sword.", obj=weapon, cmdstring="slash"
|
||||
)
|
||||
|
||||
def test_weaponrack(self):
|
||||
rack = create_object(tutobjects.TutorialWeaponRack, key="rack", location=self.room1)
|
||||
rack.db.available_weapons = ["sword"]
|
||||
self.call(tutobjects.CmdGetWeapon(), "", "You find Rusty sword.", obj=rack)
|
||||
|
||||
|
||||
class TestTutorialWorldRooms(CommandTest):
|
||||
def test_cmdtutorial(self):
|
||||
room = create_object(tutrooms.TutorialRoom, key="tutroom")
|
||||
self.char1.location = room
|
||||
self.call(tutrooms.CmdTutorial(), "", "Sorry, there is no tutorial help available here.")
|
||||
self.call(
|
||||
tutrooms.CmdTutorialSetDetail(),
|
||||
"detail;foo;foo2 = A detail",
|
||||
"Detail set: 'detail;foo;foo2': 'A detail'",
|
||||
obj=room,
|
||||
)
|
||||
self.call(tutrooms.CmdTutorialLook(), "", "tutroom(", obj=room)
|
||||
self.call(tutrooms.CmdTutorialLook(), "detail", "A detail", obj=room)
|
||||
self.call(tutrooms.CmdTutorialLook(), "foo", "A detail", obj=room)
|
||||
room.delete()
|
||||
|
||||
def test_weatherroom(self):
|
||||
room = create_object(tutrooms.WeatherRoom, key="weatherroom")
|
||||
room.update_weather()
|
||||
tutrooms.TICKER_HANDLER.remove(
|
||||
interval=room.db.interval, callback=room.update_weather, idstring="tutorial"
|
||||
)
|
||||
room.delete()
|
||||
|
||||
def test_introroom(self):
|
||||
room = create_object(tutrooms.IntroRoom, key="introroom")
|
||||
room.at_object_receive(self.char1, self.room1)
|
||||
|
||||
def test_bridgeroom(self):
|
||||
room = create_object(tutrooms.BridgeRoom, key="bridgeroom")
|
||||
room.update_weather()
|
||||
self.char1.move_to(room)
|
||||
self.call(
|
||||
tutrooms.CmdBridgeHelp(),
|
||||
"",
|
||||
"You are trying hard not to fall off the bridge ...",
|
||||
obj=room,
|
||||
)
|
||||
self.call(
|
||||
tutrooms.CmdLookBridge(),
|
||||
"",
|
||||
"bridgeroom\nYou are standing very close to the the bridge's western foundation.",
|
||||
obj=room,
|
||||
)
|
||||
room.at_object_leave(self.char1, self.room1)
|
||||
tutrooms.TICKER_HANDLER.remove(
|
||||
interval=room.db.interval, callback=room.update_weather, idstring="tutorial"
|
||||
)
|
||||
room.delete()
|
||||
|
||||
def test_darkroom(self):
|
||||
room = create_object(tutrooms.DarkRoom, key="darkroom")
|
||||
self.char1.move_to(room)
|
||||
self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until")
|
||||
|
||||
def test_teleportroom(self):
|
||||
create_object(tutrooms.TeleportRoom, key="teleportroom")
|
||||
|
||||
def test_outroroom(self):
|
||||
create_object(tutrooms.OutroRoom, key="outroroom")
|
||||
71
evennia/contrib/utils/auditing/README.md
Normal file
71
evennia/contrib/utils/auditing/README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Input/Output Auditing
|
||||
|
||||
Contrib - Johnny 2017
|
||||
|
||||
This is a tap that optionally intercepts all data sent to/from clients and the
|
||||
server and passes it to a callback of your choosing.
|
||||
|
||||
It is intended for quality assurance, post-incident investigations and debugging
|
||||
but obviously can be abused. All data is recorded in cleartext. Please
|
||||
be ethical, and if you are unwilling to properly deal with the implications of
|
||||
recording user passwords or private communications, please do not enable
|
||||
this module.
|
||||
|
||||
Some checks have been implemented to protect the privacy of users.
|
||||
|
||||
Files included in this module:
|
||||
|
||||
outputs.py - Example callback methods. This module ships with examples of
|
||||
callbacks that send data as JSON to a file in your game/server/logs
|
||||
dir or to your native Linux syslog daemon. You can of course write
|
||||
your own to do other things like post them to Kafka topics.
|
||||
|
||||
server.py - Extends the Evennia ServerSession object to pipe data to the
|
||||
callback upon receipt.
|
||||
|
||||
tests.py - Unit tests that check to make sure commands with sensitive
|
||||
arguments are having their PII scrubbed.
|
||||
|
||||
|
||||
## Installation/Configuration:
|
||||
|
||||
Deployment is completed by configuring a few settings in server.conf. This line
|
||||
is required:
|
||||
|
||||
SERVER_SESSION_CLASS = 'evennia.contrib.security.auditing.server.AuditedServerSession'
|
||||
|
||||
This tells Evennia to use this ServerSession instead of its own. Below are the
|
||||
other possible options along with the default value that will be used if unset.
|
||||
|
||||
# Where to send logs? Define the path to a module containing your callback
|
||||
# function. It should take a single dict argument as input
|
||||
AUDIT_CALLBACK = 'evennia.contrib.security.auditing.outputs.to_file'
|
||||
|
||||
# Log user input? Be ethical about this; it will log all private and
|
||||
# public communications between players and/or admins (default: False).
|
||||
AUDIT_IN = False
|
||||
|
||||
# Log server output? This will result in logging of ALL system
|
||||
# messages and ALL broadcasts to connected players, so on a busy game any
|
||||
# broadcast to all users will yield a single event for every connected user!
|
||||
AUDIT_OUT = False
|
||||
|
||||
# The default output is a dict. Do you want to allow key:value pairs with
|
||||
# null/blank values? If you're just writing to disk, disabling this saves
|
||||
# some disk space, but whether you *want* sparse values or not is more of a
|
||||
# consideration if you're shipping logs to a NoSQL/schemaless database.
|
||||
# (default: False)
|
||||
AUDIT_ALLOW_SPARSE = False
|
||||
|
||||
# If you write custom commands that handle sensitive data like passwords,
|
||||
# you must write a regular expression to remove that before writing to log.
|
||||
# AUDIT_MASKS is a list of dictionaries that define the names of commands
|
||||
# and the regexes needed to scrub them.
|
||||
# The system already has defaults to filter out sensitive login/creation
|
||||
# commands in the default command set. Your list of AUDIT_MASKS will be appended
|
||||
# to those defaults.
|
||||
#
|
||||
# In the regex, the sensitive data itself must be captured in a named group with a
|
||||
# label of 'secret' (see the Python docs on the `re` module for more info). For
|
||||
# example: `{'authentication': r"^@auth\s+(?P<secret>[\w]+)"}`
|
||||
AUDIT_MASKS = []
|
||||
0
evennia/contrib/utils/auditing/__init__.py
Normal file
0
evennia/contrib/utils/auditing/__init__.py
Normal file
60
evennia/contrib/utils/auditing/outputs.py
Normal file
60
evennia/contrib/utils/auditing/outputs.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Auditable Server Sessions - Example Outputs
|
||||
Example methods demonstrating output destinations for logs generated by
|
||||
audited server sessions.
|
||||
|
||||
This is designed to be a single source of events for developers to customize
|
||||
and add any additional enhancements before events are written out-- i.e. if you
|
||||
want to keep a running list of what IPs a user logs in from on account/character
|
||||
objects, or if you want to perform geoip or ASN lookups on IPs before committing,
|
||||
or tag certain events with the results of a reputational lookup, this should be
|
||||
the easiest place to do it. Write a method and invoke it via
|
||||
`settings.AUDIT_CALLBACK` to have log data objects passed to it.
|
||||
|
||||
Evennia contribution - Johnny 2017
|
||||
"""
|
||||
from evennia.utils.logger import log_file
|
||||
import json
|
||||
import syslog
|
||||
|
||||
|
||||
def to_file(data):
|
||||
"""
|
||||
Writes dictionaries of data generated by an AuditedServerSession to files
|
||||
in JSON format, bucketed by date.
|
||||
|
||||
Uses Evennia's native logger and writes to the default
|
||||
log directory (~/yourgame/server/logs/ or settings.LOG_DIR)
|
||||
|
||||
Args:
|
||||
data (dict): Parsed session transmission data.
|
||||
|
||||
"""
|
||||
# Bucket logs by day and remove objects before serialization
|
||||
bucket = data.pop("objects")["time"].strftime("%Y-%m-%d")
|
||||
|
||||
# Write it
|
||||
log_file(json.dumps(data), filename="audit_%s.log" % bucket)
|
||||
|
||||
|
||||
def to_syslog(data):
|
||||
"""
|
||||
Writes dictionaries of data generated by an AuditedServerSession to syslog.
|
||||
|
||||
Takes advantage of your system's native logger and writes to wherever
|
||||
you have it configured, which is independent of Evennia.
|
||||
Linux systems tend to write to /var/log/syslog.
|
||||
|
||||
If you're running rsyslog, you can configure it to dump and/or forward logs
|
||||
to disk and/or an external data warehouse (recommended-- if your server is
|
||||
compromised or taken down, losing your logs along with it is no help!).
|
||||
|
||||
Args:
|
||||
data (dict): Parsed session transmission data.
|
||||
|
||||
"""
|
||||
# Remove objects before serialization
|
||||
data.pop("objects")
|
||||
|
||||
# Write it out
|
||||
syslog.syslog(json.dumps(data))
|
||||
249
evennia/contrib/utils/auditing/server.py
Normal file
249
evennia/contrib/utils/auditing/server.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
"""
|
||||
Auditable Server Sessions:
|
||||
Extension of the stock ServerSession that yields objects representing
|
||||
user inputs and system outputs.
|
||||
|
||||
Evennia contribution - Johnny 2017
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
|
||||
from django.utils import timezone
|
||||
from django.conf import settings as ev_settings
|
||||
from evennia.utils import utils, logger, mod_import, get_evennia_version
|
||||
from evennia.server.serversession import ServerSession
|
||||
|
||||
# Attributes governing auditing of commands and where to send log objects
|
||||
AUDIT_CALLBACK = getattr(
|
||||
ev_settings, "AUDIT_CALLBACK", "evennia.contrib.security.auditing.outputs.to_file"
|
||||
)
|
||||
AUDIT_IN = getattr(ev_settings, "AUDIT_IN", False)
|
||||
AUDIT_OUT = getattr(ev_settings, "AUDIT_OUT", False)
|
||||
AUDIT_ALLOW_SPARSE = getattr(ev_settings, "AUDIT_ALLOW_SPARSE", False)
|
||||
AUDIT_MASKS = [
|
||||
{"connect": r"^[@\s]*[connect]{5,8}\s+(\".+?\"|[^\s]+)\s+(?P<secret>.+)"},
|
||||
{"connect": r"^[@\s]*[connect]{5,8}\s+(?P<secret>[\w]+)"},
|
||||
{"create": r"^[^@]?[create]{5,6}\s+(\w+|\".+?\")\s+(?P<secret>[\w]+)"},
|
||||
{"create": r"^[^@]?[create]{5,6}\s+(?P<secret>[\w]+)"},
|
||||
{"userpassword": r"^[@\s]*[userpassword]{11,14}\s+(\w+|\".+?\")\s+=*\s*(?P<secret>[\w]+)"},
|
||||
{"userpassword": r"^.*new password set to '(?P<secret>[^']+)'\."},
|
||||
{"userpassword": r"^.* has changed your password to '(?P<secret>[^']+)'\."},
|
||||
{"password": r"^[@\s]*[password]{6,9}\s+(?P<secret>.*)"},
|
||||
] + getattr(ev_settings, "AUDIT_MASKS", [])
|
||||
|
||||
|
||||
if AUDIT_CALLBACK:
|
||||
try:
|
||||
AUDIT_CALLBACK = getattr(
|
||||
mod_import(".".join(AUDIT_CALLBACK.split(".")[:-1])), AUDIT_CALLBACK.split(".")[-1]
|
||||
)
|
||||
logger.log_sec("Auditing module online.")
|
||||
logger.log_sec(
|
||||
"Audit record User input: {}, output: {}.\n"
|
||||
"Audit sparse recording: {}, Log callback: {}".format(
|
||||
AUDIT_IN, AUDIT_OUT, AUDIT_ALLOW_SPARSE, AUDIT_CALLBACK
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.log_err("Failed to activate Auditing module. %s" % e)
|
||||
|
||||
|
||||
class AuditedServerSession(ServerSession):
|
||||
"""
|
||||
This particular implementation parses all server inputs and/or outputs and
|
||||
passes a dict containing the parsed metadata to a callback method of your
|
||||
creation. This is useful for recording player activity where necessary for
|
||||
security auditing, usage analysis or post-incident forensic discovery.
|
||||
|
||||
*** WARNING ***
|
||||
All strings are recorded and stored in plaintext. This includes those strings
|
||||
which might contain sensitive data (create, connect, @password). These commands
|
||||
have their arguments masked by default, but you must mask or mask any
|
||||
custom commands of your own that handle sensitive information.
|
||||
|
||||
See README.md for installation/configuration instructions.
|
||||
"""
|
||||
|
||||
def audit(self, **kwargs):
|
||||
"""
|
||||
Extracts messages and system data from a Session object upon message
|
||||
send or receive.
|
||||
|
||||
Keyword Args:
|
||||
src (str): Source of data; 'client' or 'server'. Indicates direction.
|
||||
text (str or list): Client sends messages to server in the form of
|
||||
lists. Server sends messages to client as string.
|
||||
|
||||
Returns:
|
||||
log (dict): Dictionary object containing parsed system and user data
|
||||
related to this message.
|
||||
|
||||
"""
|
||||
# Get time at start of processing
|
||||
time_obj = timezone.now()
|
||||
time_str = str(time_obj)
|
||||
|
||||
session = self
|
||||
src = kwargs.pop("src", "?")
|
||||
bytecount = 0
|
||||
|
||||
# Do not log empty lines
|
||||
if not kwargs:
|
||||
return {}
|
||||
|
||||
# Get current session's IP address
|
||||
client_ip = session.address
|
||||
|
||||
# Capture Account name and dbref together
|
||||
account = session.get_account()
|
||||
account_token = ""
|
||||
if account:
|
||||
account_token = "%s%s" % (account.key, account.dbref)
|
||||
|
||||
# Capture Character name and dbref together
|
||||
char = session.get_puppet()
|
||||
char_token = ""
|
||||
if char:
|
||||
char_token = "%s%s" % (char.key, char.dbref)
|
||||
|
||||
# Capture Room name and dbref together
|
||||
room = None
|
||||
room_token = ""
|
||||
if char:
|
||||
room = char.location
|
||||
room_token = "%s%s" % (room.key, room.dbref)
|
||||
|
||||
# Try to compile an input/output string
|
||||
def drill(obj, bucket):
|
||||
if isinstance(obj, dict):
|
||||
return bucket
|
||||
elif utils.is_iter(obj):
|
||||
for sub_obj in obj:
|
||||
bucket.extend(drill(sub_obj, []))
|
||||
else:
|
||||
bucket.append(obj)
|
||||
return bucket
|
||||
|
||||
text = kwargs.pop("text", "")
|
||||
if utils.is_iter(text):
|
||||
text = "|".join(drill(text, []))
|
||||
|
||||
# Mask any PII in message, where possible
|
||||
bytecount = len(text.encode("utf-8"))
|
||||
text = self.mask(text)
|
||||
|
||||
# Compile the IP, Account, Character, Room, and the message.
|
||||
log = {
|
||||
"time": time_str,
|
||||
"hostname": socket.getfqdn(),
|
||||
"application": "%s" % ev_settings.SERVERNAME,
|
||||
"version": get_evennia_version(),
|
||||
"pid": os.getpid(),
|
||||
"direction": "SND" if src == "server" else "RCV",
|
||||
"protocol": self.protocol_key,
|
||||
"ip": client_ip,
|
||||
"session": "session#%s" % self.sessid,
|
||||
"account": account_token,
|
||||
"character": char_token,
|
||||
"room": room_token,
|
||||
"text": text.strip(),
|
||||
"bytes": bytecount,
|
||||
"data": kwargs,
|
||||
"objects": {
|
||||
"time": time_obj,
|
||||
"session": self,
|
||||
"account": account,
|
||||
"character": char,
|
||||
"room": room,
|
||||
},
|
||||
}
|
||||
|
||||
# Remove any keys with blank values
|
||||
if AUDIT_ALLOW_SPARSE is False:
|
||||
log["data"] = {k: v for k, v in log["data"].items() if v}
|
||||
log["objects"] = {k: v for k, v in log["objects"].items() if v}
|
||||
log = {k: v for k, v in log.items() if v}
|
||||
|
||||
return log
|
||||
|
||||
def mask(self, msg):
|
||||
"""
|
||||
Masks potentially sensitive user information within messages before
|
||||
writing to log. Recording cleartext password attempts is bad policy.
|
||||
|
||||
Args:
|
||||
msg (str): Raw text string sent from client <-> server
|
||||
|
||||
Returns:
|
||||
msg (str): Text string with sensitive information masked out.
|
||||
|
||||
"""
|
||||
# Check to see if the command is embedded within server output
|
||||
_msg = msg
|
||||
is_embedded = False
|
||||
match = re.match(".*Command.*'(.+)'.*is not available.*", msg, flags=re.IGNORECASE)
|
||||
if match:
|
||||
msg = match.group(1).replace("\\", "")
|
||||
submsg = msg
|
||||
is_embedded = True
|
||||
|
||||
for mask in AUDIT_MASKS:
|
||||
for command, regex in mask.items():
|
||||
try:
|
||||
match = re.match(regex, msg, flags=re.IGNORECASE)
|
||||
except Exception as e:
|
||||
logger.log_err(regex)
|
||||
logger.log_err(e)
|
||||
continue
|
||||
|
||||
if match:
|
||||
term = match.group("secret")
|
||||
masked = re.sub(term, "*" * len(term.zfill(8)), msg)
|
||||
|
||||
if is_embedded:
|
||||
msg = re.sub(
|
||||
submsg, "%s <Masked: %s>" % (masked, command), _msg, flags=re.IGNORECASE
|
||||
)
|
||||
else:
|
||||
msg = masked
|
||||
|
||||
return msg
|
||||
|
||||
return _msg
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
"""
|
||||
Generic hook for sending data out through the protocol.
|
||||
|
||||
Keyword Args:
|
||||
kwargs (any): Other data to the protocol.
|
||||
|
||||
"""
|
||||
if AUDIT_CALLBACK and AUDIT_OUT:
|
||||
try:
|
||||
log = self.audit(src="server", **kwargs)
|
||||
if log:
|
||||
AUDIT_CALLBACK(log)
|
||||
except Exception as e:
|
||||
logger.log_err(e)
|
||||
|
||||
super(AuditedServerSession, self).data_out(**kwargs)
|
||||
|
||||
def data_in(self, **kwargs):
|
||||
"""
|
||||
Hook for protocols to send incoming data to the engine.
|
||||
|
||||
Keyword Args:
|
||||
kwargs (any): Other data from the protocol.
|
||||
|
||||
"""
|
||||
if AUDIT_CALLBACK and AUDIT_IN:
|
||||
try:
|
||||
log = self.audit(src="client", **kwargs)
|
||||
if log:
|
||||
AUDIT_CALLBACK(log)
|
||||
except Exception as e:
|
||||
logger.log_err(e)
|
||||
|
||||
super(AuditedServerSession, self).data_in(**kwargs)
|
||||
113
evennia/contrib/utils/auditing/tests.py
Normal file
113
evennia/contrib/utils/auditing/tests.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"""
|
||||
Module containing the test cases for the Audit system.
|
||||
"""
|
||||
|
||||
from anything import Anything
|
||||
from django.conf import settings
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
import re
|
||||
|
||||
# Configure session auditing settings - TODO: This is bad practice that leaks over to other tests
|
||||
settings.AUDIT_CALLBACK = "evennia.security.contrib.auditing.outputs.to_syslog"
|
||||
settings.AUDIT_IN = True
|
||||
settings.AUDIT_OUT = True
|
||||
settings.AUDIT_ALLOW_SPARSE = True
|
||||
|
||||
# Configure settings to use custom session - TODO: This is bad practice, changing global settings
|
||||
settings.SERVER_SESSION_CLASS = "evennia.contrib.security.auditing.server.AuditedServerSession"
|
||||
|
||||
|
||||
class AuditingTest(EvenniaTest):
|
||||
def test_mask(self):
|
||||
"""
|
||||
Make sure the 'mask' function is properly masking potentially sensitive
|
||||
information from strings.
|
||||
"""
|
||||
safe_cmds = (
|
||||
"/say hello to my little friend",
|
||||
"@ccreate channel = for channeling",
|
||||
"@create/drop some stuff",
|
||||
"@create rock",
|
||||
"@create a pretty shirt : evennia.contrib.clothing.Clothing",
|
||||
"@charcreate johnnyefhiwuhefwhef",
|
||||
'Command "@logout" is not available. Maybe you meant "@color" or "@cboot"?',
|
||||
'/me says, "what is the password?"',
|
||||
"say the password is plugh",
|
||||
# Unfortunately given the syntax, there is no way to discern the
|
||||
# latter of these as sensitive
|
||||
"@create pretty sunset" "@create johnny password123",
|
||||
'{"text": "Command \'do stuff\' is not available. Type "help" for help."}',
|
||||
)
|
||||
|
||||
for cmd in safe_cmds:
|
||||
self.assertEqual(self.session.mask(cmd), cmd)
|
||||
|
||||
unsafe_cmds = (
|
||||
(
|
||||
"something - new password set to 'asdfghjk'.",
|
||||
"something - new password set to '********'.",
|
||||
),
|
||||
(
|
||||
"someone has changed your password to 'something'.",
|
||||
"someone has changed your password to '*********'.",
|
||||
),
|
||||
("connect johnny password123", "connect johnny ***********"),
|
||||
("concnct johnny password123", "concnct johnny ***********"),
|
||||
("concnct johnnypassword123", "concnct *****************"),
|
||||
('connect "johnny five" "password 123"', 'connect "johnny five" **************'),
|
||||
('connect johnny "password 123"', "connect johnny **************"),
|
||||
("create johnny password123", "create johnny ***********"),
|
||||
("@password password1234 = password2345", "@password ***************************"),
|
||||
("@password password1234 password2345", "@password *************************"),
|
||||
("@passwd password1234 = password2345", "@passwd ***************************"),
|
||||
("@userpassword johnny = password234", "@userpassword johnny = ***********"),
|
||||
("craete johnnypassword123", "craete *****************"),
|
||||
(
|
||||
"Command 'conncect teddy teddy' is not available. Maybe you meant \"@encode\"?",
|
||||
"Command 'conncect ******** ********' is not available. Maybe you meant \"@encode\"?",
|
||||
),
|
||||
(
|
||||
"{'text': u'Command \\'conncect jsis dfiidf\\' is not available. Type \"help\" for help.'}",
|
||||
"{'text': u'Command \\'conncect jsis ********\\' is not available. Type \"help\" for help.'}",
|
||||
),
|
||||
)
|
||||
|
||||
for index, (unsafe, safe) in enumerate(unsafe_cmds):
|
||||
self.assertEqual(re.sub(" <Masked: .+>", "", self.session.mask(unsafe)).strip(), safe)
|
||||
|
||||
# Make sure scrubbing is not being abused to evade monitoring
|
||||
secrets = [
|
||||
"say password password password; ive got a secret that i cant explain",
|
||||
"whisper johnny = password\n let's lynch the landlord",
|
||||
"say connect johnny password1234|the secret life of arabia",
|
||||
"@password eval(\"__import__('os').system('clear')\", {'__builtins__':{}})",
|
||||
]
|
||||
for secret in secrets:
|
||||
self.assertEqual(self.session.mask(secret), secret)
|
||||
|
||||
def test_audit(self):
|
||||
"""
|
||||
Make sure the 'audit' function is returning a dictionary based on values
|
||||
parsed from the Session object.
|
||||
"""
|
||||
log = self.session.audit(src="client", text=[["hello"]])
|
||||
obj = {
|
||||
k: v for k, v in log.items() if k in ("direction", "protocol", "application", "text")
|
||||
}
|
||||
self.assertEqual(
|
||||
obj,
|
||||
{
|
||||
"direction": "RCV",
|
||||
"protocol": "telnet",
|
||||
"application": Anything, # this will change if running tests from the game dir
|
||||
"text": "hello",
|
||||
},
|
||||
)
|
||||
|
||||
# Make sure OOB data is being recorded
|
||||
log = self.session.audit(
|
||||
src="client", text="connect johnny password123", prompt="hp=20|st=10|ma=15", pane=2
|
||||
)
|
||||
self.assertEqual(log["text"], "connect johnny ***********")
|
||||
self.assertEqual(log["data"]["prompt"], "hp=20|st=10|ma=15")
|
||||
self.assertEqual(log["data"]["pane"], 2)
|
||||
25
evennia/contrib/utils/random_string_generator/tests.py
Normal file
25
evennia/contrib/utils/random_string_generator/tests.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"""
|
||||
Random string tests.
|
||||
|
||||
"""
|
||||
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.contrib import random_string_generator
|
||||
|
||||
SIMPLE_GENERATOR = random_string_generator.RandomStringGenerator("simple", "[01]{2}")
|
||||
|
||||
|
||||
class TestRandomStringGenerator(EvenniaTest):
|
||||
def test_generate(self):
|
||||
"""Generate and fail when exhausted."""
|
||||
generated = []
|
||||
for i in range(4):
|
||||
generated.append(SIMPLE_GENERATOR.get())
|
||||
|
||||
generated.sort()
|
||||
self.assertEqual(generated, ["00", "01", "10", "11"])
|
||||
|
||||
# At this point, we have generated 4 strings.
|
||||
# We can't generate one more
|
||||
with self.assertRaises(random_string_generator.ExhaustedGenerator):
|
||||
SIMPLE_GENERATOR.get()
|
||||
62
evennia/contrib/utils/tree_select/tests.py
Normal file
62
evennia/contrib/utils/tree_select/tests.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"""
|
||||
Test tree select
|
||||
|
||||
"""
|
||||
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.contrib import tree_select
|
||||
from evennia.contrib import fieldfill
|
||||
|
||||
TREE_MENU_TESTSTR = """Foo
|
||||
Bar
|
||||
-Baz
|
||||
--Baz 1
|
||||
--Baz 2
|
||||
-Qux"""
|
||||
|
||||
|
||||
class TestTreeSelectFunc(EvenniaTest):
|
||||
def test_tree_functions(self):
|
||||
# Dash counter
|
||||
self.assertTrue(tree_select.dashcount("--test") == 2)
|
||||
# Is category
|
||||
self.assertTrue(tree_select.is_category(TREE_MENU_TESTSTR, 1) is True)
|
||||
# Parse options
|
||||
self.assertTrue(
|
||||
tree_select.parse_opts(TREE_MENU_TESTSTR, category_index=2)
|
||||
== [(3, "Baz 1"), (4, "Baz 2")]
|
||||
)
|
||||
# Index to selection
|
||||
self.assertTrue(tree_select.index_to_selection(TREE_MENU_TESTSTR, 2) == "Baz")
|
||||
# Go up one category
|
||||
self.assertTrue(tree_select.go_up_one_category(TREE_MENU_TESTSTR, 4) == 2)
|
||||
# Option list to menu options
|
||||
test_optlist = tree_select.parse_opts(TREE_MENU_TESTSTR, category_index=2)
|
||||
optlist_to_menu_expected_result = [
|
||||
{"goto": ["menunode_treeselect", {"newindex": 3}], "key": "Baz 1"},
|
||||
{"goto": ["menunode_treeselect", {"newindex": 4}], "key": "Baz 2"},
|
||||
{
|
||||
"goto": ["menunode_treeselect", {"newindex": 1}],
|
||||
"key": ["<< Go Back", "go back", "back"],
|
||||
"desc": "Return to the previous menu.",
|
||||
},
|
||||
]
|
||||
self.assertTrue(
|
||||
tree_select.optlist_to_menuoptions(TREE_MENU_TESTSTR, test_optlist, 2, True, True)
|
||||
== optlist_to_menu_expected_result
|
||||
)
|
||||
|
||||
|
||||
FIELD_TEST_TEMPLATE = [
|
||||
{"fieldname": "TextTest", "fieldtype": "text"},
|
||||
{"fieldname": "NumberTest", "fieldtype": "number", "blankmsg": "Number here!"},
|
||||
{"fieldname": "DefaultText", "fieldtype": "text", "default": "Test"},
|
||||
{"fieldname": "DefaultNum", "fieldtype": "number", "default": 3},
|
||||
]
|
||||
|
||||
FIELD_TEST_DATA = {"TextTest": None, "NumberTest": None, "DefaultText": "Test", "DefaultNum": 3}
|
||||
|
||||
|
||||
class TestFieldFillFunc(EvenniaTest):
|
||||
def test_field_functions(self):
|
||||
self.assertTrue(fieldfill.form_template_to_dict(FIELD_TEST_TEMPLATE) == FIELD_TEST_DATA)
|
||||
Loading…
Add table
Add a link
Reference in a new issue