Browse Source

Add test for LeObject with a mock on the datasource + bugfix on _LeObject and LeType

Yann Weber 9 years ago
parent
commit
965560bf6b
4 changed files with 149 additions and 26 deletions
  1. 1
    1
      leobject/datasources/dummy.py
  2. 29
    21
      leobject/leobject.py
  3. 3
    3
      leobject/letype.py
  4. 116
    1
      leobject/test/test_leobject.py

+ 1
- 1
leobject/datasources/dummy.py View File

@@ -38,7 +38,7 @@ class DummyDatasource(object):
38 38
     # @param relational_filters list : relationnal filters list (see @ref leobject_filters )
39 39
     # @return okay bool: True on success, it will raise on failure
40 40
     def delete(self, letype, leclass, filters, relational_filters):
41
-        print("DummyDatasource.delete: ", lodel_id)
41
+        print("DummyDatasource.delete: ", filters)
42 42
         return True
43 43
 
44 44
     ## @brief search for a collection of objects

+ 29
- 21
leobject/leobject.py View File

@@ -62,7 +62,6 @@ class _LeObject(object):
62 62
 
63 63
         for data in datas:
64 64
             letype.check_datas_or_raise(data, complete = True)
65
-
66 65
         return cls._datasource.insert(letype, leclass, datas)
67 66
     
68 67
     ## @brief Delete LeObjects given filters
@@ -72,34 +71,35 @@ class _LeObject(object):
72 71
     # @param filters list : list of filters (see @ref leobject_filters)
73 72
     # @return bool
74 73
     @classmethod
75
-    def delete(cls, letype, leclass, filters):
76
-        filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, cls, cls._leclass)
77
-        letype, leclass = cls._prepare_targets(letype, leclass)
78
-        return cls._datasource(letype, leclass, filters, relationnal_filters)
74
+    def delete(cls, letype, filters):
75
+        letype, leclass = cls._prepare_targets(letype)
76
+        filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
77
+        return cls._datasource.delete(letype, leclass, filters, relationnal_filters)
79 78
     
80 79
     ## @brief Update LeObjects given filters and datas
81 80
     # @param cls
82 81
     # @param letype LeType|str : LeType child class or name
83
-    # @param leclass LeClass|str : LeClass child class or name
84 82
     # @param filters list : list of filters (see @ref leobject_filters)
85 83
     @classmethod
86
-    def update(cls, letype, leclass, filters, datas):
87
-        filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, cls, cls._leclass)
88
-        letype, leclass = cls._prepare_targets(letype, leclass)
84
+    def update(cls, letype, filters, datas):
85
+        letype, leclass = cls._prepare_targets(letype)
86
+        filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
89 87
         if letype is None:
90 88
             raise ValueError("Argument letype cannot be None")
91 89
         letype.check_datas_or_raise(datas, False)
92
-        return cls._datasource(letype, leclass, filters, relationnal_filters, datas)
90
+        return cls._datasource.update(letype, leclass, filters, relationnal_filters, datas)
93 91
 
94 92
     ## @brief make a search to retrieve a collection of LeObject
95 93
     # @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
96 94
     # @param field_list list|None : list of string representing fields see @ref leobject_filters
97 95
     # @param typename str : The name of the LeType we want
98 96
     # @param classname str : The name of the LeClass we want
97
+    # @param cls
99 98
     # @return responses ({string:*}): a list of dict with field:value
100
-    def get(self, query_filters, field_list = None, typename = None, classname = None):
99
+    @classmethod
100
+    def get(cls, query_filters, field_list = None, typename = None, classname = None):
101 101
 
102
-        letype,leclass = self._prepare_targets(typename, classname)
102
+        letype,leclass = cls._prepare_targets(typename, classname)
103 103
 
104 104
         #Fetching LeType
105 105
         if typename is None:
@@ -107,21 +107,21 @@ class _LeObject(object):
107 107
                 field_list.append('type_id')
108 108
 
109 109
         #Checking field_list
110
-        if field_list is None:
110
+        if field_list is None or len(field_list) == 0:
111
+            #default field_list
111 112
             if not (letype is None):
112
-                flist = letype._fields
113
+                field_list = letype._fields
113 114
             elif not (leclass is None):
114
-                flist = leclass._fieldtypes.keys()
115
+                field_list = leclass._fieldtypes.keys()
115 116
             else:
