450 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import unittest
 | |
| from six import iterkeys
 | |
| 
 | |
| from mongoengine import Document
 | |
| from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict
 | |
| 
 | |
| 
 | |
| class DocumentStub(object):
 | |
|     def __init__(self):
 | |
|         self._changed_fields = []
 | |
| 
 | |
|     def _mark_as_changed(self, key):
 | |
|         self._changed_fields.append(key)
 | |
| 
 | |
| 
 | |
| class TestBaseDict(unittest.TestCase):
 | |
| 
 | |
|     @staticmethod
 | |
|     def _get_basedict(dict_items):
 | |
|         """Get a BaseList bound to a fake document instance"""
 | |
|         fake_doc = DocumentStub()
 | |
|         base_list = BaseDict(dict_items, instance=None, name='my_name')
 | |
|         base_list._instance = fake_doc  # hack to inject the mock, it does not work in the constructor
 | |
|         return base_list
 | |
| 
 | |
|     def test___init___(self):
 | |
|         class MyDoc(Document):
 | |
|             pass
 | |
| 
 | |
|         dict_items = {'k': 'v'}
 | |
|         doc = MyDoc()
 | |
|         base_dict = BaseDict(dict_items, instance=doc, name='my_name')
 | |
|         self.assertIsInstance(base_dict._instance, Document)
 | |
|         self.assertEqual(base_dict._name, 'my_name')
 | |
|         self.assertEqual(base_dict, dict_items)
 | |
| 
 | |
|     def test_setdefault_calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         base_dict.setdefault('k', 'v')
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
 | |
| 
 | |
|     def test_popitems_calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         self.assertEqual(base_dict.popitem(), ('k', 'v'))
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
 | |
|         self.assertFalse(base_dict)
 | |
| 
 | |
|     def test_pop_calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         self.assertEqual(base_dict.pop('k'), 'v')
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
 | |
|         self.assertFalse(base_dict)
 | |
| 
 | |
|     def test_pop_calls_does_not_mark_as_changed_when_it_fails(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         with self.assertRaises(KeyError):
 | |
|             base_dict.pop('X')
 | |
|         self.assertFalse(base_dict._instance._changed_fields)
 | |
| 
 | |
|     def test_clear_calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         base_dict.clear()
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_dict, {})
 | |
| 
 | |
|     def test___delitem___calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         del base_dict['k']
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
 | |
|         self.assertEqual(base_dict, {})
 | |
| 
 | |
|     def test___getitem____KeyError(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         with self.assertRaises(KeyError):
 | |
|             base_dict['new']
 | |
| 
 | |
|     def test___getitem____simple_value(self):
 | |
|         base_dict = self._get_basedict({'k': 'v'})
 | |
|         base_dict['k'] = 'v'
 | |
| 
 | |
|     def test___getitem____sublist_gets_converted_to_BaseList(self):
 | |
|         base_dict = self._get_basedict({'k': [0, 1, 2]})
 | |
|         sub_list = base_dict['k']
 | |
|         self.assertEqual(sub_list, [0, 1, 2])
 | |
|         self.assertIsInstance(sub_list, BaseList)
 | |
|         self.assertIs(sub_list._instance, base_dict._instance)
 | |
|         self.assertEqual(sub_list._name, 'my_name.k')
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [])
 | |
| 
 | |
|         # Challenge mark_as_changed from sublist
 | |
|         sub_list[1] = None
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.1'])
 | |
| 
 | |
|     def test___getitem____subdict_gets_converted_to_BaseDict(self):
 | |
|         base_dict = self._get_basedict({'k': {'subk': 'subv'}})
 | |
|         sub_dict = base_dict['k']
 | |
|         self.assertEqual(sub_dict, {'subk': 'subv'})
 | |
|         self.assertIsInstance(sub_dict, BaseDict)
 | |
|         self.assertIs(sub_dict._instance, base_dict._instance)
 | |
|         self.assertEqual(sub_dict._name, 'my_name.k')
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [])
 | |
| 
 | |
|         # Challenge mark_as_changed from subdict
 | |
|         sub_dict['subk'] = None
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.subk'])
 | |
| 
 | |
|     def test_get_sublist_gets_converted_to_BaseList_just_like__getitem__(self):
 | |
|         base_dict = self._get_basedict({'k': [0, 1, 2]})
 | |
|         sub_list = base_dict.get('k')
 | |
|         self.assertEqual(sub_list, [0, 1, 2])
 | |
|         self.assertIsInstance(sub_list, BaseList)
 | |
| 
 | |
|     def test_get_returns_the_same_as___getitem__(self):
 | |
