Add tests of xmodule.model and xmodule.runtime, and fix some exposed bugs
This commit is contained in:
@@ -133,8 +133,6 @@ class NamespaceDescriptor(object):
|
||||
self._namespace = namespace
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if owner is None:
|
||||
return self
|
||||
return self._namespace(instance)
|
||||
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ class KeyValueStore(object):
|
||||
# data.
|
||||
Key = namedtuple("Key", "scope, student_id, module_scope_id, field_name")
|
||||
|
||||
def get(key):
|
||||
def get(self, key):
|
||||
pass
|
||||
|
||||
def set(key, value):
|
||||
def set(self, key, value):
|
||||
pass
|
||||
|
||||
def delete(key):
|
||||
def delete(self, key):
|
||||
pass
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class DbModel(MutableMapping):
|
||||
for namespace_name in self._module_cls.namespaces:
|
||||
namespace = getattr(self._module_cls, namespace_name)
|
||||
namespace_field = getattr(type(namespace), name, None)
|
||||
if namespace_field is not None and isinstance(module_field, ModelType):
|
||||
if namespace_field is not None and isinstance(namespace_field, ModelType):
|
||||
return namespace_field
|
||||
|
||||
# Not in the class or in any of the namespaces, so name
|
||||
@@ -59,7 +59,6 @@ class DbModel(MutableMapping):
|
||||
|
||||
def _key(self, name):
|
||||
field = self._getfield(name)
|
||||
print name, field
|
||||
module = field.scope.module
|
||||
|
||||
if module == ModuleScope.ALL:
|
||||
@@ -69,7 +68,7 @@ class DbModel(MutableMapping):
|
||||
elif module == ModuleScope.DEFINITION:
|
||||
module_id = self._usage.def_id
|
||||
elif module == ModuleScope.TYPE:
|
||||
module_id = self.module_type.__name__
|
||||
module_id = self._module_cls.__name__
|
||||
|
||||
if field.scope.student:
|
||||
student_id = self._student_id
|
||||
|
||||
@@ -1,8 +1,154 @@
|
||||
from mock import patch
|
||||
from unittest import TestCase
|
||||
from nose.tools import assert_in, assert_equals, assert_raises
|
||||
|
||||
class ModelMetaclassTester(object):
|
||||
__metaclass__ = ModelMetaclass
|
||||
from xmodule.model import *
|
||||
|
||||
field_a = Int(scope=Scope.settings)
|
||||
field_b = Int(scope=Scope.content)
|
||||
|
||||
def test_model_metaclass():
|
||||
class ModelMetaclassTester(object):
|
||||
__metaclass__ = ModelMetaclass
|
||||
|
||||
field_a = Int(scope=Scope.settings)
|
||||
field_b = Int(scope=Scope.content)
|
||||
|
||||
def __init__(self, model_data):
|
||||
self._model_data = model_data
|
||||
|
||||
assert hasattr(ModelMetaclassTester, 'field_a')
|
||||
assert hasattr(ModelMetaclassTester, 'field_b')
|
||||
|
||||
assert_in(ModelMetaclassTester.field_a, ModelMetaclassTester.fields)
|
||||
assert_in(ModelMetaclassTester.field_b, ModelMetaclassTester.fields)
|
||||
|
||||
|
||||
def test_parent_metaclass():
|
||||
|
||||
class HasChildren(object):
|
||||
__metaclass__ = ParentModelMetaclass
|
||||
|
||||
has_children = True
|
||||
|
||||
class WithoutChildren(object):
|
||||
__metaclass = ParentModelMetaclass
|
||||
|
||||
assert hasattr(HasChildren, 'children')
|
||||
assert not hasattr(WithoutChildren, 'children')
|
||||
|
||||
assert isinstance(HasChildren.children, List)
|
||||
assert_equals(Scope.settings, HasChildren.children.scope)
|
||||
|
||||
|
||||
def test_field_access():
|
||||
class FieldTester(object):
|
||||
__metaclass__ = ModelMetaclass
|
||||
|
||||
field_a = Int(scope=Scope.settings)
|
||||
field_b = Int(scope=Scope.content, default=10)
|
||||
field_c = Int(scope=Scope.student_state, computed_default=lambda s: s.field_a + s.field_b)
|
||||
|
||||
def __init__(self, model_data):
|
||||
self._model_data = model_data
|
||||
|
||||
field_tester = FieldTester({'field_a': 5, 'field_x': 15})
|
||||
|
||||
assert_equals(5, field_tester.field_a)
|
||||
assert_equals(10, field_tester.field_b)
|
||||
assert_equals(15, field_tester.field_c)
|
||||
assert not hasattr(field_tester, 'field_x')
|
||||
|
||||
field_tester.field_a = 20
|
||||
assert_equals(20, field_tester._model_data['field_a'])
|
||||
assert_equals(10, field_tester.field_b)
|
||||
assert_equals(30, field_tester.field_c)
|
||||
|
||||
del field_tester.field_a
|
||||
assert_equals(None, field_tester.field_a)
|
||||
assert hasattr(FieldTester, 'field_a')
|
||||
|
||||
|
||||
class TestNamespace(Namespace):
|
||||
field_x = List(scope=Scope.content)
|
||||
field_y = String(scope=Scope.student_state, default="default_value")
|
||||
|
||||
|
||||
@patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)])
|
||||
def test_namespace_metaclass(mock_load_classes):
|
||||
class TestClass(object):
|
||||
__metaclass__ = NamespacesMetaclass
|
||||
|
||||
assert hasattr(TestClass, 'test')
|
||||
assert hasattr(TestClass.test, 'field_x')
|
||||
assert hasattr(TestClass.test, 'field_y')
|
||||
|
||||
assert_in(TestNamespace.field_x, TestClass.test.fields)
|
||||
assert_in(TestNamespace.field_y, TestClass.test.fields)
|
||||
assert isinstance(TestClass.test, Namespace)
|
||||
|
||||
|
||||
@patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)])
|
||||
def test_namespace_field_access(mock_load_classes):
|
||||
class Metaclass(ModelMetaclass, NamespacesMetaclass):
|
||||
pass
|
||||
|
||||
class FieldTester(object):
|
||||
__metaclass__ = Metaclass
|
||||
|
||||
field_a = Int(scope=Scope.settings)
|
||||
field_b = Int(scope=Scope.content, default=10)
|
||||
field_c = Int(scope=Scope.student_state, computed_default=lambda s: s.field_a + s.field_b)
|
||||
|
||||
def __init__(self, model_data):
|
||||
self._model_data = model_data
|
||||
|
||||
field_tester = FieldTester({
|
||||
'field_a': 5,
|
||||
'field_x': [1, 2, 3],
|
||||
})
|
||||
|
||||
assert_equals(5, field_tester.field_a)
|
||||
assert_equals(10, field_tester.field_b)
|
||||
assert_equals(15, field_tester.field_c)
|
||||
assert_equals([1, 2, 3], field_tester.test.field_x)
|
||||
assert_equals('default_value', field_tester.test.field_y)
|
||||
|
||||
field_tester.test.field_x = ['a', 'b']
|
||||
assert_equals(['a', 'b'], field_tester._model_data['field_x'])
|
||||
|
||||
del field_tester.test.field_x
|
||||
assert_equals(None, field_tester.test.field_x)
|
||||
|
||||
assert_raises(AttributeError, getattr, field_tester.test, 'field_z')
|
||||
assert_raises(AttributeError, delattr, field_tester.test, 'field_z')
|
||||
|
||||
# Namespaces are created on the fly, so setting a new attribute on one
|
||||
# has no long-term effect
|
||||
field_tester.test.field_z = 'foo'
|
||||
assert_raises(AttributeError, getattr, field_tester.test, 'field_z')
|
||||
assert 'field_z' not in field_tester._model_data
|
||||
|
||||
|
||||
def test_field_serialization():
|
||||
|
||||
class CustomField(ModelType):
|
||||
def from_json(self, value):
|
||||
return value['value']
|
||||
|
||||
def to_json(self, value):
|
||||
return {'value': value}
|
||||
|
||||
class FieldTester(object):
|
||||
__metaclass__ = ModelMetaclass
|
||||
|
||||
field = CustomField()
|
||||
|
||||
def __init__(self, model_data):
|
||||
self._model_data = model_data
|
||||
|
||||
field_tester = FieldTester({
|
||||
'field': {'value': 4}
|
||||
})
|
||||
|
||||
assert_equals(4, field_tester.field)
|
||||
field_tester.field = 5
|
||||
assert_equals({'value': 5}, field_tester._model_data['field'])
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from nose.tools import assert_equals
|
||||
from mock import patch
|
||||
|
||||
from xmodule.model import *
|
||||
from xmodule.runtime import *
|
||||
|
||||
class Metaclass(NamespacesMetaclass, ParentModelMetaclass, ModelMetaclass):
|
||||
pass
|
||||
|
||||
|
||||
class TestNamespace(Namespace):
|
||||
n_content = String(scope=Scope.content, default='nc')
|
||||
n_settings = String(scope=Scope.settings, default='ns')
|
||||
n_student_state = String(scope=Scope.student_state, default='nss')
|
||||
n_student_preferences = String(scope=Scope.student_preferences, default='nsp')
|
||||
n_student_info = String(scope=Scope.student_info, default='nsi')
|
||||
n_by_type = String(scope=Scope(False, ModuleScope.TYPE), default='nbt')
|
||||
n_for_all = String(scope=Scope(False, ModuleScope.ALL), default='nfa')
|
||||
n_student_def = String(scope=Scope(True, ModuleScope.DEFINITION), default='nsd')
|
||||
|
||||
|
||||
with patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)]):
|
||||
class TestModel(object):
|
||||
__metaclass__ = Metaclass
|
||||
|
||||
content = String(scope=Scope.content, default='c')
|
||||
settings = String(scope=Scope.settings, default='s')
|
||||
student_state = String(scope=Scope.student_state, default='ss')
|
||||
student_preferences = String(scope=Scope.student_preferences, default='sp')
|
||||
student_info = String(scope=Scope.student_info, default='si')
|
||||
by_type = String(scope=Scope(False, ModuleScope.TYPE), default='bt')
|
||||
for_all = String(scope=Scope(False, ModuleScope.ALL), default='fa')
|
||||
student_def = String(scope=Scope(True, ModuleScope.DEFINITION), default='sd')
|
||||
|
||||
def __init__(self, model_data):
|
||||
self._model_data = model_data
|
||||
|
||||
|
||||
class DictKeyValueStore(KeyValueStore):
|
||||
def __init__(self):
|
||||
self.db = {}
|
||||
|
||||
def get(self, key):
|
||||
return self.db[key]
|
||||
|
||||
def set(self, key, value):
|
||||
self.db[key] = value
|
||||
|
||||
def delete(self, key):
|
||||
del self.db[key]
|
||||
|
||||
|
||||
Usage = namedtuple('Usage', 'id, def_id')
|
||||
|
||||
|
||||
def test_empty():
|
||||
tester = TestModel(DbModel(DictKeyValueStore(), TestModel, 's0', Usage('u0', 'd0')))
|
||||
|
||||
for collection in (tester, tester.test):
|
||||
for field in collection.fields:
|
||||
print "Getting %s from %r" % (field.name, collection)
|
||||
assert_equals(field.default, getattr(collection, field.name))
|
||||
new_value = 'new ' + field.name
|
||||
print "Setting %s to %s on %r" % (field.name, new_value, collection)
|
||||
setattr(collection, field.name, new_value)
|
||||
print "Checking %s on %r" % (field.name, collection)
|
||||
assert_equals(new_value, getattr(collection, field.name))
|
||||
|
||||
Reference in New Issue
Block a user