116
-                flist = EditorialModel.classtype.common_fields.keys()
117
-        else:
118
-            LeFactory._check_fields(letype, leclass, field_list)
117
+                field_list = EditorialModel.classtype.common_fields.keys()
118
+        field_list = cls._prepare_field_list(field_list, letype, leclass)
119 119
         
120 120
         #preparing filters
121
-        filters, relationnal_filters = self._prepare_filters(query_filters, letype, leclass)
121
+        filters, relationnal_filters = cls._prepare_filters(query_filters, letype, leclass)
122 122
 
123 123
         #Fetching datas from datasource
124
-        datas = self._datasource.get(emclass, emtype, field_list, filters, relational_filters)
124
+        datas = cls._datasource.get(leclass, letype, field_list, filters, relationnal_filters)
125 125
         
126 126
         #Instanciating corresponding LeType child classes with datas
127 127
         result = list()
@@ -131,6 +131,14 @@ class _LeObject(object):
131 131
 
132 132
         return result
133 133
 
134
+    @classmethod
135
+    def _prepare_field_list(cls, field_list, letype, leclass):
136
+        cls._check_fields(letype, leclass, [f for f in field_list if not cls._field_is_relational(f)])
137
+        for i, field in enumerate(field_list):
138
+            if cls._field_is_relational(field):
139
+                field_list[i] = cls._prepare_relational_field(field)
140
+        return field_list
141
+
134 142
     ## @brief Preparing letype and leclass arguments
135 143
     # 
136 144
     # This function will do multiple things : 
@@ -194,7 +202,7 @@ class _LeObject(object):
194 202
             #Checks that fields are in this type
195 203
             for field in fields:
196 204
                 if field not in field_l:
197
-                    raise LeObjectQueryError("No field named '%s' in '%s'"%(field, typename))
205
+                    raise LeObjectQueryError("No field named '%s' in '%s'"%(field, letype.__name__))
198 206
         pass
199 207
 
200 208
     ## @brief Prepare filters for datasource

+ 3
- 3
leobject/letype.py View File

@@ -118,15 +118,15 @@ class LeType(leobject.leobject._LeObject):
118 118
     # @throw AttributeError if datas provides values for fields that doesn't exists
119 119
     @classmethod
120 120
     def check_datas_or_raise(cls, datas, complete = False):
121
-        autom_fields = [f.name for f in cls._fieldtypes if f.internal]
121
+        autom_fields = [f for f, ft in cls._fieldtypes.items() if hasattr(ft,'internal') and ft.internal]
122 122
         for dname, dval in datas.items():
123 123
             if dname in autom_fields:
124 124
                 raise AttributeError("The field '%s' is internal"%(dname))
125 125
             if dname not in cls._fields:
126 126
                 raise AttributeError("No such field '%s' for %s"%(dname, self.__class__.__name__))
127
-            cls._fieldtypess[dname].check_or_raise(dval)
127
+            cls._fieldtypes[dname].check_or_raise(dval)
128 128
         
129
-        fields = [f.name for f in cls._fieldtypes if not f.internal]
129
+        fields = [f for f, ft in cls._fieldtypes.items() if not hasattr(ft,'internal') or not ft.internal]
130 130
         if complete and len(datas) < len(fields):
131 131
             raise LeObjectError("The argument complete was True but some fields are missing : %s"%(set(fields) - set(datas.keys())))
132 132
     

+ 116
- 1
leobject/test/test_leobject.py View File

@@ -4,6 +4,7 @@
4 4
 
5 5
 import unittest
6 6
 from unittest import TestCase
7
+from unittest.mock import patch
7 8
 
8 9
 import EditorialModel
9 10
 import leobject
@@ -53,7 +54,6 @@ class _LeObjectTestCase(TestCase):
53 54
                 _LeObject._split_filter(query)
54 55
 
55 56
 ## Testing methods that need the generated code
56
-# @todo mock the datasource to test the get, update, delete and insert methods
57 57
 class LeObjectTestCase(TestCase):
58 58
 
59 59
     @classmethod
@@ -201,4 +201,119 @@ class LeObjectTestCase(TestCase):
201 201
         filters = ['hello world !']
202 202
         with self.assertRaises(ValueError):
203 203
             LeObject._prepare_filters(filters, None, None)
204
+
205
+class LeObjectMockDatasourceTestCase(TestCase):
206
+    """ Testing _LeObject using a mock on the datasource """
207
+
208
+    @classmethod
209
+    def setUpClass(cls):
210
+        """ Write the generated code in a temporary directory and import it """
211
+        cls.tmpdir = leobject.test.utils.tmp_load_factory_code()
212
+    @classmethod
213
+    def tearDownClass(cls):
214
+        """ Remove the temporary directory created at class setup """
215
+        leobject.test.utils.cleanup(cls.tmpdir)
204 216
     