|         base_dict = self._get_basedict({'k': [0, 1, 2]})
 | |
|         get_ = base_dict.get('k')
 | |
|         getitem_ = base_dict['k']
 | |
|         self.assertEqual(get_, getitem_)
 | |
| 
 | |
|     def test_get_default(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         sentinel = object()
 | |
|         self.assertEqual(base_dict.get('new'), None)
 | |
|         self.assertIs(base_dict.get('new', sentinel), sentinel)
 | |
| 
 | |
|     def test___setitem___calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         base_dict['k'] = 'v'
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
 | |
|         self.assertEqual(base_dict, {'k': 'v'})
 | |
| 
 | |
|     def test_update_calls_mark_as_changed(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         base_dict.update({'k': 'v'})
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test___setattr____not_tracked_by_changes(self):
 | |
|         base_dict = self._get_basedict({})
 | |
|         base_dict.a_new_attr = 'test'
 | |
|         self.assertEqual(base_dict._instance._changed_fields, [])
 | |
| 
 | |
|     def test___delattr____tracked_by_changes(self):
 | |
|         # This is probably a bug as __setattr__ is not tracked
 | |
|         # This is even bad because it could be that there is an attribute
 | |
|         # with the same name as a key
 | |
|         base_dict = self._get_basedict({})
 | |
|         base_dict.a_new_attr = 'test'
 | |
|         del base_dict.a_new_attr
 | |
|         self.assertEqual(base_dict._instance._changed_fields, ['my_name.a_new_attr'])
 | |
| 
 | |
| 
 | |
| class TestBaseList(unittest.TestCase):
 | |
| 
 | |
|     @staticmethod
 | |
|     def _get_baselist(list_items):
 | |
|         """Get a BaseList bound to a fake document instance"""
 | |
|         fake_doc = DocumentStub()
 | |
|         base_list = BaseList(list_items, instance=None, name='my_name')
 | |
|         base_list._instance = fake_doc  # hack to inject the mock, it does not work in the constructor
 | |
|         return base_list
 | |
| 
 | |
|     def test___init___(self):
 | |
|         class MyDoc(Document):
 | |
|             pass
 | |
| 
 | |
|         list_items = [True]
 | |
|         doc = MyDoc()
 | |
|         base_list = BaseList(list_items, instance=doc, name='my_name')
 | |
|         self.assertIsInstance(base_list._instance, Document)
 | |
|         self.assertEqual(base_list._name, 'my_name')
 | |
|         self.assertEqual(base_list, list_items)
 | |
| 
 | |
|     def test___iter__(self):
 | |
|         values = [True, False, True, False]
 | |
|         base_list = BaseList(values, instance=None, name='my_name')
 | |
|         self.assertEqual(values, list(base_list))
 | |
| 
 | |
|     def test___iter___allow_modification_while_iterating_withou_error(self):
 | |
|         # regular list allows for this, thus this subclass must comply to that
 | |
|         base_list = BaseList([True, False, True, False], instance=None, name='my_name')
 | |
|         for idx, val in enumerate(base_list):
 | |
|             if val:
 | |
|                 base_list.pop(idx)
 | |
| 
 | |
|     def test_append_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([])
 | |
|         self.assertFalse(base_list._instance._changed_fields)
 | |
|         base_list.append(True)
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_subclass_append(self):
 | |
|         # Due to the way mark_as_changed_wrapper is implemented
 | |
|         # it is good to test subclasses
 | |
|         class SubBaseList(BaseList):
 | |
|             pass
 | |
| 
 | |
|         base_list = SubBaseList([], instance=None, name='my_name')
 | |
|         base_list.append(True)
 | |
| 
 | |
|     def test___getitem__using_simple_index(self):
 | |
|         base_list = self._get_baselist([0, 1, 2])
 | |
|         self.assertEqual(base_list[0], 0)
 | |
|         self.assertEqual(base_list[1], 1)
 | |
|         self.assertEqual(base_list[-1], 2)
 | |
| 
 | |
|     def test___getitem__using_slice(self):
 | |
|         base_list = self._get_baselist([0, 1, 2])
 | |
|         self.assertEqual(base_list[1:3], [1, 2])
 | |
|         self.assertEqual(base_list[0:3:2], [0, 2])
 | |
| 
 | |
|     def test___getitem___using_slice_returns_list(self):
 | |
|         # Bug: using slice does not properly handles the instance
 | |
|         # and mark_as_changed behaviour.
 | |
|         base_list = self._get_baselist([0, 1, 2])
 | |
|         sliced = base_list[1:3]
 | |
|         self.assertEqual(sliced, [1, 2])
 | |
|         self.assertIsInstance(sliced, list)
 | |
|         self.assertEqual(base_list._instance._changed_fields, [])
 | |
| 
 | |
|     def test___getitem__sublist_returns_BaseList_bound_to_instance(self):
 | |
|         base_list = self._get_baselist(
 | |
|             [
 | |
|                 [1, 2],
 | |
|                 [3, 4]
 | |
|             ]
 | |
|         )
 | |
|         sub_list = base_list[0]
 | |
|         self.assertEqual(sub_list, [1, 2])
 | |
|         self.assertIsInstance(sub_list, BaseList)
 | |
|         self.assertIs(sub_list._instance, base_list._instance)
 | |
|         self.assertEqual(sub_list._name, 'my_name.0')
 | |
|         self.assertEqual(base_list._instance._changed_fields, [])
 | |
| 
 | |
|         # Challenge mark_as_changed from sublist
 | |
|         sub_list[1] = None
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name.0.1'])
 | |
| 
 | |
|     def test___getitem__subdict_returns_BaseList_bound_to_instance(self):
 | |
|         base_list = self._get_baselist(
 | |
|             [
 | |
|                 {'subk': 'subv'}
 | |
|             ]
 | |
|         )
 | |
|         sub_dict = base_list[0]
 | |
|         self.assertEqual(sub_dict, {'subk': 'subv'})
 | |
|         self.assertIsInstance(sub_dict, BaseDict)
 | |
|         self.assertIs(sub_dict._instance, base_list._instance)
 | |
|         self.assertEqual(sub_dict._name, 'my_name.0')
 | |
|         self.assertEqual(base_list._instance._changed_fields, [])
 | |
| 
 | |
|         # Challenge mark_as_changed from subdict
 | |
|         sub_dict['subk'] = None
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name.0.subk'])
 | |
| 
 | |
|     def test_extend_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([])
 | |
|         base_list.extend([True])
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_insert_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([])
 | |
|         base_list.insert(0, True)
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_remove_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         base_list.remove(True)
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_remove_not_mark_as_changed_when_it_fails(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         with self.assertRaises(ValueError):
 | |
|             base_list.remove(False)
 | |
|         self.assertFalse(base_list._instance._changed_fields)
 | |
| 
 | |
|     def test_pop_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         base_list.pop()
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_reverse_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True, False])
 | |
|         base_list.reverse()
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test___delitem___calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         del base_list[0]
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test___setitem___calls_with_full_slice_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([])
 | |
|         base_list[:] = [0, 1]      # Will use __setslice__ under py2 and __setitem__ under py3
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [0, 1])
 | |
