First version of moved contrib tests

This commit is contained in:
Griatch 2021-12-18 23:42:14 +01:00
parent a6cb94056c
commit 04a95297b5
37 changed files with 4135 additions and 3589 deletions

View 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")

View 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")

View 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)

View 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.")

View file

@ -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"

View 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(), "", "======")

View 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,
)

View 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))

View 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.]',
)

View 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)

View 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"])

View 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)

View 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)

View 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")

View 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})

View 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])

View 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.")

View 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...|""",
)

View 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"
)

View 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.")

View 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)

View 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).")

View 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,
)

View 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

View file

@ -1,7 +1,9 @@
"""
Tests for the bodyfunctions.
"""
from mock import Mock, patch
from evennia.utils.test_resources import EvenniaTest
from .bodyfunctions import BodyFunctions

View 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.

View file

@ -0,0 +1,6 @@
"""
Mirror object - Griatch 2015.
"""
from . import TutorialMirror # noqa

View 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()

View 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")

View 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 = []

View 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))

View 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)

View 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)

View 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()

View 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)