217
+    @patch('leobject.datasources.dummy.DummyDatasource.insert')
218
+    def test_insert(self, dsmock):
219
+        from dyncode import Publication, Numero, LeObject
220
+        ndatas = [
221
+            [{'titre' : 'FooBar'}],
222
+            [{'titre':'hello'},{'titre':'world'}],
223
+        ]
224
+        for ndats in ndatas:
225
+            LeObject.insert(Numero,ndats)
226
+            dsmock.assert_called_once_with(Numero, Publication, ndats)
227
+            dsmock.reset_mock()
228
+
229
+            LeObject.insert('Numero',ndats)
230
+            dsmock.assert_called_once_with(Numero, Publication, ndats)
231
+            dsmock.reset_mock()
232
+
233
+    @patch('leobject.datasources.dummy.DummyDatasource.update')
234
+    def test_update(self, dsmock):
235
+        from dyncode import Publication, Numero, LeObject
236
+
237
+        args = [
238
+            (   ['lodel_id = 1'],
239
+                {'titre':'foobar'},
240
+                [('lodel_id','=','1')],
241
+                []
242
+            ),
243
+            (   ['superior.parent in [1,2,3,4,5,6]', 'titre != "FooBar"'],
244
+                {'titre':'FooBar'},
245
+                [( 'titre','!=','"FooBar"')],
246
+                [( (leobject.leobject.REL_SUP, 'parent') ,' in ', '[1,2,3,4,5,6]')]
247
+            ),
248
+        ]
249
+
250
+        for filters, datas, ds_filters, ds_relfilters in args:
251
+            LeObject.update(Numero, filters, datas)
252
+            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters, datas)
253
+            dsmock.reset_mock()
254
+
255
+            LeObject.update('Numero', filters, datas)
256
+            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters, datas)
257
+            dsmock.reset_mock()
258
+
259
+    @patch('leobject.datasources.dummy.DummyDatasource.delete')
260
+    def test_delete(self, dsmock):
261
+        from dyncode import Publication, Numero, LeObject
262
+
263
+        args = [
264
+            (
265
+                ['lodel_id=1'],
266
+                [('lodel_id', '=', '1')],
267
+                []
268
+            ),
269
+            (
270
+                ['subordinate.parent not in [1,2,3]', 'titre = "titre nul"'],
271
+                [('titre','=', '"titre nul"')],
272
+                [( (leobject.leobject.REL_SUB, 'parent'), ' not in ', '[1,2,3]')]
273
+            ),
274
+        ]
275
+
276
+        for filters, ds_filters, ds_relfilters in args:
277
+            LeObject.delete(Numero, filters)
278
+            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
279
+            dsmock.reset_mock()
280
+
281
+            LeObject.delete('Numero', filters)
282
+            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
283
+            dsmock.reset_mock()
284
+        
285
+    @patch('leobject.datasources.dummy.DummyDatasource.get')
286
+    def test_get(self, dsmock):
287
+        from dyncode import Publication, Numero, LeObject
288
+        
289
+        args = [
290
+            (
291
+                ['lodel_id', 'superior.parent'],
292
+                ['titre != "foobar"'],
293
+
294
+                ['lodel_id', (leobject.leobject.REL_SUP, 'parent')],
295
+                [('titre','!=', '"foobar"')],
296
+                []
297
+            ),
298
+            (
299
+                ['lodel_id', 'titre', 'superior.parent', 'subordinate.translation'],
300
+                ['superior.parent in  [1,2,3,4,5]'],
301
+
302
+                ['lodel_id', 'titre', (leobject.leobject.REL_SUP,'parent'), (leobject.leobject.REL_SUB, 'translation')],
303
+                [],
304
+                [( (leobject.leobject.REL_SUP, 'parent'), ' in ', '[1,2,3,4,5]')]
305
+            ),
306
+            (
307
+                [],
308
+                [],
309
+
310
+                Numero._fields,
311
+                [],
312
+                []
313
+            ),
314
+        ]
315
+
316
+        for field_list, filters, fl_ds, filters_ds, rfilters_ds in args:
317
+            LeObject.get(filters, field_list, Numero)
318
+            dsmock.assert_called_with(Publication, Numero, fl_ds, filters_ds, rfilters_ds)
319
+            dsmock.reset_mock()

Loading…
Cancel
Save