| 
 | |
|     def test___setitem___calls_with_partial_slice_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([0, 1, 2])
 | |
|         base_list[0:2] = [1, 0]     # Will use __setslice__ under py2 and __setitem__ under py3
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [1, 0, 2])
 | |
| 
 | |
|     def test___setitem___calls_with_step_slice_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([0, 1, 2])
 | |
|         base_list[0:3:2] = [-1, -2]   # uses __setitem__ in both py2 & 3
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [-1, 1, -2])
 | |
| 
 | |
|     def test___setitem___with_slice(self):
 | |
|         base_list = self._get_baselist([0, 1, 2, 3, 4, 5])
 | |
|         base_list[0:6:2] = [None, None, None]
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [None, 1, None, 3, None, 5])
 | |
| 
 | |
|     def test___setitem___item_0_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         base_list[0] = False
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [False])
 | |
| 
 | |
|     def test___setitem___item_1_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True, True])
 | |
|         base_list[1] = False
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name.1'])
 | |
|         self.assertEqual(base_list, [True, False])
 | |
| 
 | |
|     def test___delslice___calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([0, 1])
 | |
|         del base_list[0:1]
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
|         self.assertEqual(base_list, [1])
 | |
| 
 | |
|     def test___iadd___calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         base_list += [False]
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test___imul___calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         self.assertEqual(base_list._instance._changed_fields, [])
 | |
|         base_list *= 2
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_sort_calls_not_marked_as_changed_when_it_fails(self):
 | |
|         base_list = self._get_baselist([True])
 | |
|         with self.assertRaises(TypeError):
 | |
|             base_list.sort(key=1)
 | |
| 
 | |
|         self.assertEqual(base_list._instance._changed_fields, [])
 | |
| 
 | |
|     def test_sort_calls_mark_as_changed(self):
 | |
|         base_list = self._get_baselist([True, False])
 | |
|         base_list.sort()
 | |
|         self.assertEqual(base_list._instance._changed_fields, ['my_name'])
 | |
