import factory import json from mock import Mock from django.contrib.auth.models import User from functools import partial from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError, ModelDataCache from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField from xblock.core import Scope, BlockScope from xmodule.modulestore import Location from django.test import TestCase def mock_field(scope, name): field = Mock() field.scope = scope field.name = name return field def mock_descriptor(fields=[], lms_fields=[]): descriptor = Mock() descriptor.stores_state = True descriptor.location = location('def_id') descriptor.module_class.fields = fields descriptor.module_class.lms.fields = lms_fields return descriptor location = partial(Location, 'i4x', 'edX', 'test_course', 'problem') course_id = 'edX/test_course/test' content_key = partial(LmsKeyValueStore.Key, Scope.content, None, location('def_id')) settings_key = partial(LmsKeyValueStore.Key, Scope.settings, None, location('def_id')) student_state_key = partial(LmsKeyValueStore.Key, Scope.student_state, 'user', location('def_id')) student_prefs_key = partial(LmsKeyValueStore.Key, Scope.student_preferences, 'user', 'problem') student_info_key = partial(LmsKeyValueStore.Key, Scope.student_info, 'user', None) class UserFactory(factory.Factory): FACTORY_FOR = User username = 'user' class StudentModuleFactory(factory.Factory): FACTORY_FOR = StudentModule module_type = 'problem' module_state_key = location('def_id').url() student = factory.SubFactory(UserFactory) course_id = course_id state = None class ContentFactory(factory.Factory): FACTORY_FOR = XModuleContentField field_name = 'existing_field' value = json.dumps('old_value') definition_id = location('def_id').url() class SettingsFactory(factory.Factory): FACTORY_FOR = XModuleSettingsField field_name = 'existing_field' value = json.dumps('old_value') usage_id = '%s-%s' % (course_id, location('def_id').url()) class StudentPrefsFactory(factory.Factory): FACTORY_FOR = XModuleStudentPrefsField field_name = 'existing_field' value = json.dumps('old_value') student = factory.SubFactory(UserFactory) module_type = 'problem' class StudentInfoFactory(factory.Factory): FACTORY_FOR = XModuleStudentInfoField field_name = 'existing_field' value = json.dumps('old_value') student = factory.SubFactory(UserFactory) class TestDescriptorFallback(TestCase): def setUp(self): self.desc_md = { 'field_a': 'content', 'field_b': 'settings', } self.kvs = LmsKeyValueStore(self.desc_md, None) def test_get_from_descriptor(self): self.assertEquals('content', self.kvs.get(content_key('field_a'))) self.assertEquals('settings', self.kvs.get(settings_key('field_b'))) def test_write_to_descriptor(self): self.assertRaises(InvalidWriteError, self.kvs.set, content_key('field_a'), 'foo') self.assertEquals('content', self.desc_md['field_a']) self.assertRaises(InvalidWriteError, self.kvs.set, settings_key('field_b'), 'foo') self.assertEquals('settings', self.desc_md['field_b']) self.assertRaises(InvalidWriteError, self.kvs.delete, content_key('field_a')) self.assertEquals('content', self.desc_md['field_a']) self.assertRaises(InvalidWriteError, self.kvs.delete, settings_key('field_b')) self.assertEquals('settings', self.desc_md['field_b']) class TestInvalidScopes(TestCase): def setUp(self): self.desc_md = {} self.user = UserFactory.create() self.mdc = ModelDataCache([mock_descriptor([mock_field(Scope.student_state, 'a_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) def test_invalid_scopes(self): for scope in (Scope(student=True, block=BlockScope.DEFINITION), Scope(student=False, block=BlockScope.TYPE), Scope(student=False, block=BlockScope.ALL)): self.assertRaises(InvalidScopeError, self.kvs.get, LmsKeyValueStore.Key(scope, None, None, 'field')) self.assertRaises(InvalidScopeError, self.kvs.set, LmsKeyValueStore.Key(scope, None, None, 'field'), 'value') self.assertRaises(InvalidScopeError, self.kvs.delete, LmsKeyValueStore.Key(scope, None, None, 'field')) self.assertRaises(InvalidScopeError, self.kvs.has, LmsKeyValueStore.Key(scope, None, None, 'field')) class TestStudentModuleStorage(TestCase): def setUp(self): self.desc_md = {} student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value'})) self.user = student_module.student self.mdc = ModelDataCache([mock_descriptor([mock_field(Scope.student_state, 'a_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) def test_get_existing_field(self): "Test that getting an existing field in an existing StudentModule works" self.assertEquals('a_value', self.kvs.get(student_state_key('a_field'))) def test_get_missing_field(self): "Test that getting a missing field from an existing StudentModule raises a KeyError" self.assertRaises(KeyError, self.kvs.get, student_state_key('not_a_field')) def test_set_existing_field(self): "Test that setting an existing student_state field changes the value" self.kvs.set(student_state_key('a_field'), 'new_value') self.assertEquals(1, StudentModule.objects.all().count()) self.assertEquals({'a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state)) def test_set_missing_field(self): "Test that setting a new student_state field changes the value" self.kvs.set(student_state_key('not_a_field'), 'new_value') self.assertEquals(1, StudentModule.objects.all().count()) self.assertEquals({'a_field': 'a_value', 'not_a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state)) def test_delete_existing_field(self): "Test that deleting an existing field removes it from the StudentModule" self.kvs.delete(student_state_key('a_field')) self.assertEquals(1, StudentModule.objects.all().count()) self.assertRaises(KeyError, self.kvs.get, student_state_key('not_a_field')) def test_delete_missing_field(self): "Test that deleting a missing field from an existing StudentModule raises a KeyError" self.assertRaises(KeyError, self.kvs.delete, student_state_key('not_a_field')) self.assertEquals(1, StudentModule.objects.all().count()) self.assertEquals({'a_field': 'a_value'}, json.loads(StudentModule.objects.all()[0].state)) def test_has_existing_field(self): "Test that `has` returns True for existing fields in StudentModules" self.assertTrue(self.kvs.has(student_state_key('a_field'))) def test_has_missing_field(self): "Test that `has` returns False for missing fields in StudentModule" self.assertFalse(self.kvs.has(student_state_key('not_a_field'))) class TestMissingStudentModule(TestCase): def setUp(self): self.user = UserFactory.create() self.desc_md = {} self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) def test_get_field_from_missing_student_module(self): "Test that getting a field from a missing StudentModule raises a KeyError" self.assertRaises(KeyError, self.kvs.get, student_state_key('a_field')) def test_set_field_in_missing_student_module(self): "Test that setting a field in a missing StudentModule creates the student module" self.assertEquals(0, len(self.mdc.cache)) self.assertEquals(0, StudentModule.objects.all().count()) self.kvs.set(student_state_key('a_field'), 'a_value') self.assertEquals(1, len(self.mdc.cache)) self.assertEquals(1, StudentModule.objects.all().count()) student_module = StudentModule.objects.all()[0] self.assertEquals({'a_field': 'a_value'}, json.loads(student_module.state)) self.assertEquals(self.user, student_module.student) self.assertEquals(location('def_id').url(), student_module.module_state_key) self.assertEquals(course_id, student_module.course_id) def test_delete_field_from_missing_student_module(self): "Test that deleting a field from a missing StudentModule raises a KeyError" self.assertRaises(KeyError, self.kvs.delete, student_state_key('a_field')) def test_has_field_for_missing_student_module(self): "Test that `has` returns False for missing StudentModules" self.assertFalse(self.kvs.has(student_state_key('a_field'))) class StorageTestBase(object): factory = None scope = None key_factory = None storage_class = None def setUp(self): field_storage = self.factory.create() if hasattr(field_storage, 'student'): self.user = field_storage.student else: self.user = UserFactory.create() self.desc_md = {} self.mdc = ModelDataCache([mock_descriptor([mock_field(self.scope, 'existing_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) def test_get_existing_field(self): "Test that getting an existing field in an existing Storage Field works" self.assertEquals('old_value', self.kvs.get(self.key_factory('existing_field'))) def test_get_missing_field(self): "Test that getting a missing field from an existing Storage Field raises a KeyError" self.assertRaises(KeyError, self.kvs.get, self.key_factory('missing_field')) def test_set_existing_field(self): "Test that setting an existing field changes the value" self.kvs.set(self.key_factory('existing_field'), 'new_value') self.assertEquals(1, self.storage_class.objects.all().count()) self.assertEquals('new_value', json.loads(self.storage_class.objects.all()[0].value)) def test_set_missing_field(self): "Test that setting a new field changes the value" self.kvs.set(self.key_factory('missing_field'), 'new_value') self.assertEquals(2, self.storage_class.objects.all().count()) self.assertEquals('old_value', json.loads(self.storage_class.objects.get(field_name='existing_field').value)) self.assertEquals('new_value', json.loads(self.storage_class.objects.get(field_name='missing_field').value)) def test_delete_existing_field(self): "Test that deleting an existing field removes it" self.kvs.delete(self.key_factory('existing_field')) self.assertEquals(0, self.storage_class.objects.all().count()) def test_delete_missing_field(self): "Test that deleting a missing field from an existing Storage Field raises a KeyError" self.assertRaises(KeyError, self.kvs.delete, self.key_factory('missing_field')) self.assertEquals(1, self.storage_class.objects.all().count()) def test_has_existing_field(self): "Test that `has` returns True for an existing Storage Field" self.assertTrue(self.kvs.has(self.key_factory('existing_field'))) def test_has_missing_field(self): "Test that `has` return False for an existing Storage Field" self.assertFalse(self.kvs.has(self.key_factory('missing_field'))) class TestSettingsStorage(StorageTestBase, TestCase): factory = SettingsFactory scope = Scope.settings key_factory = settings_key storage_class = XModuleSettingsField class TestContentStorage(StorageTestBase, TestCase): factory = ContentFactory scope = Scope.content key_factory = content_key storage_class = XModuleContentField class TestStudentPrefsStorage(StorageTestBase, TestCase): factory = StudentPrefsFactory scope = Scope.student_preferences key_factory = student_prefs_key storage_class = XModuleStudentPrefsField class TestStudentInfoStorage(StorageTestBase, TestCase): factory = StudentInfoFactory scope = Scope.student_info key_factory = student_info_key storage_class = XModuleStudentInfoField