mirror of
https://github.com/yweber/lodel2.git
synced 2026-08-06 04:18:37 +02:00
Add test for LeObject with a mock on the datasource + bugfix on _LeObject and LeType
This commit is contained in:
parent
ee89b62f40
commit
965560bf6b
4 changed files with 149 additions and 26 deletions
|
|
@ -38,7 +38,7 @@ class DummyDatasource(object):
|
|||
# @param relational_filters list : relationnal filters list (see @ref leobject_filters )
|
||||
# @return okay bool: True on success, it will raise on failure
|
||||
def delete(self, letype, leclass, filters, relational_filters):
|
||||
print("DummyDatasource.delete: ", lodel_id)
|
||||
print("DummyDatasource.delete: ", filters)
|
||||
return True
|
||||
|
||||
## @brief search for a collection of objects
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ class _LeObject(object):
|
|||
|
||||
for data in datas:
|
||||
letype.check_datas_or_raise(data, complete = True)
|
||||
|
||||
return cls._datasource.insert(letype, leclass, datas)
|
||||
|
||||
## @brief Delete LeObjects given filters
|
||||
|
|
@ -72,34 +71,35 @@ class _LeObject(object):
|
|||
# @param filters list : list of filters (see @ref leobject_filters)
|
||||
# @return bool
|
||||
@classmethod
|
||||
def delete(cls, letype, leclass, filters):
|
||||
filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, cls, cls._leclass)
|
||||
letype, leclass = cls._prepare_targets(letype, leclass)
|
||||
return cls._datasource(letype, leclass, filters, relationnal_filters)
|
||||
def delete(cls, letype, filters):
|
||||
letype, leclass = cls._prepare_targets(letype)
|
||||
filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
|
||||
return cls._datasource.delete(letype, leclass, filters, relationnal_filters)
|
||||
|
||||
## @brief Update LeObjects given filters and datas
|
||||
# @param cls
|
||||
# @param letype LeType|str : LeType child class or name
|
||||
# @param leclass LeClass|str : LeClass child class or name
|
||||
# @param filters list : list of filters (see @ref leobject_filters)
|
||||
@classmethod
|
||||
def update(cls, letype, leclass, filters, datas):
|
||||
filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, cls, cls._leclass)
|
||||
letype, leclass = cls._prepare_targets(letype, leclass)
|
||||
def update(cls, letype, filters, datas):
|
||||
letype, leclass = cls._prepare_targets(letype)
|
||||
filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
|
||||
if letype is None:
|
||||
raise ValueError("Argument letype cannot be None")
|
||||
letype.check_datas_or_raise(datas, False)
|
||||
return cls._datasource(letype, leclass, filters, relationnal_filters, datas)
|
||||
return cls._datasource.update(letype, leclass, filters, relationnal_filters, datas)
|
||||
|
||||
## @brief make a search to retrieve a collection of LeObject
|
||||
# @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
|
||||
# @param field_list list|None : list of string representing fields see @ref leobject_filters
|
||||
# @param typename str : The name of the LeType we want
|
||||
# @param classname str : The name of the LeClass we want
|
||||
# @param cls
|
||||
# @return responses ({string:*}): a list of dict with field:value
|
||||
def get(self, query_filters, field_list = None, typename = None, classname = None):
|
||||
@classmethod
|
||||
def get(cls, query_filters, field_list = None, typename = None, classname = None):
|
||||
|
||||
letype,leclass = self._prepare_targets(typename, classname)
|
||||
letype,leclass = cls._prepare_targets(typename, classname)
|
||||
|
||||
#Fetching LeType
|
||||
if typename is None:
|
||||
|
|
@ -107,21 +107,21 @@ class _LeObject(object):
|
|||
field_list.append('type_id')
|
||||
|
||||
#Checking field_list
|
||||
if field_list is None:
|
||||
if field_list is None or len(field_list) == 0:
|
||||
#default field_list
|
||||
if not (letype is None):
|
||||
flist = letype._fields
|
||||
field_list = letype._fields
|
||||
elif not (leclass is None):
|
||||
flist = leclass._fieldtypes.keys()
|
||||
field_list = leclass._fieldtypes.keys()
|
||||
else:
|
||||
flist = EditorialModel.classtype.common_fields.keys()
|
||||
else:
|
||||
LeFactory._check_fields(letype, leclass, field_list)
|
||||
field_list = EditorialModel.classtype.common_fields.keys()
|
||||
field_list = cls._prepare_field_list(field_list, letype, leclass)
|
||||
|
||||
#preparing filters
|
||||
filters, relationnal_filters = self._prepare_filters(query_filters, letype, leclass)
|
||||
filters, relationnal_filters = cls._prepare_filters(query_filters, letype, leclass)
|
||||
|
||||
#Fetching datas from datasource
|
||||
datas = self._datasource.get(emclass, emtype, field_list, filters, relational_filters)
|
||||
datas = cls._datasource.get(leclass, letype, field_list, filters, relationnal_filters)
|
||||
|
||||
#Instanciating corresponding LeType child classes with datas
|
||||
result = list()
|
||||
|
|
@ -131,6 +131,14 @@ class _LeObject(object):
|
|||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _prepare_field_list(cls, field_list, letype, leclass):
|
||||
cls._check_fields(letype, leclass, [f for f in field_list if not cls._field_is_relational(f)])
|
||||
for i, field in enumerate(field_list):
|
||||
if cls._field_is_relational(field):
|
||||
field_list[i] = cls._prepare_relational_field(field)
|
||||
return field_list
|
||||
|
||||
## @brief Preparing letype and leclass arguments
|
||||
#
|
||||
# This function will do multiple things :
|
||||
|
|
@ -194,7 +202,7 @@ class _LeObject(object):
|
|||
#Checks that fields are in this type
|
||||
for field in fields:
|
||||
if field not in field_l:
|
||||
raise LeObjectQueryError("No field named '%s' in '%s'"%(field, typename))
|
||||
raise LeObjectQueryError("No field named '%s' in '%s'"%(field, letype.__name__))
|
||||
pass
|
||||
|
||||
## @brief Prepare filters for datasource
|
||||
|
|
|
|||
|
|
@ -118,15 +118,15 @@ class LeType(leobject.leobject._LeObject):
|
|||
# @throw AttributeError if datas provides values for fields that doesn't exists
|
||||
@classmethod
|
||||
def check_datas_or_raise(cls, datas, complete = False):
|
||||
autom_fields = [f.name for f in cls._fieldtypes if f.internal]
|
||||
autom_fields = [f for f, ft in cls._fieldtypes.items() if hasattr(ft,'internal') and ft.internal]
|
||||
for dname, dval in datas.items():
|
||||
if dname in autom_fields:
|
||||
raise AttributeError("The field '%s' is internal"%(dname))
|
||||
if dname not in cls._fields:
|
||||
raise AttributeError("No such field '%s' for %s"%(dname, self.__class__.__name__))
|
||||
cls._fieldtypess[dname].check_or_raise(dval)
|
||||
cls._fieldtypes[dname].check_or_raise(dval)
|
||||
|
||||
fields = [f.name for f in cls._fieldtypes if not f.internal]
|
||||
fields = [f for f, ft in cls._fieldtypes.items() if not hasattr(ft,'internal') or not ft.internal]
|
||||
if complete and len(datas) < len(fields):
|
||||
raise LeObjectError("The argument complete was True but some fields are missing : %s"%(set(fields) - set(datas.keys())))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import unittest
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
import EditorialModel
|
||||
import leobject
|
||||
|
|
@ -53,7 +54,6 @@ class _LeObjectTestCase(TestCase):
|
|||
_LeObject._split_filter(query)
|
||||
|
||||
## Testing methods that need the generated code
|
||||
# @todo mock the datasource to test the get, update, delete and insert methods
|
||||
class LeObjectTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
|
|
@ -202,3 +202,118 @@ class LeObjectTestCase(TestCase):
|
|||
with self.assertRaises(ValueError):
|
||||
LeObject._prepare_filters(filters, None, None)
|
||||
|
||||
class LeObjectMockDatasourceTestCase(TestCase):
|
||||
""" Testing _LeObject using a mock on the datasource """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Write the generated code in a temporary directory and import it """
|
||||
cls.tmpdir = leobject.test.utils.tmp_load_factory_code()
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Remove the temporary directory created at class setup """
|
||||
leobject.test.utils.cleanup(cls.tmpdir)
|
||||
|
||||
@patch('leobject.datasources.dummy.DummyDatasource.insert')
|
||||
def test_insert(self, dsmock):
|
||||
from dyncode import Publication, Numero, LeObject
|
||||
ndatas = [
|
||||
[{'titre' : 'FooBar'}],
|
||||
[{'titre':'hello'},{'titre':'world'}],
|
||||
]
|
||||
for ndats in ndatas:
|
||||
LeObject.insert(Numero,ndats)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ndats)
|
||||
dsmock.reset_mock()
|
||||
|
||||
LeObject.insert('Numero',ndats)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ndats)
|
||||
dsmock.reset_mock()
|
||||
|
||||
@patch('leobject.datasources.dummy.DummyDatasource.update')
|
||||
def test_update(self, dsmock):
|
||||
from dyncode import Publication, Numero, LeObject
|
||||
|
||||
args = [
|
||||
( ['lodel_id = 1'],
|
||||
{'titre':'foobar'},
|
||||
[('lodel_id','=','1')],
|
||||
[]
|
||||
),
|
||||
( ['superior.parent in [1,2,3,4,5,6]', 'titre != "FooBar"'],
|
||||
{'titre':'FooBar'},
|
||||
[( 'titre','!=','"FooBar"')],
|
||||
[( (leobject.leobject.REL_SUP, 'parent') ,' in ', '[1,2,3,4,5,6]')]
|
||||
),
|
||||
]
|
||||
|
||||
for filters, datas, ds_filters, ds_relfilters in args:
|
||||
LeObject.update(Numero, filters, datas)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters, datas)
|
||||
dsmock.reset_mock()
|
||||
|
||||
LeObject.update('Numero', filters, datas)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters, datas)
|
||||
dsmock.reset_mock()
|
||||
|
||||
@patch('leobject.datasources.dummy.DummyDatasource.delete')
|
||||
def test_delete(self, dsmock):
|
||||
from dyncode import Publication, Numero, LeObject
|
||||
|
||||
args = [
|
||||
(
|
||||
['lodel_id=1'],
|
||||
[('lodel_id', '=', '1')],
|
||||
[]
|
||||
),
|
||||
(
|
||||
['subordinate.parent not in [1,2,3]', 'titre = "titre nul"'],
|
||||
[('titre','=', '"titre nul"')],
|
||||
[( (leobject.leobject.REL_SUB, 'parent'), ' not in ', '[1,2,3]')]
|
||||
),
|
||||
]
|
||||
|
||||
for filters, ds_filters, ds_relfilters in args:
|
||||
LeObject.delete(Numero, filters)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
|
||||
dsmock.reset_mock()
|
||||
|
||||
LeObject.delete('Numero', filters)
|
||||
dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
|
||||
dsmock.reset_mock()
|
||||
|
||||
@patch('leobject.datasources.dummy.DummyDatasource.get')
|
||||
def test_get(self, dsmock):
|
||||
from dyncode import Publication, Numero, LeObject
|
||||
|
||||
args = [
|
||||
(
|
||||
['lodel_id', 'superior.parent'],
|
||||
['titre != "foobar"'],
|
||||
|
||||
['lodel_id', (leobject.leobject.REL_SUP, 'parent')],
|
||||
[('titre','!=', '"foobar"')],
|
||||
[]
|
||||
),
|
||||
(
|
||||
['lodel_id', 'titre', 'superior.parent', 'subordinate.translation'],
|
||||
['superior.parent in [1,2,3,4,5]'],
|
||||
|
||||
['lodel_id', 'titre', (leobject.leobject.REL_SUP,'parent'), (leobject.leobject.REL_SUB, 'translation')],
|
||||
[],
|
||||
[( (leobject.leobject.REL_SUP, 'parent'), ' in ', '[1,2,3,4,5]')]
|
||||
),
|
||||
(
|
||||
[],
|
||||
[],
|
||||
|
||||
Numero._fields,
|
||||
[],
|
||||
[]
|
||||
),
|
||||
]
|
||||
|
||||
for field_list, filters, fl_ds, filters_ds, rfilters_ds in args:
|
||||
LeObject.get(filters, field_list, Numero)
|
||||
dsmock.assert_called_with(Publication, Numero, fl_ds, filters_ds, rfilters_ds)
|
||||
dsmock.reset_mock()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue