Change save/search_prototype, extend unittests

This commit is contained in:
Griatch 2019-02-09 16:52:02 +01:00
parent dac10eef2b
commit 0dfea46d5c
12 changed files with 154 additions and 39 deletions

View file

@ -53,6 +53,15 @@ Web/Django standard initiative (@strikaco)
- Bugfixes - Bugfixes
- Fixes bug on login page where error messages were not being displayed - Fixes bug on login page where error messages were not being displayed
### Prototypes
- `evennia.prototypes.save_prototype` now takes the prototype as a normal
argument (`prototype`) instead of having to give it as `**prototype`.
- `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that
raises a KeyError exception if query gave 0 or >1 results.
- `evennia.prototypes.spawner` can now spawn by passing a `prototype_key`
### Typeclasses ### Typeclasses
- Add new methods on all typeclasses, useful specifically for object handling from the website/admin: - Add new methods on all typeclasses, useful specifically for object handling from the website/admin:

View file

@ -1494,7 +1494,6 @@ class DefaultGuest(DefaultAccount):
characters = self.db._playable_characters characters = self.db._playable_characters
for character in characters: for character in characters:
if character: if character:
print "deleting Character:", character
character.delete() character.delete()
def at_post_disconnect(self, **kwargs): def at_post_disconnect(self, **kwargs):

View file

@ -1,13 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from mock import Mock, MagicMock import sys
from mock import Mock, MagicMock, patch
from random import randint from random import randint
from unittest import TestCase from unittest import TestCase
from django.test import override_settings from django.test import override_settings
from evennia.accounts.accounts import AccountSessionHandler from evennia.accounts.accounts import AccountSessionHandler
from evennia.accounts.accounts import DefaultAccount, DefaultGuest from evennia.accounts.accounts import DefaultAccount, DefaultGuest
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest, unload_module
from evennia.utils import create from evennia.utils import create
from django.conf import settings from django.conf import settings
@ -60,6 +61,7 @@ class TestAccountSessionHandler(TestCase):
"Check count method" "Check count method"
self.assertEqual(self.handler.count(), len(self.handler.get())) self.assertEqual(self.handler.count(), len(self.handler.get()))
class TestDefaultGuest(EvenniaTest): class TestDefaultGuest(EvenniaTest):
"Check DefaultGuest class" "Check DefaultGuest class"
@ -162,6 +164,7 @@ class TestDefaultAccountAuth(EvenniaTest):
self.assertFalse(account.set_password('Mxyzptlk')) self.assertFalse(account.set_password('Mxyzptlk'))
account.delete() account.delete()
class TestDefaultAccount(TestCase): class TestDefaultAccount(TestCase):
"Check DefaultAccount class" "Check DefaultAccount class"
@ -279,13 +282,36 @@ class TestAccountPuppetDeletion(EvenniaTest):
@override_settings(MULTISESSION_MODE=2) @override_settings(MULTISESSION_MODE=2)
def test_puppet_deletion(self): def test_puppet_deletion(self):
# Check for existing chars # Check for existing chars
self.assertFalse(self.account.db._playable_characters, 'Account should not have any chars by default.') self.assertFalse(self.account.db._playable_characters,
'Account should not have any chars by default.')
# Add char1 to account's playable characters # Add char1 to account's playable characters
self.account.db._playable_characters.append(self.char1) self.account.db._playable_characters.append(self.char1)
self.assertTrue(self.account.db._playable_characters, 'Char was not added to account.') self.assertTrue(self.account.db._playable_characters,
'Char was not added to account.')
# See what happens when we delete char1. # See what happens when we delete char1.
self.char1.delete() self.char1.delete()
# Playable char list should be empty. # Playable char list should be empty.
self.assertFalse(self.account.db._playable_characters, 'Playable character list is not empty! %s' % self.account.db._playable_characters) self.assertFalse(self.account.db._playable_characters,
'Playable character list is not empty! %s' % self.account.db._playable_characters)
class TestDefaultAccountEv(EvenniaTest):
"""
Testing using the EvenniaTest parent
"""
def test_characters_property(self):
"test existence of None in _playable_characters Attr"
self.account.db._playable_characters = [self.char1, None]
chars = self.account.characters
self.assertEqual(chars, [self.char1])
self.assertEqual(self.account.db._playable_characters, [self.char1])
def test_puppet_success(self):
unload_module(DefaultAccount)
self.account.msg = MagicMock()
with patch("evennia.accounts.accounts._MULTISESSION_MODE", 2):
self.account.puppet_object(self.session, self.char1)
self.account.msg.assert_called_with("You are already puppeting this object.")

View file

@ -2978,7 +2978,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
# all seems ok. Try to save. # all seems ok. Try to save.
try: try:
prot = protlib.save_prototype(**prototype) prot = protlib.save_prototype(prototype)
if not prot: if not prot:
caller.msg("|rError saving:|R {}.|n".format(prototype_key)) caller.msg("|rError saving:|R {}.|n".format(prototype_key))
return return

View file

@ -680,9 +680,9 @@ class TestBuilding(CommandTest):
goblin.delete() goblin.delete()
# create prototype # create prototype
protlib.create_prototype(**{'key': 'Ball', protlib.create_prototype({'key': 'Ball',
'typeclass': 'evennia.objects.objects.DefaultCharacter', 'typeclass': 'evennia.objects.objects.DefaultCharacter',
'prototype_key': 'testball'}) 'prototype_key': 'testball'})
# Tests "@spawn <prototype_name>" # Tests "@spawn <prototype_name>"
self.call(building.CmdSpawn(), "testball", "Spawned Ball") self.call(building.CmdSpawn(), "testball", "Spawned Ball")

View file

@ -262,7 +262,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
con = self.contents_cache.get(exclude=exclude) con = self.contents_cache.get(exclude=exclude)
# print "contents_get:", self, con, id(self), calledby() # DEBUG # print "contents_get:", self, con, id(self), calledby() # DEBUG
return con return con
contents = property(contents_get)
def contents_set(self, *args):
"You cannot replace this property"
raise AttributeError("{}.contents is read-only. Use obj.move_to or "
"obj.location to move an object here.".format(self.__class__))
contents = property(contents_get, contents_set, contents_set)
@property @property
def exits(self): def exits(self):

View file

@ -76,7 +76,7 @@ from evennia import prototypes
goblin = {"prototype_key": "goblin:, ... } goblin = {"prototype_key": "goblin:, ... }
prototype = prototypes.save_prototype(caller, **goblin) prototype = prototypes.save_prototype(goblin)
``` ```

View file

@ -2138,7 +2138,7 @@ def node_prototype_save(caller, **kwargs):
# we already validated and accepted the save, so this node acts as a goto callback and # we already validated and accepted the save, so this node acts as a goto callback and
# should now only return the next node # should now only return the next node
prototype_key = prototype.get("prototype_key") prototype_key = prototype.get("prototype_key")
protlib.save_prototype(**prototype) protlib.save_prototype(prototype)
spawned_objects = protlib.search_objects_with_prototype(prototype_key) spawned_objects = protlib.search_objects_with_prototype(prototype_key)
nspawned = spawned_objects.count() nspawned = spawned_objects.count()

View file

@ -147,13 +147,13 @@ class DbPrototype(DefaultScript):
# Prototype manager functions # Prototype manager functions
def save_prototype(**kwargs): def save_prototype(prototype):
""" """
Create/Store a prototype persistently. Create/Store a prototype persistently.
Kwargs: Args:
prototype_key (str): This is required for any storage. prototype (dict): The prototype to save. A `prototype_key` key is
All other kwargs are considered part of the new prototype dict. required.
Returns: Returns:
prototype (dict or None): The prototype stored using the given kwargs, None if deleting. prototype (dict or None): The prototype stored using the given kwargs, None if deleting.
@ -166,8 +166,8 @@ def save_prototype(**kwargs):
is expected to have valid permissions. is expected to have valid permissions.
""" """
in_prototype = prototype
kwargs = homogenize_prototype(kwargs) in_prototype = homogenize_prototype(in_prototype)
def _to_batchtuple(inp, *args): def _to_batchtuple(inp, *args):
"build tuple suitable for batch-creation" "build tuple suitable for batch-creation"
@ -176,7 +176,7 @@ def save_prototype(**kwargs):
return inp return inp
return (inp, ) + args return (inp, ) + args
prototype_key = kwargs.get("prototype_key") prototype_key = in_prototype.get("prototype_key")
if not prototype_key: if not prototype_key:
raise ValidationError("Prototype requires a prototype_key") raise ValidationError("Prototype requires a prototype_key")
@ -192,21 +192,21 @@ def save_prototype(**kwargs):
stored_prototype = DbPrototype.objects.filter(db_key=prototype_key) stored_prototype = DbPrototype.objects.filter(db_key=prototype_key)
prototype = stored_prototype[0].prototype if stored_prototype else {} prototype = stored_prototype[0].prototype if stored_prototype else {}
kwargs['prototype_desc'] = kwargs.get("prototype_desc", prototype.get("prototype_desc", "")) in_prototype['prototype_desc'] = in_prototype.get("prototype_desc", prototype.get("prototype_desc", ""))
prototype_locks = kwargs.get( prototype_locks = in_prototype.get(
"prototype_locks", prototype.get('prototype_locks', "spawn:all();edit:perm(Admin)")) "prototype_locks", prototype.get('prototype_locks', "spawn:all();edit:perm(Admin)"))
is_valid, err = validate_lockstring(prototype_locks) is_valid, err = validate_lockstring(prototype_locks)
if not is_valid: if not is_valid:
raise ValidationError("Lock error: {}".format(err)) raise ValidationError("Lock error: {}".format(err))
kwargs['prototype_locks'] = prototype_locks in_prototype['prototype_locks'] = prototype_locks
prototype_tags = [ prototype_tags = [
_to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY) _to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY)
for tag in make_iter(kwargs.get("prototype_tags", for tag in make_iter(in_prototype.get("prototype_tags",
prototype.get('prototype_tags', [])))] prototype.get('prototype_tags', [])))]
kwargs["prototype_tags"] = prototype_tags in_prototype["prototype_tags"] = prototype_tags
prototype.update(kwargs) prototype.update(in_prototype)
if stored_prototype: if stored_prototype:
# edit existing prototype # edit existing prototype
@ -261,19 +261,25 @@ def delete_prototype(prototype_key, caller=None):
return True return True
def search_prototype(key=None, tags=None): def search_prototype(key=None, tags=None, require_single=False):
""" """
Find prototypes based on key and/or tags, or all prototypes. Find prototypes based on key and/or tags, or all prototypes.
Kwargs: Kwargs:
key (str): An exact or partial key to query for. key (str): An exact or partial key to query for.
tags (str or list): Tag key or keys to query for. These tags (str or list): Tag key or keys to query for. These
will always be applied with the 'db_protototype' will always be applied with the 'db_protototype'
tag category. tag category.
require_single (bool): If set, raise KeyError if the result
was not found or if there are multiple matches.
Return: Return:
matches (list): All found prototype dicts. If no keys matches (list): All found prototype dicts. Empty list if
or tags are given, all available prototypes will be returned. no match was found. Note that if neither `key` nor `tags`
were given, *all* available prototypes will be returned.
Raises:
KeyError: If `require_single` is True and there are 0 or >1 matches.
Note: Note:
The available prototypes is a combination of those supplied in The available prototypes is a combination of those supplied in
@ -329,6 +335,10 @@ def search_prototype(key=None, tags=None):
if mta.get('prototype_key') and mta['prototype_key'] == key] if mta.get('prototype_key') and mta['prototype_key'] == key]
if filter_matches and len(filter_matches) < nmatches: if filter_matches and len(filter_matches) < nmatches:
matches = filter_matches matches = filter_matches
nmatches = len(matches)
if nmatches != 1 and require_single:
raise KeyError("Found {} matching prototypes.".format(nmatches))
return matches return matches

View file

@ -23,7 +23,7 @@ prot = {
"attrs": [("weapon", "sword")] "attrs": [("weapon", "sword")]
} }
prot = prototypes.create_prototype(**prot) prot = prototypes.create_prototype(prot)
``` ```
@ -662,8 +662,9 @@ def spawn(*prototypes, **kwargs):
Spawn a number of prototyped objects. Spawn a number of prototyped objects.
Args: Args:
prototypes (dict): Each argument should be a prototype prototypes (str or dict): Each argument should either be a
dictionary. prototype_key (will be used to find the prototype) or a full prototype
dictionary. These will be batched-spawned as one object each.
Kwargs: Kwargs:
prototype_modules (str or list): A python-path to a prototype prototype_modules (str or list): A python-path to a prototype
module, or a list of such paths. These will be used to build module, or a list of such paths. These will be used to build
@ -686,6 +687,11 @@ def spawn(*prototypes, **kwargs):
`return_parents` is set, instead return dict of prototype parents. `return_parents` is set, instead return dict of prototype parents.
""" """
# search string (=prototype_key) from input
prototypes = [protlib.search_prototype(prot, require_single=True)[0]
if isinstance(prot, basestring) else prot
for prot in prototypes]
# get available protparents # get available protparents
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}

View file

@ -54,7 +54,7 @@ class TestSpawner(EvenniaTest):
self.prot1 = {"prototype_key": "testprototype", self.prot1 = {"prototype_key": "testprototype",
"typeclass": "evennia.objects.objects.DefaultObject"} "typeclass": "evennia.objects.objects.DefaultObject"}
def test_spawn(self): def test_spawn_from_prot(self):
obj1 = spawner.spawn(self.prot1) obj1 = spawner.spawn(self.prot1)
# check spawned objects have the right tag # check spawned objects have the right tag
self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1) self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1)
@ -62,6 +62,14 @@ class TestSpawner(EvenniaTest):
_PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"], _PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"],
prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard']) prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard'])
def test_spawn_from_str(self):
protlib.save_prototype(self.prot1)
obj1 = spawner.spawn(self.prot1['prototype_key'])
self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1)
self.assertEqual([o.key for o in spawner.spawn(
_PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"],
prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard'])
class TestUtils(EvenniaTest): class TestUtils(EvenniaTest):
@ -245,6 +253,7 @@ class TestProtLib(EvenniaTest):
super(TestProtLib, self).setUp() super(TestProtLib, self).setUp()
self.obj1.attributes.add("testattr", "testval") self.obj1.attributes.add("testattr", "testval")
self.prot = spawner.prototype_from_object(self.obj1) self.prot = spawner.prototype_from_object(self.obj1)
def test_prototype_to_str(self): def test_prototype_to_str(self):
prstr = protlib.prototype_to_str(self.prot) prstr = protlib.prototype_to_str(self.prot)
@ -253,6 +262,22 @@ class TestProtLib(EvenniaTest):
def test_check_permission(self): def test_check_permission(self):
pass pass
def test_save_prototype(self):
result = protlib.save_prototype(self.prot)
self.assertEqual(result, self.prot)
# faulty
self.prot['prototype_key'] = None
self.assertRaises(protlib.ValidationError, protlib.save_prototype, self.prot)
def test_search_prototype(self):
protlib.save_prototype(self.prot)
match = protlib.search_prototype("NotFound")
self.assertFalse(match)
match = protlib.search_prototype()
self.assertTrue(match)
match = protlib.search_prototype(self.prot['prototype_key'])
self.assertEqual(match, [self.prot])
@override_settings(PROT_FUNC_MODULES=['evennia.prototypes.protfuncs'], CLIENT_DEFAULT_WIDTH=20) @override_settings(PROT_FUNC_MODULES=['evennia.prototypes.protfuncs'], CLIENT_DEFAULT_WIDTH=20)
class TestProtFuncs(EvenniaTest): class TestProtFuncs(EvenniaTest):
@ -424,7 +449,7 @@ class TestPrototypeStorage(EvenniaTest):
def test_prototype_storage(self): def test_prototype_storage(self):
# from evennia import set_trace;set_trace(term_size=(180, 50)) # from evennia import set_trace;set_trace(term_size=(180, 50))
prot1 = protlib.create_prototype(**self.prot1) prot1 = protlib.create_prototype(self.prot1)
self.assertTrue(bool(prot1)) self.assertTrue(bool(prot1))
self.assertEqual(prot1, self.prot1) self.assertEqual(prot1, self.prot1)
@ -436,7 +461,7 @@ class TestPrototypeStorage(EvenniaTest):
protlib.DbPrototype.objects.get_by_tag( protlib.DbPrototype.objects.get_by_tag(
"foo1", _PROTOTYPE_TAG_META_CATEGORY)[0].db.prototype, prot1) "foo1", _PROTOTYPE_TAG_META_CATEGORY)[0].db.prototype, prot1)
prot2 = protlib.create_prototype(**self.prot2) prot2 = protlib.create_prototype(self.prot2)
self.assertEqual( self.assertEqual(
[pobj.db.prototype [pobj.db.prototype
for pobj in protlib.DbPrototype.objects.get_by_tag( for pobj in protlib.DbPrototype.objects.get_by_tag(
@ -445,7 +470,7 @@ class TestPrototypeStorage(EvenniaTest):
# add to existing prototype # add to existing prototype
prot1b = protlib.create_prototype( prot1b = protlib.create_prototype(
prototype_key='testprototype1', foo='bar', prototype_tags=['foo2']) {"prototype_key": 'testprototype1', "foo": 'bar', "prototype_tags": ['foo2']})
self.assertEqual( self.assertEqual(
[pobj.db.prototype [pobj.db.prototype
@ -457,7 +482,7 @@ class TestPrototypeStorage(EvenniaTest):
self.assertNotEqual(list(protlib.search_prototype("testprototype1")), [prot1]) self.assertNotEqual(list(protlib.search_prototype("testprototype1")), [prot1])
self.assertEqual(list(protlib.search_prototype("testprototype1")), [prot1b]) self.assertEqual(list(protlib.search_prototype("testprototype1")), [prot1b])
prot3 = protlib.create_prototype(**self.prot3) prot3 = protlib.create_prototype(self.prot3)
# partial match # partial match
with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}): with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}):
@ -606,7 +631,7 @@ class TestMenuModule(EvenniaTest):
self.assertEqual(olc_menus._display_tag(olc_menus._get_menu_prototype(caller)['tags'][0]), Something) self.assertEqual(olc_menus._display_tag(olc_menus._get_menu_prototype(caller)['tags'][0]), Something)
self.assertEqual(olc_menus._caller_tags(caller), ["foo2", "foo3"]) self.assertEqual(olc_menus._caller_tags(caller), ["foo2", "foo3"])
protlib.save_prototype(**self.test_prot) protlib.save_prototype(self.test_prot)
# locks helpers # locks helpers
self.assertEqual(olc_menus._lock_add(caller, "foo:false()"), "Added lock 'foo:false()'.") self.assertEqual(olc_menus._lock_add(caller, "foo:false()"), "Added lock 'foo:false()'.")

View file

@ -1,3 +1,8 @@
"""
Various helper resources for writing unittests.
"""
import sys
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from mock import Mock from mock import Mock
@ -14,6 +19,35 @@ SESSIONS.data_out = Mock()
SESSIONS.disconnect = Mock() SESSIONS.disconnect = Mock()
def unload_module(module_or_object):
"""
Reset import so one can mock global constants.
Args:
module_or_object (module or object): The module will
be removed so it will have to be imported again.
Example:
# (in a test method)
unload_module(foo)
with mock.patch("foo.GLOBALTHING", "mockval"):
import foo
... # test code using foo.GLOBALTHING, now set to 'mockval'
This allows for mocking constants global to the module, since
otherwise those would not be mocked (since a module is only
loaded once).
"""
if hasattr(module_or_object, "__module__"):
modulename = module_or_object.__module__
else:
modulename = module_or_object.__name__
if modulename in sys.modules:
del sys.modules[modulename]
class EvenniaTest(TestCase): class EvenniaTest(TestCase):
""" """
Base test for Evennia, sets up a basic environment. Base test for Evennia, sets up a basic environment.