| 
 | |
|     def test_sort_calls_with_key(self):
 | |
|         base_list = self._get_baselist([1, 2, 11])
 | |
|         base_list.sort(key=lambda i: str(i))
 | |
|         self.assertEqual(base_list, [1, 11, 2])
 | |
| 
 | |
| 
 | |
| class TestStrictDict(unittest.TestCase):
 | |
|     def strict_dict_class(self, *args, **kwargs):
 | |
|         return StrictDict.create(*args, **kwargs)
 | |
| 
 | |
|     def setUp(self):
 | |
|         self.dtype = self.strict_dict_class(("a", "b", "c"))
 | |
| 
 | |
|     def test_init(self):
 | |
|         d = self.dtype(a=1, b=1, c=1)
 | |
|         self.assertEqual((d.a, d.b, d.c), (1, 1, 1))
 | |
| 
 | |
|     def test_iterkeys(self):
 | |
|         d = self.dtype(a=1)
 | |
|         self.assertEqual(list(iterkeys(d)), ['a'])
 | |
| 
 | |
|     def test_len(self):
 | |
|         d = self.dtype(a=1)
 | |
|         self.assertEqual(len(d), 1)
 | |
| 
 | |
|     def test_pop(self):
 | |
|         d = self.dtype(a=1)
 | |
|         self.assertIn('a', d)
 | |
|         d.pop('a')
 | |
|         self.assertNotIn('a', d)
 | |
| 
 | |
|     def test_repr(self):
 | |
|         d = self.dtype(a=1, b=2, c=3)
 | |
|         self.assertEqual(repr(d), '{"a": 1, "b": 2, "c": 3}')
 | |
| 
 | |
|         # make sure quotes are escaped properly
 | |
|         d = self.dtype(a='"', b="'", c="")
 | |
|         self.assertEqual(repr(d), '{"a": \'"\', "b": "\'", "c": \'\'}')
 | |
| 
 | |
|     def test_init_fails_on_nonexisting_attrs(self):
 | |
|         with self.assertRaises(AttributeError):
 | |
|             self.dtype(a=1, b=2, d=3)
 | |
| 
 | |
|     def test_eq(self):
 | |
|         d = self.dtype(a=1, b=1, c=1)
 | |
|         dd = self.dtype(a=1, b=1, c=1)
 | |
|         e = self.dtype(a=1, b=1, c=3)
 | |
|         f = self.dtype(a=1, b=1)
 | |
|         g = self.strict_dict_class(("a", "b", "c", "d"))(a=1, b=1, c=1, d=1)
 | |
|         h = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=1)
 | |
|         i = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=2)
 | |
| 
 | |
|         self.assertEqual(d, dd)
 | |
|         self.assertNotEqual(d, e)
 | |
|         self.assertNotEqual(d, f)
 | |
|         self.assertNotEqual(d, g)
 | |
|         self.assertNotEqual(f, d)
 | |
|         self.assertEqual(d, h)
 | |
|         self.assertNotEqual(d, i)
 | |
| 
 | |
|     def test_setattr_getattr(self):
 | |
|         d = self.dtype()
 | |
|         d.a = 1
 | |
|         self.assertEqual(d.a, 1)
 | |
|         self.assertRaises(AttributeError, getattr, d, 'b')
 | |
| 
 | |
|     def test_setattr_raises_on_nonexisting_attr(self):
 | |
|         d = self.dtype()
 | |
|         with self.assertRaises(AttributeError):
 | |
|             d.x = 1
 | |
| 
 | |
|     def test_setattr_getattr_special(self):
 | |
|         d = self.strict_dict_class(["items"])
 | |
|         d.items = 1
 | |
|         self.assertEqual(d.items, 1)
 | |
| 
 | |
|     def test_get(self):
 | |
|         d = self.dtype(a=1)
 | |
|         self.assertEqual(d.get('a'), 1)
 | |
|         self.assertEqual(d.get('b', 'bla'), 'bla')
 | |
| 
 | |
|     def test_items(self):
 | |
|         d = self.dtype(a=1)
 | |
|         self.assertEqual(d.items(), [('a', 1)])
 | |
|         d = self.dtype(a=1, b=2)
 | |
|         self.assertEqual(d.items(), [('a', 1), ('b', 2)])
 | |
| 
 | |
|     def test_mappings_protocol(self):
 | |
|         d = self.dtype(a=1, b=2)
 | |
|         self.assertEqual(dict(d), {'a': 1, 'b': 2})
 | |
|         self.assertEqual(dict(**d), {'a': 1, 'b': 2})
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     unittest.main()
 |