Allow guests for multisession_mode>1. Resolves #2098. Fix unit tests

This commit is contained in:
Griatch 2020-06-26 16:34:35 +02:00
parent 9d7461e21e
commit a721760da6
5 changed files with 81 additions and 36 deletions

View file

@ -649,6 +649,51 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
logger.log_sec(f"Password successfully changed for {self}.") logger.log_sec(f"Password successfully changed for {self}.")
self.at_password_change() self.at_password_change()
def create_character(self, *args, **kwargs):
"""
Create a character linked to this account.
Args:
key (str, optional): If not given, use the same name as the account.
typeclass (str, optional): Typeclass to use for this character. If
not given, use settings.BASE_CHARACTER_TYPECLASS.
permissions (list, optional): If not given, use the account's permissions.
ip (str, optiona): The client IP creating this character. Will fall back to the
one stored for the account if not given.
kwargs (any): Other kwargs will be used in the create_call.
Returns:
Object: A new character of the `character_typeclass` type. None on an error.
list or None: A list of errors, or None.
"""
# parse inputs
character_key = kwargs.pop("key", self.key)
character_ip = kwargs.pop("ip", self.db.creator_ip)
character_permissions = kwargs.pop("permissions", self.permissions)
# Load the appropriate Character class
character_typeclass = kwargs.pop("typeclass", None)
character_typeclass = character_typeclass if character_typeclass else settings.BASE_CHARACTER_TYPECLASS
Character = class_from_module(character_typeclass)
# Create the character
character, errs = Character.create(
character_key,
self,
ip=character_ip,
typeclass=character_typeclass,
permissions=character_permissions,
**kwargs
)
if character:
# Update playable character list
if character not in self.characters:
self.db._playable_characters.append(character)
# We need to set this to have @ic auto-connect to this character
self.db._last_puppet = character
return character, errs
@classmethod @classmethod
def create(cls, *args, **kwargs): def create(cls, *args, **kwargs):
""" """
@ -755,31 +800,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
logger.log_err(string) logger.log_err(string)
if account and settings.MULTISESSION_MODE < 2: if account and settings.MULTISESSION_MODE < 2:
# Load the appropriate Character class # Auto-create a character to go with this account
character_typeclass = kwargs.get(
"character_typeclass", settings.BASE_CHARACTER_TYPECLASS
)
character_home = kwargs.get("home")
Character = class_from_module(character_typeclass)
# Create the character character, errs = account.create_character(typeclass=kwargs.get("character_typeclass"))
character, errs = Character.create( if errs:
account.key, errors.extend(errs)
account,
ip=ip,
typeclass=character_typeclass,
permissions=permissions,
home=character_home,
)
errors.extend(errs)
if character:
# Update playable character list
if character not in account.characters:
account.db._playable_characters.append(character)
# We need to set this to have @ic auto-connect to this character
account.db._last_puppet = character
except Exception: except Exception:
# We are in the middle between logged in and -not, so we have # We are in the middle between logged in and -not, so we have
@ -1540,7 +1565,7 @@ class DefaultGuest(DefaultAccount):
try: try:
# Find an available guest name. # Find an available guest name.
for name in settings.GUEST_LIST: for name in settings.GUEST_LIST:
if not AccountDB.objects.filter(username__iexact=name).count(): if not AccountDB.objects.filter(username__iexact=name).exists():
username = name username = name
break break
if not username: if not username:
@ -1566,6 +1591,15 @@ class DefaultGuest(DefaultAccount):
ip=ip, ip=ip,
) )
errors.extend(errs) errors.extend(errs)
if not account.characters:
# this can happen for multisession_mode > 1. For guests we
# always auto-create a character, regardless of multi-session-mode.
character, errs = account.create_character()
if errs:
errors.extend(errs)
return account, errors return account, errors
except Exception as e: except Exception as e:

View file

