Browse Source

Bugfix + added some tests to LeCrud

Yann Weber 9 years ago
parent
commit
aae570e5bc
5 changed files with 169 additions and 18 deletions
  1. 4
    0
      EditorialModel/fieldtypes/generic.py
  2. 22
    5
      leapi/lecrud.py
  3. 1
    1
      leapi/letype.py
  4. 142
    0
      leapi/test/test_lecrud.py
  5. 0
    12
      leapi/test/test_letype.py

+ 4
- 0
EditorialModel/fieldtypes/generic.py View File

@@ -63,6 +63,10 @@ class GenericFieldType(object):
63 63
     @property
64 64
     def name(self):
65 65
         return self.__module__.split('.')[-1]
66
+    
67
+    ## @return True if a field is internal, else returns False
68
+    def is_internal(self):
69
+        return hasattr(self, 'internal') and self.internal
66 70
 
67 71
     ## @brief Check if a Value is correct else return a check fail reason
68 72
     # @param value * : The value to check

+ 22
- 5
leapi/lecrud.py View File

@@ -9,13 +9,29 @@ import importlib
9 9
 import re
10 10
 
11 11
 import leapi.leobject
12
-import EditorialModel.fieldtypes.generic
12
+
13
+class LeApiErrors(Exception):
14
+    ## @brief Instanciate a new exceptions handling multiple exceptions
15
+    # @param expt_l list : A list of data check Exception
16
+    def __init__(self, msg = "Unknow error", exceptions = None):
17
+        self._msg = msg
18
+        self._exceptions = list() if exceptions is None else exceptions
19
+
20
+    def __str__(self):
21
+        msg = self._msg
22
+        for expt in self._exceptions:
23
+            msg += " {expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
24
+        return msg
25
+
26
+    def __repr__(self):
27
+        return str(self)
28
+
13 29
 
14 30
 ## @brief When an error concern a query
15
-class LeApiQueryError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError): pass
31
+class LeApiQueryError(LeApiErrors): pass
16 32
 
17 33
 ## @brief When an error concerns a datas
18
-class LeApiDataCheckError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError): pass
34
+class LeApiDataCheckError(LeApiErrors): pass
19 35
     
20 36
 
21 37
 ## @brief Main class to handler lodel editorial components (relations and objects)
@@ -109,7 +125,7 @@ class _LeCrud(object):
109 125
         correct = [] #Valid fields name
110 126
         mandatory = [] #mandatory fields name
111 127
         for fname, ftt in cls.fieldtypes().items():
112
-            if allow_internal and not ftt.is_internal():
128
+            if allow_internal or not ftt.is_internal():
113 129
                 correct.append(fname)
114 130
                 if complete and not hasattr(ftt, 'default'):
115 131
                     mandatory.append(fname)
@@ -118,6 +134,7 @@ class _LeCrud(object):
118 134
         provided = set(datas.keys())
119 135
 
120 136
         #searching unknow fields
137
+        print("provided", provided, "correct", correct)
121 138
         unknown = provided - correct
122 139
         for u_f in unknown:
123 140
             #here we can check if the field is unknown or rejected because it is internal
@@ -134,7 +151,7 @@ class _LeCrud(object):
134 151
                 err_l.append(err)
135 152
 
136 153
         if len(err_l) > 0:
137
-            raise LeApiDataCheckError("The argument complete was %s but some fields are missing : %s" % (complete, err_l))
154
+            raise LeApiDataCheckError("Error while checking datas", err_l)
138 155
         return checked_datas
139 156
 
140 157
     ## @brief Retrieve a collection of lodel editorial components

+ 1
- 1
leapi/letype.py View File

@@ -55,7 +55,7 @@ class LeType(object):
55 55
 
56 56
     @classmethod
57 57
     def fieldtypes(cls):
