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
- 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
- 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
for character in characters:
if character:
print "deleting Character:", character
character.delete()
def at_post_disconnect(self, **kwargs):

View file

@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
from mock import Mock, MagicMock
import sys
from mock import Mock, MagicMock, patch
from random import randint
from unittest import TestCase
from django.test import override_settings
from evennia.accounts.accounts import AccountSessionHandler
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 django.conf import settings
@ -60,6 +61,7 @@ class TestAccountSessionHandler(TestCase):
"Check count method"
self.assertEqual(self.handler.count(), len(self.handler.get()))
class TestDefaultGuest(EvenniaTest):
"Check DefaultGuest class"
@ -162,6 +164,7 @@ class TestDefaultAccountAuth(EvenniaTest):
self.assertFalse(account.set_password('Mxyzptlk'))
account.delete()
class TestDefaultAccount(TestCase):
"Check DefaultAccount class"
@ -279,13 +282,36 @@ class TestAccountPuppetDeletion(EvenniaTest):
@override_settings(MULTISESSION_MODE=2)
def test_puppet_deletion(self):
# 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
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.
self.char1.delete()
# 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.
try:
prot = protlib.save_prototype(**prototype)
prot = protlib.save_prototype(prototype)
if not prot:
caller.msg("|rError saving:|R {}.|n".format(prototype_key))
return

View file

@ -680,9 +680,9 @@ class TestBuilding(CommandTest):
goblin.delete()
# create prototype
protlib.create_prototype(**{'key': 'Ball',
'typeclass': 'evennia.objects.objects.DefaultCharacter',
'prototype_key': 'testball'})
protlib.create_prototype({'key': 'Ball',
'typeclass': 'evennia.objects.objects.DefaultCharacter',
'prototype_key': 'testball'})
# Tests "@spawn <prototype_name>"
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)
# print "contents_get:", self, con, id(self), calledby() # DEBUG
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
def exits(self):

View file

@ -76,7 +76,7 @@ from evennia import prototypes
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
# should now only return the next node
prototype_key = prototype.get("prototype_key")
protlib.save_prototype(**prototype)
protlib.save_prototype(prototype)
spawned_objects = protlib.search_objects_with_prototype(prototype_key)
nspawned = spawned_objects.count()

View file