@ -17,7 +17,7 @@ import datetime
from anything import Anything from anything import Anything
from django.conf import settings from django.conf import settings
from mock import Mock, mock from unittest.mock import patch, Mock, MagicMock
from evennia import DefaultRoom, DefaultExit, ObjectDB from evennia import DefaultRoom, DefaultExit, ObjectDB
from evennia.commands.default.cmdset_character import CharacterCmdSet from evennia.commands.default.cmdset_character import CharacterCmdSet
@ -56,6 +56,7 @@ _RE = re.compile(r"^\+|-+\+|\+-+|--+|\|(?:\s|$)", re.MULTILINE)
# ------------------------------------------------------------ # ------------------------------------------------------------
@patch("evennia.server.portal.portal.LoopingCall", new=MagicMock())
class CommandTest(EvenniaTest): class CommandTest(EvenniaTest):
""" """
Tests a command Tests a command
@ -505,7 +506,7 @@ class TestBuilding(CommandTest):
self.call(building.CmdSetAttribute(), "Obj2/test2", "Attribute Obj2/test2 = value2") self.call(building.CmdSetAttribute(), "Obj2/test2", "Attribute Obj2/test2 = value2")
self.call(building.CmdSetAttribute(), "Obj2/NotFound", "Obj2 has no attribute 'notfound'.") self.call(building.CmdSetAttribute(), "Obj2/NotFound", "Obj2 has no attribute 'notfound'.")
with mock.patch("evennia.commands.default.building.EvEditor") as mock_ed: with patch("evennia.commands.default.building.EvEditor") as mock_ed:
self.call(building.CmdSetAttribute(), "/edit Obj2/test3") self.call(building.CmdSetAttribute(), "/edit Obj2/test3")
mock_ed.assert_called_with(self.char1, Anything, Anything, key="Obj2/test3") mock_ed.assert_called_with(self.char1, Anything, Anything, key="Obj2/test3")
@ -789,7 +790,7 @@ class TestBuilding(CommandTest):
) )
self.call(building.CmdDesc(), "", "Usage: ") self.call(building.CmdDesc(), "", "Usage: ")
with mock.patch("evennia.commands.default.building.EvEditor") as mock_ed: with patch("evennia.commands.default.building.EvEditor") as mock_ed:
self.call(building.CmdDesc(), "/edit") self.call(building.CmdDesc(), "/edit")
mock_ed.assert_called_with( mock_ed.assert_called_with(
self.char1, self.char1,
@ -1004,9 +1005,9 @@ class TestBuilding(CommandTest):
} }
) )
] ]
with mock.patch( with patch(
"evennia.commands.default.building.protlib.search_prototype", "evennia.commands.default.building.protlib.search_prototype",
new=mock.MagicMock(return_value=test_prototype), new=MagicMock(return_value=test_prototype),
) as mprot: ) as mprot:
self.call( self.call(
building.CmdTypeclass(), building.CmdTypeclass(),
@ -1072,7 +1073,7 @@ class TestBuilding(CommandTest):
self.call(building.CmdFind(), "/exact Obj", "One Match") self.call(building.CmdFind(), "/exact Obj", "One Match")
# Test multitype filtering # Test multitype filtering
with mock.patch( with patch(
"evennia.commands.default.building.CHAR_TYPECLASS", "evennia.commands.default.building.CHAR_TYPECLASS",
"evennia.objects.objects.DefaultCharacter", "evennia.objects.objects.DefaultCharacter",
): ):
@ -1540,11 +1541,11 @@ class TestSystemCommands(CommandTest):
self.call(multimatch, "look", "") self.call(multimatch, "look", "")
@mock.patch("evennia.commands.default.syscommands.ChannelDB") @patch("evennia.commands.default.syscommands.ChannelDB")
def test_channelcommand(self, mock_channeldb): def test_channelcommand(self, mock_channeldb):
channel = mock.MagicMock() channel = MagicMock()
channel.msg = mock.MagicMock() channel.msg = MagicMock()
mock_channeldb.objects.get_channel = mock.MagicMock(return_value=channel) mock_channeldb.objects.get_channel = MagicMock(return_value=channel)
self.call(syscommands.SystemSendToChannel(), "public:Hello") self.call(syscommands.SystemSendToChannel(), "public:Hello")
channel.msg.assert_called() channel.msg.assert_called()

View file

@ -163,6 +163,7 @@ class Portal(object):
self.server_info_dict = {} self.server_info_dict = {}
self.start_time = time.time() self.start_time = time.time()
self.maintenance_task = LoopingCall(_portal_maintenance) self.maintenance_task = LoopingCall(_portal_maintenance)
self.maintenance_task.start(60, now=True) # call every minute self.maintenance_task.start(60, now=True) # call every minute

View file

@ -6,7 +6,7 @@ Test AMP client
import pickle import pickle
from model_mommy import mommy from model_mommy import mommy
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch from unittest.mock import MagicMock, patch
from twisted.trial.unittest import TestCase as TwistedTestCase from twisted.trial.unittest import TestCase as TwistedTestCase
from evennia.server import amp_client from evennia.server import amp_client
from evennia.server.portal import amp_server from evennia.server.portal import amp_server
@ -36,6 +36,7 @@ class _TestAMP(TwistedTestCase):
self.server.sessions[1] = self.session self.server.sessions[1] = self.session
self.portal = portal.Portal(MagicMock()) self.portal = portal.Portal(MagicMock())
self.portal.maintenance_task.stop()
self.portalsession = session.Session() self.portalsession = session.Session()
self.portalsession.sessid = 1 self.portalsession.sessid = 1
self.portal.sessions[1] = self.portalsession self.portal.sessions[1] = self.portalsession

View file

@ -6,6 +6,7 @@ Runs as part of the Evennia's test suite with 'evennia test evennia"
""" """
from django.test.runner import DiscoverRunner from django.test.runner import DiscoverRunner
from unittest import mock
class EvenniaTestSuiteRunner(DiscoverRunner): class EvenniaTestSuiteRunner(DiscoverRunner):
@ -21,9 +22,16 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
Build a test suite for Evennia. test_labels is a list of apps to test. Build a test suite for Evennia. test_labels is a list of apps to test.
If not given, a subset of settings.INSTALLED_APPS will be used. If not given, a subset of settings.INSTALLED_APPS will be used.
""" """
# the portal looping call starts before the unit-test suite so we
# can't mock it - instead we stop it before starting the test - otherwise
# we'd get unclean reactor errors across test boundaries.
from evennia.server.portal.portal import PORTAL
PORTAL.maintenance_task.stop()
import evennia import evennia
evennia._init() evennia._init()
return super(EvenniaTestSuiteRunner, self).build_suite( return super(EvenniaTestSuiteRunner, self).build_suite(
test_labels, extra_tests=extra_tests, **kwargs test_labels, extra_tests=extra_tests, **kwargs
) )