58
-        return { fname: ftype for fname,ftype in [ (fname, cls._fieldtypes[fname]) for fname in cls._fieldtypes if fname in cls._fields ] }
58
+        return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname in cls._fields }
59 59
 
60 60
     ## @brief Populate the LeType wih datas from DB
61 61
     # @param field_list None|list : List of fieldname to fetch. If None fetch all the missing datas

+ 142
- 0
leapi/test/test_lecrud.py View File

@@ -0,0 +1,142 @@
1
+"""
2
+    Tests for _LeObject and LeObject
3
+"""
4
+
5
+import unittest
6
+from unittest import TestCase
7
+from unittest.mock import patch
8
+
9
+import EditorialModel
10
+import leapi
11
+import leapi.test.utils
12
+from leapi.lecrud import _LeCrud
13
+
14
+class LeCrudTestCase(TestCase):
15
+    @classmethod
16
+    def setUpClass(cls):
17
+        """ Write the generated code in a temporary directory and import it """
18
+        cls.tmpdir = leapi.test.utils.tmp_load_factory_code()
19
+    @classmethod
20
+    def tearDownClass(cls):
21
+        """ Remove the temporary directory created at class setup """
22
+        leapi.test.utils.cleanup(cls.tmpdir)
23
+
24
+    def test_split_query_filter(self):
25
+        """ Tests the _split_filter() classmethod """
26
+        import dyncode
27
+        query_results = {
28
+            'Hello = world' : ('Hello', '=', 'world'),
29
+            'hello <= "world"': ('hello', '<=', '"world"'),
30
+            '_he42_ll-o >= \'world"': ('_he42_ll-o', '>=', '\'world"'),
31
+            'foo in ["foo", 42, \'bar\']': ('foo', ' in ', '["foo", 42, \'bar\']'),
32
+            ' bar42              < 42': ('bar42', '<', '42'),
33
+            ' _hidden > 1337': ('_hidden', '>', '1337'),
34
+            '_42 not in foobar': ('_42', ' not in ', 'foobar'),
35
+            'hello                       in      foo':('hello', ' in ', 'foo'),
36
+            "\t\t\thello\t\t\nin\nfoo\t\t\n\t":('hello', ' in ', 'foo'),
37
+            "hello \nnot\tin \nfoo":('hello', ' not in ', 'foo'),
38
+            'hello != bar':('hello', '!=', 'bar'),
39
+            'hello = "world>= <= != in not in"': ('hello', '=', '"world>= <= != in not in"'),
40
+            'superior.parent = 13': ('superior.parent', '=', '13'),
41
+        }
42
+        for query, result in query_results.items():
43
+            res = dyncode.LeCrud._split_filter(query)
44
+            self.assertEqual(res, result, "When parsing the query : '%s' the returned value is different from the expected '%s'"%(query, result))
45
+
46
+    def test_invalid_split_query_filter(self):
47
+        """ Testing the _split_filter() method with invalid queries """
48
+        import dyncode
49
+        invalid_queries = [
50
+            '42 = 42',
51
+            '4hello = foo',
52
+            'foo == bar',
53
+            'hello >> world',
54
+            'hello =    ',
55
+            ' = world',
56
+            '=',
57
+            '42',
58
+            '"hello" = world',
59
+            'foo.bar = 15',
60
+        ]
61
+        for query in invalid_queries:
62
+            with self.assertRaises(ValueError, msg='But the query was not valid : "%s"'%query):
63
+                dyncode.LeCrud._split_filter(query)
64
+
65
+    def test_field_is_relational(self):
66
+        """ Testing the test to check if a field is relational """
67
+        from dyncode import LeCrud
68
+
69
+        test_fields = {
70
+            'superior.parent': True,
71
+            'subordinate.parent': True,
72
+            'foofoo.foo': False,
73
+        }
74
+
75
+        for field, eres in test_fields.items():
76
+            self.assertEqual(LeCrud._field_is_relational(field), eres)
77
+
78
+    def test_check_datas(self):
79
+        """ testing the check_datas* methods """
80
+        from dyncode import Publication, Numero, LeObject
81
+
82
+        datas = { 'titre':'foobar' }
83
+        Numero.check_datas_value(datas, False, False)
84
+        Numero.check_datas_value(datas, True, False)
85
+        with self.assertRaises(leapi.lecrud.LeApiDataCheckError):
86
+            Numero.check_datas_value({}, True)
87
+
88
+    def test_prepare_filters(self):
89
+        """ Testing the _prepare_filters() method """
90
+        from dyncode import Publication, Numero, LeObject, Personnes
91
+        
92
+        #Simple filters
93
+        filters = [
94
+            'lodel_id = 1',
95
+            'superior.parent  > 2'
96
+        ]
97
+
98
+        filt, rfilt = Numero._prepare_filters(filters)
99
+        self.assertEqual(filt, [('lodel_id', '=', '1')])
100
+        self.assertEqual(rfilt, [((leapi.leobject.REL_SUP,'parent'), '>', '2')])
101
+        
102
+        #All fields, no relationnal and class given
103
+        filters = []
104
+        res_filt = []
105
+        for field in Numero._fields:
106
+            filters.append('%s=1'%field)
107
+            res_filt.append((field, '=', '1'))
108
+
109
+        filt, rfilt = Publication._prepare_filters(filters)
110
+        self.assertEqual(rfilt, [])
111
+        self.assertEqual(filt, res_filt)
112
+        
113
+        #Mixed type filters (tuple and string)
114
+        filters = [
115
+            ('lodel_id', '<=', '0'),
116
+            'subordinate.parent = 2',
117
+        ]
118
+        
119
+        filt, rfilt = Numero._prepare_filters(filters)
120
+        self.assertEqual(filt, [('lodel_id', '<=', '0')])
121
+        self.assertEqual(rfilt, [((leapi.leobject.REL_SUB,'parent'), '=', '2')])
122
+ 
123
+    def test_prepare_filters_invalid(self):
124
+        """ Testing the _prepare_filters() method """
125
+        from dyncode import LeCrud, Publication, Numero, Personnes, LeObject
126
+
127
+        #Numero fields filters but no letype nor leclass given
128
+        filters = []
129
+        res_filt = []
130
+        for field in Numero._fields:
131
+            filters.append('%s=1'%field)
132
+            res_filt.append((field, '=', '1'))
133
+        
134
+        with self.assertRaises(leapi.lecrud.LeApiDataCheckError):
135
+            LeObject._prepare_filters(filters)
136
+
137
+
138
+        #simply invalid filters
139
+        filters = ['hello world !']
140
+        with self.assertRaises(ValueError):
141
+            Personnes._prepare_filters(filters)
142
+

+ 0
- 12
leapi/test/test_letype.py View File

@@ -45,18 +45,6 @@ class LeTypeTestCase(TestCase):
45 45
             with self.assertRaises(expect_e, msg="Invalid argument given %s"%badarg):
46 46
                 Numero(**badarg)
47 47
     
48
-    ## @todo when we will have a field in a type that has a check_function try to make check_datas_or_raise raise with invalid value
49
-    @unittest.skip("Waiting for EmFieldType full implementation")
50
-    def test_check_datas(self):
51
-        """ testing the check_datas* methods """
52
-        from dyncode import Publication, Numero, LeObject
53
-
54
-        datas = { 'titre':'foobar' }
55
-        Numero.check_datas_value(datas, False, False)
56
-        Numero.check_datas_value(datas, True, False)
57
-        with self.assertRaises(leapi.leobject.LeObjectError):
58
-            Numero.check_datas_value({}, True)
59
-
60 48
     @patch('leapi.letype.LeType.populate')
61 49
     def test_datas(self, dsmock):
62 50
         """ Testing the datas @property method """

Loading…
Cancel
Save