@ -147,13 +147,13 @@ class DbPrototype(DefaultScript):
# Prototype manager functions
def save_prototype(**kwargs):
def save_prototype(prototype):
"""
Create/Store a prototype persistently.
Kwargs:
prototype_key (str): This is required for any storage.
All other kwargs are considered part of the new prototype dict.
Args:
prototype (dict): The prototype to save. A `prototype_key` key is
required.
Returns:
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.
"""
kwargs = homogenize_prototype(kwargs)
in_prototype = prototype
in_prototype = homogenize_prototype(in_prototype)
def _to_batchtuple(inp, *args):
"build tuple suitable for batch-creation"
@ -176,7 +176,7 @@ def save_prototype(**kwargs):
return inp
return (inp, ) + args
prototype_key = kwargs.get("prototype_key")
prototype_key = in_prototype.get("prototype_key")
if not 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)
prototype = stored_prototype[0].prototype if stored_prototype else {}
kwargs['prototype_desc'] = kwargs.get("prototype_desc", prototype.get("prototype_desc", ""))
prototype_locks = kwargs.get(
in_prototype['prototype_desc'] = in_prototype.get("prototype_desc", prototype.get("prototype_desc", ""))
prototype_locks = in_prototype.get(
"prototype_locks", prototype.get('prototype_locks', "spawn:all();edit:perm(Admin)"))
is_valid, err = validate_lockstring(prototype_locks)
if not is_valid:
raise ValidationError("Lock error: {}".format(err))
kwargs['prototype_locks'] = prototype_locks
in_prototype['prototype_locks'] = prototype_locks
prototype_tags = [
_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', [])))]
kwargs["prototype_tags"] = prototype_tags
in_prototype["prototype_tags"] = prototype_tags
prototype.update(kwargs)
prototype.update(in_prototype)
if stored_prototype:
# edit existing prototype
@ -261,19 +261,25 @@ def delete_prototype(prototype_key, caller=None):
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.
Kwargs:
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'
tag category.
require_single (bool): If set, raise KeyError if the result
was not found or if there are multiple matches.
Return:
matches (list): All found prototype dicts. If no keys
or tags are given, all available prototypes will be returned.
matches (list): All found prototype dicts. Empty list if
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:
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 filter_matches and len(filter_matches) < nmatches:
matches = filter_matches
nmatches = len(matches)
if nmatches != 1 and require_single:
raise KeyError("Found {} matching prototypes.".format(nmatches))
return matches

View file

@ -23,7 +23,7 @@ prot = {
"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.
Args:
prototypes (dict): Each argument should be a prototype
dictionary.
prototypes (str or dict): Each argument should either be a
prototype_key (will be used to find the prototype) or a full prototype
dictionary. These will be batched-spawned as one object each.
Kwargs:
prototype_modules (str or list): A python-path to a prototype
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.
"""
# 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
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",
"typeclass": "evennia.objects.objects.DefaultObject"}
def test_spawn(self):
def test_spawn_from_prot(self):
obj1 = spawner.spawn(self.prot1)
# check spawned objects have the right tag
self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1)
@ -62,6 +62,14 @@ class TestSpawner(EvenniaTest):
_PROTPARENTS["GOBLIN"], _PROTPARENTS["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):
@ -245,6 +253,7 @@ class TestProtLib(EvenniaTest):
super(TestProtLib, self).setUp()
self.obj1.attributes.add("testattr", "testval")
self.prot = spawner.prototype_from_object(self.obj1)
def test_prototype_to_str(self):
prstr = protlib.prototype_to_str(self.prot)
@ -253,6 +262,22 @@ class TestProtLib(EvenniaTest):
def test_check_permission(self):
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)
class TestProtFuncs(EvenniaTest):
@ -424,7 +449,7 @@ class TestPrototypeStorage(EvenniaTest):
def test_prototype_storage(self):
# 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.assertEqual(prot1, self.prot1)
@ -436,7 +461,7 @@ class TestPrototypeStorage(EvenniaTest):
protlib.DbPrototype.objects.get_by_tag(
"foo1", _PROTOTYPE_TAG_META_CATEGORY)[0].db.prototype, prot1)
prot2 = protlib.create_prototype(**self.prot2)
prot2 = protlib.create_prototype(self.prot2)
self.assertEqual(
[pobj.db.prototype
for pobj in protlib.DbPrototype.objects.get_by_tag(
@ -445,7 +470,7 @@ class TestPrototypeStorage(EvenniaTest):
# add to existing prototype
prot1b = protlib.create_prototype(
prototype_key='testprototype1', foo='bar', prototype_tags=['foo2'])
{"prototype_key": 'testprototype1', "foo": 'bar', "prototype_tags": ['foo2']})
self.assertEqual(
[pobj.db.prototype
@ -457,7 +482,7 @@ class TestPrototypeStorage(EvenniaTest):
self.assertNotEqual(list(protlib.search_prototype("testprototype1")), [prot1])
self.assertEqual(list(protlib.search_prototype("testprototype1")), [prot1b])
prot3 = protlib.create_prototype(**self.prot3)
prot3 = protlib.create_prototype(self.prot3)
# partial match
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._caller_tags(caller), ["foo2", "foo3"])
protlib.save_prototype(**self.test_prot)
protlib.save_prototype(self.test_prot)
# locks helpers
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.test import TestCase
from mock import Mock
@ -14,6 +19,35 @@ SESSIONS.data_out = 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):
"""
Base test for Evennia, sets up a basic environment.