diff --git a/product/ERP5SyncML/Conduit/ERP5Conduit.py b/product/ERP5SyncML/Conduit/ERP5Conduit.py index 6e2c811974609d8356d49bffdd04a681a5e8ff63..605c661f46f7731ab557aa5939ad097bd9f3aed5 100755 --- a/product/ERP5SyncML/Conduit/ERP5Conduit.py +++ b/product/ERP5SyncML/Conduit/ERP5Conduit.py @@ -125,7 +125,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): # In the case where this new node is a object to add LOG('addNode',0,'object.id: %s' % object.getId()) LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName) - LOG('addNode',0,'isSubObjectAdd: %i' % self.getSubObjectDepth(xml)) + LOG('addNode',0,'getSubObjectDepth: %i' % self.getSubObjectDepth(xml)) LOG('addNode',0,'isHistoryAdd: %i' % self.isHistoryAdd(xml)) if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==0: if self.isHistoryAdd(xml)!=-1: # bad hack XXX to be removed @@ -209,21 +209,24 @@ class ERP5Conduit(XMLSyncUtilsMixin): #for action in self.getWorkflowActionFromXml(xml): status = self.getStatusFromXml(xml) LOG('addNode, status:',0,status) - wf_conflict_list = self.isWorkflowActionAddable(object=object, + add_action = self.isWorkflowActionAddable(object=object, status=status,wf_tool=wf_tool, - xml=xml) - LOG('addNode, workflow_history wf_conflict_list:',0,wf_conflict_list) - if wf_conflict_list==[] or force and not simulate: + wf_id=wf_id,xml=xml) + #LOG('addNode, workflow_history wf_conflict_list:',0,wf_conflict_list) + LOG('addNode, workflow_history add_action:',0,add_action) + if add_action and not simulate: LOG('addNode, setting status:',0,'ok') wf_tool.setStatusOf(wf_id,object,status) - else: - conflict_list += wf_conflict_list + #else: + # conflict_list += wf_conflict_list elif xml.nodeName in self.local_role_list and not simulate: # We want to add a local role roles = self.convertXmlValue(xml.childNodes[0].data,data_type='tokens') + user = self.getAttribute(xml,'id') roles = list(roles) # Needed for CPS, or we have a CPS error - user = roles[0] - roles = roles[1:] + LOG('local_role: ',0,'user: %s roles: %s' % (repr(user),repr(roles))) + #user = roles[0] + #roles = roles[1:] object.manage_setLocalRoles(user,roles) else: conflict_list += self.updateNode(xml=xml,object=object, force=force, @@ -236,9 +239,10 @@ class ERP5Conduit(XMLSyncUtilsMixin): """ A node is deleted """ - # In the case where this new node is a object to delete + # In the case where we have to delete an object LOG('ERP5Conduit',0,'deleteNode') LOG('ERP5Conduit',0,'deleteNode, object.id: %s' % object.getId()) + LOG('ERP5Conduit',0,'deleteNode, object path: %s' % repr(object.getPhysicalPath())) conflict_list = [] if xml is not None: xml = self.convertToXml(xml) @@ -248,6 +252,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): object_id = self.getAttribute(xml,'id') elif self.getSubObjectDepth(xml)==1: object_id = self.getSubObjectId(xml) + #LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml)) elif self.getSubObjectDepth(xml)==2: # we have to call delete node on a subsubobject sub_object_id = self.getSubObjectId(xml) @@ -256,14 +261,24 @@ class ERP5Conduit(XMLSyncUtilsMixin): sub_xml = self.getSubObjectXupdate(xml) conflict_list += self.deleteNode(xml=sub_xml,object=sub_object, force=force, simulate=simulate, **kw) - except KeyError: + except (KeyError, AttributeError): + LOG('ERP5Conduit',0,'deleteNode, Unable to delete SubObject: %s' % str(sub_object_id)) pass - else: # We do have an object_id + if object_id is not None: # We do have an object_id try: object._delObject(object_id) except (AttributeError, KeyError): LOG('ERP5Conduit',0,'deleteNode, Unable to delete: %s' % str(object_id)) pass + # In the case where we have to delete an user role + # If we are still there, this means the delete is for this node + elif xml.nodeName in self.XUPDATE_DEL: + xml = self.getElementFromXupdate(xml) + if xml.nodeName in self.local_role_list and not simulate: + # We want to del a local role + user = self.getAttribute(xml,'id') + LOG('local_role: ',0,'user: %s' % repr(user)) + object.manage_delLocalRoles([user]) return conflict_list security.declareProtected(Permissions.ModifyPortalContent, 'updateNode') @@ -382,12 +397,14 @@ class ERP5Conduit(XMLSyncUtilsMixin): conflict_list += self.addNode(xml=subnode,object=object,force=force, simulate=simulate,**kw) elif keyword == self.local_role_tag and not simulate: - # This is the case where we have to call addNode + # This is the case where we have to update Roles LOG('updateNode',0,'we will add a local role') - roles = self.convertXmlValue(data,data_type='tokens') - user = roles[0] - roles = roles[1:] - object.manage_setLocalRoles(user,roles) + #user = self.getSubObjectId(xml) + #roles = self.convertXmlValue(data,data_type='tokens') + #object.manage_setLocalRoles(user,roles) + xml = self.getElementFromXupdate(xml) + conflict_list += self.addNode(xml=xml,object=object,force=force, + simulate=simulate,**kw) elif self.isSubObjectModification(xml): # We should find the object corresponding to # this update, so we have to look in the previous_xml @@ -537,7 +554,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): """ Return a string wich is the selection for the subobject ex: for "/object[@id='161']/object[@id='default_address']/street_address" - if returns "/object[@id='default_address']/street_address" + it returns "/object[@id='default_address']/street_address" """ if re.search(self.object_exp,select) is not None: s = '/' @@ -594,17 +611,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): return subnode return None -# def getObjectId(self, xml): -# """ -# Retrieve the id -# # XXX Deprecated, we should use instead, getParameter(xml,'id') XXX -# """ -# for attribute in self.getAttributeNodeList(xml): -# if attribute.nodeName == 'id': -# data = attribute.childNodes[0].data -# return self.convertXmlValue(data) -# return None - security.declareProtected(Permissions.AccessContentsInformation,'getAttribute') def getAttribute(self, xml, param): """ @@ -801,6 +807,36 @@ class ERP5Conduit(XMLSyncUtilsMixin): result += xml_string[maxi:xml_string.find('</xupdate:element>')] result += '</' + xml.attributes[0].nodeValue + '>' return self.convertToXml(result) + if xml.nodeName in (self.XUPDATE_UPDATE+self.XUPDATE_DEL): + result = u'<' + for subnode in self.getAttributeNodeList(xml): + if subnode.nodeName == 'select': + attribute = subnode.nodeValue + s = '[@id=' + s_place = attribute.find(s) + select_id = None + if (s_place > 0): + select_id = attribute[s_place+len(s):] + select_id = select_id[:select_id.find("'",1)+1] + else: + s_place = len(attribute) + property = attribute[:s_place] + if property.find('/')==0: + property = property[1:] + result += property + if select_id is not None: + result += ' id=%s' % select_id + result += '>' + # Then dumps the xml and remove what we does'nt want + xml_string = StringIO() + PrettyPrint(xml,xml_string) + xml_string = xml_string.getvalue() + xml_string = unicode(xml_string,encoding='utf-8') + maxi = xml_string.find('>')+1 + result += xml_string[maxi:xml_string.find('</%s>' % xml.nodeName)] + result += '</' + property + '>' + LOG('getElementFromXupdate, result:',0,repr(result)) + return self.convertToXml(result) return xml security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml') @@ -833,6 +869,8 @@ class ERP5Conduit(XMLSyncUtilsMixin): data = data.replace('\n','') if type(data) is type(u"a"): data = data.encode(self.getEncoding()) + if data=='None': + return None # We can now convert string in tuple, dict, binary... if data_type in self.list_type_list: if type(data) is type('a'): @@ -891,26 +929,47 @@ class ERP5Conduit(XMLSyncUtilsMixin): return conflict_list - def isWorkflowActionAddable(self, object=None,status=None,wf_tool=None,xml=None): + def isWorkflowActionAddable(self, object=None,status=None,wf_tool=None, + wf_id=None,xml=None): """ Some checking in order to check if we should add the workfow or not - """ - conflict_list = [] - if status.has_key('action'): - action_name = status['action'] - authorized = 0 - authorized_actions = wf_tool.getActionsFor(object) - LOG('isWorkflowActionAddable, status:',0,status) - LOG('isWorkflowActionAddable, authorized_actions:',0,authorized_actions) - for action in authorized_actions: - if action['id']==action_name: - authorized = 1 - if not authorized: - string_io = StringIO() - PrettyPrint(xml,stream=string_io) - conflict = Conflict(object_path=object.getPhysicalPath(), - keyword=self.history_tag) - conflict.setXupdate(string_io.getvalue()) - conflict.setRemoteValue(status) - conflict_list += [conflict] - return conflict_list + We should not returns a conflict list as we wanted before, we should + instead go to a conflict state. + """ + # We first test if the status in not already inside the workflow_history + wf_history = object.workflow_history + if wf_history.has_key(wf_id): + action_list = wf_history[wf_id] + else: action_list = [] + addable = 1 + for action in action_list: + this_one = 1 + for key in action.keys(): + if status[key] != action[key]: + this_one = 0 + break + if this_one: + addable = 0 + break + return addable + + # This doesn't works fine because all workflows variables + # are not set the same way +# if status.has_key('action'): +# action_name = status['action'] +# authorized = 0 +# authorized_actions = wf_tool.getActionsFor(object) +# LOG('isWorkflowActionAddable, status:',0,status) +# LOG('isWorkflowActionAddable, authorized_actions:',0,authorized_actions) +# for action in authorized_actions: +# if action['id']==action_name: +# authorized = 1 +# if not authorized: +# string_io = StringIO() +# PrettyPrint(xml,stream=string_io) +# conflict = Conflict(object_path=object.getPhysicalPath(), +# keyword=self.history_tag) +# conflict.setXupdate(string_io.getvalue()) +# conflict.setRemoteValue(status) +# conflict_list += [conflict] +# return conflict_list diff --git a/product/ERP5SyncML/Subscription.py b/product/ERP5SyncML/Subscription.py index 04b2358eb8c216e52e8625afc89c92be19f037e8..81d87c56f359fd786fae08b971b455099c582f81 100755 --- a/product/ERP5SyncML/Subscription.py +++ b/product/ERP5SyncML/Subscription.py @@ -187,9 +187,9 @@ class Signature(SyncCode): """ # Constructor - def __init__(self,gid=None, status=None, xml_string=None): + def __init__(self,gid=None, id=None, status=None, xml_string=None): self.setGid(gid) - self.setId(None) + self.setId(id) self.status = status self.setXML(xml_string) self.partial_xml = None @@ -595,14 +595,14 @@ class Subscription(SyncCode, Implicit): """ self.xml_mapping = xml_mapping - def setGidGenerator(self, method_id): + def setGidGenerator(self, method): """ This set the method name wich allows to find a gid from any object """ - if method_id in (None,''): - method_id = 'getId' - self.gid_generator = method_id + if method in (None,''): + method = 'getId' + self.gid_generator = method def getGidGenerator(self): """ @@ -611,6 +611,22 @@ class Subscription(SyncCode, Implicit): """ return self.gid_generator + def getGidFromObject(self, object): + """ + """ + o_base = aq_base(object) + o_gid = None + LOG('getGidFromObject',0,'gidgenerator : %s' % repr(self.getGidGenerator())) + gid_gen = self.getGidGenerator() + if callable(gid_gen): + o_gid=gid_gen(object) + elif hasattr(o_base, gid_gen): + LOG('getGidFromObject',0,'there is the gid generator') + generator = getattr(object, self.getGidGenerator()) + o_gid = generator() + LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid)) + return o_gid + def getObjectFromGid(self, gid): """ This tries to get the object with the given gid @@ -619,15 +635,9 @@ class Subscription(SyncCode, Implicit): signature = self.getSignature(gid) # First look if we do already have the mapping between # the id and the gid -# query_list = [] -# query = self.getQuery() -# if query is type('a'): -# query_method = getattr(object,self.getQuery(),None) -# query_list = query() -# if callable(query): -# query_list = query(self) object_list = self.getObjectList() destination = self.getDestination() + LOG('getObjectFromGid',0,'gid: %s' % repr(gid)) if signature is not None: o_id = signature.getId() o = None @@ -639,16 +649,9 @@ class Subscription(SyncCode, Implicit): return o for o in object_list: LOG('getObjectFromGid',0,'working on : %s' % repr(o)) - o_base = aq_base(o) - LOG('getObjectFromGid',0,'gidgenerator : %s' % repr(self.getGidGenerator())) - if hasattr(o_base, self.getGidGenerator()): - LOG('getObjectFromGid',0,'there is the gid generator') - generator = getattr(o, self.getGidGenerator()) - o_gid = generator() - LOG('getObjectFromGid',0,'o_gid: %s' % repr(o_gid)) - LOG('getObjectFromGid',0,'gid: %s' % repr(gid)) - if o_gid == gid: - return o + o_gid = self.getGidFromObject(o) + if o_gid == gid: + return o LOG('getObjectFromGid',0,'returning None') return None @@ -675,20 +678,27 @@ class Subscription(SyncCode, Implicit): """ This tries to generate a new Id """ - if self.getIdGenerator() is not None: + LOG('generateNewId, object: ',0,object.getPhysicalPath()) + id_generator = self.getIdGenerator() + LOG('generateNewId, id_generator: ',0,id_generator) + if id_generator is not None: o_base = aq_base(object) - if hasattr(aq_base, self.getIdGenerator()): - generator = getattr(o, self.getIdGenerator()) + new_id = None + if callable(id_generator): + new_id = id_generator(object) + elif hasattr(o_base, id_generator): + generator = getattr(object, id_generator) new_id = generator() - return new_id + LOG('generateNewId, new_id: ',0,new_id) + return new_id return None - def setIdGenerator(self, method_id): + def setIdGenerator(self, method): """ This set the method name wich allows to generate a new id """ - self.id_generator = method_id + self.id_generator = method def getIdGenerator(self): """ diff --git a/product/ERP5SyncML/XMLSyncUtils.py b/product/ERP5SyncML/XMLSyncUtils.py index f21202ce8c269fa918d8868a02dac6143f3954b2..ceb35621a4c3a10fb16700b46798ae5000d2751a 100755 --- a/product/ERP5SyncML/XMLSyncUtils.py +++ b/product/ERP5SyncML/XMLSyncUtils.py @@ -198,14 +198,8 @@ class XMLSyncUtilsMixin(SyncCode): file2 = open('/tmp/sync_old_object','w') file2.write(old_xml) file2.close() - #xupdate = commands.getoutput('xmldiff -xg /tmp/sync_old_object /tmp/sync_new_object') xupdate = commands.getoutput('erp5diff /tmp/sync_old_object /tmp/sync_new_object') xupdate = xupdate[xupdate.find('<xupdate:modifications'):] - # XXX To be removed, this is only needed for xmldiff with does bad things - #while xupdate.find('xupdate:move')>0: - # LOG('getXupdateObject',0,'Removing the move section') - # xupdate = xupdate[:xupdate.find('<xupdate:move')] + \ - # xupdate[xupdate.find('</xupdate:move>\n')+16:] return xupdate def getXMLObject(self, object=None, xml_mapping=None): @@ -318,6 +312,17 @@ class XMLSyncUtilsMixin(SyncCode): return int(subnode.childNodes[0].data) return None + def getStatusCommand(self, xml): + """ + Return the value of the command inside the xml_stream + """ + # Get informations from the body + if xml.nodeName=='Status': + for subnode in xml.childNodes: + if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Cmd': + return subnode.childNodes[0].data + return None + def getAlertCode(self, xml_stream): """ Return the value of the alert code inside the full syncml message @@ -428,20 +433,20 @@ class XMLSyncUtilsMixin(SyncCode): return subnode return next_status - def getActionObjectId(self, action): - """ - XXX Deprecated - Return the id of the object described by the action - """ - for subnode in action.childNodes: - if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item': - for subnode2 in subnode.childNodes: - if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Data': - for subnode3 in subnode2.childNodes: - if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'object': - for subnode4 in subnode3.childNodes: - if subnode4.nodeType == subnode4.ELEMENT_NODE and subnode4.nodeName == 'id': - return str(subnode4.childNodes[0].data) +# def getActionObjectId(self, action): +# """ +# XXX Deprecated +# Return the id of the object described by the action +# """ +# for subnode in action.childNodes: +# if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item': +# for subnode2 in subnode.childNodes: +# if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Data': +# for subnode3 in subnode2.childNodes: +# if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'object': +# for subnode4 in subnode3.childNodes: +# if subnode4.nodeType == subnode4.ELEMENT_NODE and subnode4.nodeName == 'id': +# return str(subnode4.childNodes[0].data) #return subnode4.childNodes[0].data def getDataSubNode(self, action): @@ -562,20 +567,14 @@ class XMLSyncUtilsMixin(SyncCode): """ local_gid_list = [] syncml_data = '' -# store_xupdate = 0 -# if object is None: -# object_list = domain.getObjectList() -# else: -# store_xupdate = 1 -# object_list = [object] for object in domain.getObjectList(): status = self.SENT - gid_generator = getattr(object,domain.getGidGenerator(),None) - object_gid = None - if gid_generator is not None: - object_gid = gid_generator() - local_gid_list += [object_gid] + #gid_generator = getattr(object,domain.getGidGenerator(),None) + object_gid = domain.getGidFromObject(object) + local_gid_list += [object_gid] + #if gid_generator is not None: + # object_gid = gid_generator() force = 0 if syncml_data.count('\n') < self.MAX_LINES and (object.id.find('.')!=0): # If not we have to cut xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping) @@ -597,7 +596,7 @@ class XMLSyncUtilsMixin(SyncCode): #LOG('PubSyncModif',0,'Current object.getPath: %s' % object.getPath()) LOG('getSyncMLData',0,'no signature for gid: %s' % object_gid) xml_string = xml_object - signature = Signature(gid=object_gid) + signature = Signature(gid=object_gid,id=object.getId()) signature.setTempXML(xml_object) if xml_string.count('\n') > self.MAX_LINES: more_data=1 @@ -710,7 +709,6 @@ class XMLSyncUtilsMixin(SyncCode): # were not able to create syncml_data += self.deleteXMLObject(xml_object=signature.getXML() or '', object_gid=object_gid,cmd_id=cmd_id) - subscriber.delSignature(object_gid) return (syncml_data,xml_confirmation,cmd_id) @@ -750,7 +748,6 @@ class XMLSyncUtilsMixin(SyncCode): data_subnode = self.getDataSubNode(next_action) if next_action.nodeName == 'Add': # Then store the xml of this new subobject - #object = domain.getObjectFromGid(object=destination_path,gid=object_gid) if object is None: object_id = domain.generateNewId(object=destination_path) conflict_list += conduit.addNode(xml=data_subnode, object=destination_path, @@ -763,7 +760,6 @@ class XMLSyncUtilsMixin(SyncCode): xml_object = '' if mapping is not None: xml_object = mapping() - #xml_object = object.asXML() signature.setStatus(self.SYNCHRONIZED) signature.setId(object.getId()) signature.setXML(xml_object) @@ -785,7 +781,6 @@ class XMLSyncUtilsMixin(SyncCode): xml_object = '' if mapping is not None: xml_object = mapping() - #xml_object = object.asXML() signature.setTempXML(xml_object) if conflict_list != []: status_code = self.CONFLICT @@ -809,16 +804,14 @@ class XMLSyncUtilsMixin(SyncCode): data_subnode_string = string_io.getvalue() LOG('applyActionList, subscriber_xupdate:',0,data_subnode_string) signature.setSubscriberXupdate(data_subnode_string) -# xml_string = self.getXupdateObject(object=object, -# xml_mapping=domain.xml_mapping, -# old_xml=signature.getXML()) - # signature.setPublisherXupdate(xml_string) XXX is it needed ?? elif next_action.nodeName == 'Delete': - object_id = object.id + object_id = signature.getId() conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path, - object_id=self.getActionId(next_action)) + object_id=object_id) subscriber.delSignature(object_gid) + xml_confirmation += self.SyncMLConfirmation(cmd_id, + object_gid,status_code,'Delete') else: # We want to retrieve more data signature.setStatus(self.PARTIAL) #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial)) @@ -851,23 +844,28 @@ class XMLSyncUtilsMixin(SyncCode): while next_status != None: object_gid = self.getStatusTarget(next_status) status_code = self.getStatusCode(next_status) + status_cmd = self.getStatusCommand(next_status) signature = subscriber.getSignature(object_gid) LOG('SyncModif',0,'next_status: %s' % str(status_code)) - if status_code == self.CHUNK_OK: - destination_waiting_more_data = 1 - signature.setStatus(self.PARTIAL) - elif status_code == self.CONFLICT: - signature.setStatus(self.CONFLICT) - elif status_code == self.CONFLICT_MERGE: - # We will have to apply the update, and we should not care about conflicts - # so we have to force the update - signature.setStatus(self.NOT_SYNCHRONIZED) - signature.setForce(1) - elif status_code == self.CONFLICT_CLIENT_WIN: - # The server was agree to apply our updates, nothing to do - signature.setStatus(self.SYNCHRONIZED) - elif status_code == self.SUCCESS: - signature.setStatus(self.SYNCHRONIZED) + if status_cmd in ('Add','Replace'): + if status_code == self.CHUNK_OK: + destination_waiting_more_data = 1 + signature.setStatus(self.PARTIAL) + elif status_code == self.CONFLICT: + signature.setStatus(self.CONFLICT) + elif status_code == self.CONFLICT_MERGE: + # We will have to apply the update, and we should not care about conflicts + # so we have to force the update + signature.setStatus(self.NOT_SYNCHRONIZED) + signature.setForce(1) + elif status_code == self.CONFLICT_CLIENT_WIN: + # The server was agree to apply our updates, nothing to do + signature.setStatus(self.SYNCHRONIZED) + elif status_code == self.SUCCESS: + signature.setStatus(self.SYNCHRONIZED) + elif status_cmd == 'Delete': + if status_code == self.SUCCESS: + subscriber.delSignature(object_gid) next_status = self.getNextSyncBodyStatus(remote_xml, next_status) return (destination_waiting_more_data, has_status_list) diff --git a/product/ERP5SyncML/tests/crm.zexp b/product/ERP5SyncML/tests/crm.zexp index 2ffecbcd0b0494ee5b181d906be8d875937e61fb..a828288861b3be25cbafb27e7ccd4782beb719b4 100755 Binary files a/product/ERP5SyncML/tests/crm.zexp and b/product/ERP5SyncML/tests/crm.zexp differ diff --git a/product/ERP5SyncML/tests/testERP5SyncML.py b/product/ERP5SyncML/tests/testERP5SyncML.py index 9d0cd6b0daf4d5de2e2e34719c9a275282d3ed2d..893ab86e1db6fb7e453a0602209f2be58c56c619 100755 --- a/product/ERP5SyncML/tests/testERP5SyncML.py +++ b/product/ERP5SyncML/tests/testERP5SyncML.py @@ -53,15 +53,24 @@ class TestERP5SyncML(ERP5TypeTestCase): # Different variables used for this test run_all_test = 1 + workflow_id = 'edit_workflow' first_name1 = 'Sebastien' last_name1 = 'Robin' description1 = 'description1 $sdfr鏮sdfs鏳f_oisfsopf' + lang1 = 'fr' + format2 = 'html' + format3 = 'xml' + format4 = 'txt' first_name2 = 'Jean-Paul' last_name2 = 'Smets' description2 = 'description2猷@ $*< <<< >>>></title>&oekd' + lang2 = 'en' first_name3 = 'Yoshinori' last_name3 = 'Okuji' description3 = 'description3 鐂df__sdf珑鏮df___&&閉]]鞍鞍鞍' + description4 = 'description4 sdflkmooo^^^^]]]]]{{{{{{{' + lang3 = 'jp' + lang4 = 'ca' xml_mapping = 'asXML' id1 = '170' id2 = '171' @@ -153,8 +162,8 @@ class TestERP5SyncML(ERP5TypeTestCase): def login(self, quiet=0, run=run_all_test): uf = self.getPortal().acl_users - uf._doAddUser('ERP5TypeTestCase', '', ['Manager'], []) - user = uf.getUserById('ERP5TypeTestCase').__of__(uf) + uf._doAddUser('seb', '', ['Manager'], []) + user = uf.getUserById('seb').__of__(uf) newSecurityManager(None, user) def populatePersonServer(self, quiet=0, run=run_all_test): @@ -178,9 +187,24 @@ class TestERP5SyncML(ERP5TypeTestCase): return nb_person def setupPublicationAndSubscription(self, quiet=0, run=run_all_test): - self.testAddPublication(quiet=quiet,run=run) - self.testAddSubscription1(quiet=quiet,run=run) - self.testAddSubscription2(quiet=quiet,run=run) + self.testAddPublication(quiet=1,run=1) + self.testAddSubscription1(quiet=1,run=1) + self.testAddSubscription2(quiet=1,run=1) + + def setupPublicationAndSubscriptionAndGid(self, quiet=0, run=run_all_test): + self.setupPublicationAndSubscription(quiet=1,run=1) + def getGid(object): + return object.getTitle() + portal_sync = self.getSynchronizationTool() + sub1 = portal_sync.getSubscription(self.sub_id1) + sub2 = portal_sync.getSubscription(self.sub_id2) + pub = portal_sync.getPublication(self.pub_id) + pub.setGidGenerator(getGid) + sub1.setGidGenerator(getGid) + sub2.setGidGenerator(getGid) + pub.setIdGenerator('generateNewId') + sub1.setIdGenerator('generateNewId') + sub2.setIdGenerator('generateNewId') def testGetSynchronizationList(self, quiet=0, run=run_all_test): # This test the getSynchronizationList, ie, @@ -196,9 +220,11 @@ class TestERP5SyncML(ERP5TypeTestCase): self.failUnless(len(synchronization_list)==self.nb_synchronization) def testGetObjectList(self, quiet=0, run=run_all_test): - # This test the default getObjectList, ie, when the - # query is 'objectValues', and this also test if we enter - # a new method for the query + """ + This test the default getObjectList, ie, when the + query is 'objectValues', and this also test if we enter + a new method for the query + """ if not run: return if not quiet: ZopeTestCase._print('\nTest getObjectList ') @@ -224,8 +250,10 @@ class TestERP5SyncML(ERP5TypeTestCase): self.failUnless(len(object_list)==1) def testExportImport(self, quiet=0, run=run_all_test): - # We will try to export a person with asXML - # And then try to add it to another folder with a conduit + """ + We will try to export a person with asXML + And then try to add it to another folder with a conduit + """ if not run: return if not quiet: ZopeTestCase._print('\nTest Export and Import ') @@ -242,10 +270,17 @@ class TestERP5SyncML(ERP5TypeTestCase): new_object = person_client1._getOb(self.id1) self.failUnless(new_object.getLastName()==self.last_name1) self.failUnless(new_object.getFirstName()==self.first_name1) + # XXX We should also looks at the workflow history + self.failUnless(len(new_object.workflow_history[self.workflow_id])==2) + s_local_role = person_server.get_local_roles() + c_local_role = person_client1.get_local_roles() + self.assertEqual(s_local_role,c_local_role) def synchronize(self, id, run=run_all_test): - # This just define how we synchronize, we have - # to define it here because it is specific to the unit testing + """ + This just define how we synchronize, we have + to define it here because it is specific to the unit testing + """ portal_sync = self.getSynchronizationTool() portal_sync.email = None subscription = portal_sync.getSubscription(id) @@ -489,6 +524,7 @@ class TestERP5SyncML(ERP5TypeTestCase): - id1 - id1 - id2 + - id1 - id2 """ if not run: return @@ -501,12 +537,18 @@ class TestERP5SyncML(ERP5TypeTestCase): kw = {'first_name':self.first_name1,'last_name':self.last_name1, 'description':self.description1} sub_person1.edit(**kw) - sub_sub_person = sub_person1.newContent(id=self.id2,portal_type='Person') + sub_sub_person1 = sub_person1.newContent(id=self.id1,portal_type='Person') + kw = {'first_name':self.first_name1,'last_name':self.last_name1, + 'description':self.description1} + sub_sub_person1.edit(**kw) + sub_sub_person2 = sub_person1.newContent(id=self.id2,portal_type='Person') kw = {'first_name':self.first_name2,'last_name':self.last_name2, 'description':self.description2} - sub_sub_person.edit(**kw) + sub_sub_person2.edit(**kw) # remove ('','portal...','person_server') - len_path = len(sub_sub_person.getPhysicalPath()) - 3 + len_path = len(sub_sub_person1.getPhysicalPath()) - 3 + self.failUnless(len_path==3) + len_path = len(sub_sub_person2.getPhysicalPath()) - 3 self.failUnless(len_path==3) def testAddSubObject(self, quiet=0, run=run_all_test): @@ -522,23 +564,32 @@ class TestERP5SyncML(ERP5TypeTestCase): LOG('Testing... ',0,'testAddSubObject') self.populatePersonServerWithSubObject(quiet=1,run=1) self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) self.checkSynchronizationStateIsSynchronized() person_client1 = self.getPersonClient1() person1_c = person_client1._getOb(self.id1) sub_person1_c = person1_c._getOb(self.id1) - sub_sub_person = sub_person1_c._getOb(self.id2) + sub_sub_person1 = sub_person1_c._getOb(self.id1) + sub_sub_person2 = sub_person1_c._getOb(self.id2) # remove ('','portal...','person_server') - len_path = len(sub_sub_person.getPhysicalPath()) - 3 + len_path = len(sub_sub_person1.getPhysicalPath()) - 3 self.failUnless(len_path==3) - self.failUnless(sub_sub_person.getDescription()==self.description2) - self.failUnless(sub_sub_person.getFirstName()==self.first_name2) - self.failUnless(sub_sub_person.getLastName()==self.last_name2) - - def testUpdateSubObject(self, quiet=0, run=1): + len_path = len(sub_sub_person2.getPhysicalPath()) - 3 + self.failUnless(len_path==3) + self.failUnless(sub_sub_person1.getDescription()==self.description1) + self.failUnless(sub_sub_person1.getFirstName()==self.first_name1) + self.failUnless(sub_sub_person1.getLastName()==self.last_name1) + self.failUnless(sub_sub_person2.getDescription()==self.description2) + self.failUnless(sub_sub_person2.getFirstName()==self.first_name2) + self.failUnless(sub_sub_person2.getLastName()==self.last_name2) + + def testUpdateSubObject(self, quiet=0, run=run_all_test): """ - In this test, we start with a tree of object already + In this test, we start with a tree of object already synchronized, then we update a subobject, and we will see - if it is updated correctly + if it is updated correctly. + To make this test a bit more harder, we will update on both + the client and the server by the same time """ if not run: return self.testAddSubObject(quiet=1,run=1) @@ -562,6 +613,326 @@ class TestERP5SyncML(ERP5TypeTestCase): self.failUnless(sub_sub_person_s.getDescription()==self.description3) self.failUnless(sub_sub_person_s.getFirstName()==self.first_name3) + def testDeleteObject(self, quiet=0, run=run_all_test): + """ + We will do a first synchronization, then delete an object on both + sides, and we will see if nothing is left on the server and also + on the two clients + """ + if not run: return + self.testFirstSynchronization(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Delete Object ') + LOG('Testing... ',0,'testDeleteObject') + person_server = self.getPersonServer() + person_server.manage_delObjects(self.id1) + person_client1 = self.getPersonClient1() + person_client1.manage_delObjects(self.id2) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + self.checkSynchronizationStateIsSynchronized() + portal_sync = self.getSynchronizationTool() + publication = portal_sync.getPublication(self.pub_id) + subscription1 = portal_sync.getSubscription(self.sub_id1) + subscription2 = portal_sync.getSubscription(self.sub_id2) + self.failUnless(len(publication.getObjectList())==0) + self.failUnless(len(subscription1.getObjectList())==0) + self.failUnless(len(subscription2.getObjectList())==0) + + def testDeleteSubObject(self, quiet=0, run=run_all_test): + """ + We will do a first synchronization, then delete a sub-object on both + sides, and we will see if nothing is left on the server and also + on the two clients + - before : after : + - id1 - id1 + - id1 - id1 + - id2 - id2 + - id1 + - id2 + """ + if not run: return + self.testAddSubObject(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Delete Sub Object ') + LOG('Testing... ',0,'testDeleteSubObject') + person_server = self.getPersonServer() + sub_object_s = person_server._getOb(self.id1)._getOb(self.id1) + sub_object_s.manage_delObjects(self.id1) + person_client1 = self.getPersonClient1() + sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1) + sub_object_c1.manage_delObjects(self.id2) + person_client2 = self.getPersonClient2() + sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + self.checkSynchronizationStateIsSynchronized() + len_s = len(sub_object_s.objectValues()) + len_c1 = len(sub_object_c1.objectValues()) + len_c2 = len(sub_object_c2.objectValues()) + self.failUnless(len_s==len_c1==len_c2==0) + + def testGetConflictListOnSubObject(self, quiet=0, run=run_all_test): + """ + We will change several attributes on a sub object on both the server + and a client, then we will see if we have correctly the conflict list + """ + if not run: return + self.testAddSubObject(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Get Conflict List On Sub Object ') + LOG('Testing... ',0,'testGetConflictListOnSubObject') + person_server = self.getPersonServer() + object_s = person_server._getOb(self.id1) + sub_object_s = object_s._getOb(self.id1) + person_client1 = self.getPersonClient1() + sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1) + person_client2 = self.getPersonClient2() + sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1) + # Change values so that we will get conflicts + kw = {'language':self.lang2,'description':self.description2} + sub_object_s.edit(**kw) + kw = {'language':self.lang3,'description':self.description3} + sub_object_c1.edit(**kw) + self.synchronize(self.sub_id1) + portal_sync = self.getSynchronizationTool() + conflict_list = portal_sync.getConflictList() + self.failUnless(len(conflict_list)==2) + conflict_list = portal_sync.getConflictList(sub_object_c1) + self.failUnless(len(conflict_list)==0) + conflict_list = portal_sync.getConflictList(object_s) + self.failUnless(len(conflict_list)==0) + conflict_list = portal_sync.getConflictList(sub_object_s) + self.failUnless(len(conflict_list)==2) + + def testApplyPublisherDocumentOnSubObject(self, quiet=0, run=run_all_test): + """ + there's several conflict on a sub object, we will see if we can + correctly have the publisher version of this document + """ + if not run: return + self.testGetConflictListOnSubObject(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Apply Publisher Document On Sub Object ') + LOG('Testing... ',0,'testApplyPublisherDocumentOnSubObject') + portal_sync = self.getSynchronizationTool() + conflict_list = portal_sync.getConflictList() + conflict = conflict_list[0] + conflict.applyPublisherDocument() + person_server = self.getPersonServer() + sub_object_s = person_server._getOb(self.id1)._getOb(self.id1) + person_client1 = self.getPersonClient1() + sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1) + person_client2 = self.getPersonClient2() + sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(sub_object_s.getDescription()==self.description2) + self.failUnless(sub_object_s.getLanguage()==self.lang2) + self.failUnless(sub_object_c1.getDescription()==self.description2) + self.failUnless(sub_object_c1.getLanguage()==self.lang2) + self.failUnless(sub_object_c2.getDescription()==self.description2) + self.failUnless(sub_object_c2.getLanguage()==self.lang2) + + def testApplySubscriberDocumentOnSubObject(self, quiet=0, run=run_all_test): + """ + there's several conflict on a sub object, we will see if we can + correctly have the subscriber version of this document + """ + if not run: return + self.testGetConflictListOnSubObject(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Apply Subscriber Document On Sub Object ') + LOG('Testing... ',0,'testApplySubscriberDocumentOnSubObject') + portal_sync = self.getSynchronizationTool() + conflict_list = portal_sync.getConflictList() + conflict = conflict_list[0] + conflict.applySubscriberDocument() + person_server = self.getPersonServer() + sub_object_s = person_server._getOb(self.id1)._getOb(self.id1) + person_client1 = self.getPersonClient1() + sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1) + person_client2 = self.getPersonClient2() + sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(sub_object_s.getDescription()==self.description3) + self.failUnless(sub_object_s.getLanguage()==self.lang3) + self.failUnless(sub_object_c1.getDescription()==self.description3) + self.failUnless(sub_object_c1.getLanguage()==self.lang3) + self.failUnless(sub_object_c2.getDescription()==self.description3) + self.failUnless(sub_object_c2.getLanguage()==self.lang3) + + def testSynchronizeWithStrangeGid(self, quiet=0, run=run_all_test): + """ + By default, the synchronization process use the id in order to + recognize objects (because by default, getGid==getId. Here, we will see + if it also works with a somewhat strange getGid + """ + if not run: return + if not quiet: + ZopeTestCase._print('\nTest Synchronize With Strange Gid ') + LOG('Testing... ',0,'testSynchronizeWithStrangeGid') + self.login() + self.setupPublicationAndSubscriptionAndGid(quiet=1,run=1) + nb_person = self.populatePersonServer(quiet=1,run=1) + # This will test adding object + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + portal_sync = self.getSynchronizationTool() + subscription1 = portal_sync.getSubscription(self.sub_id1) + self.failUnless(len(subscription1.getObjectList())==nb_person) + publication = portal_sync.getPublication(self.pub_id) + self.failUnless(len(publication.getObjectList())==nb_person) + gid = self.first_name1 + ' ' + self.last_name1 # ie the title 'Sebastien Robin' + person_c1 = subscription1.getObjectFromGid(gid) + id_c1 = person_c1.getId() + self.failUnless(id_c1 in ('1','2')) # id given by the default generateNewId + person_s = publication.getObjectFromGid(gid) + id_s = person_s.getId() + self.failUnless(id_s==self.id1) + # This will test updating object + person_s.setDescription(self.description3) + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(person_s.getDescription()==self.description3) + self.failUnless(person_c1.getDescription()==self.description3) + # This will test deleting object + person_server = self.getPersonServer() + person_client1 = self.getPersonClient1() + person_server.manage_delObjects(self.id2) + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(len(subscription1.getObjectList())==(nb_person-1)) + self.failUnless(len(publication.getObjectList())==(nb_person-1)) + person_s = publication.getObjectFromGid(gid) + person_c1 = subscription1.getObjectFromGid(gid) + self.failUnless(person_s.getDescription()==self.description3) + self.failUnless(person_c1.getDescription()==self.description3) + + def testMultiNodeConflict(self, quiet=0, run=run_all_test): + """ + We will create conflicts with 3 differents nodes, and we will + solve it by taking one full version of documents. + """ + if not run: return + self.testFirstSynchronization(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Multi Node Conflict ') + LOG('Testing... ',0,'testMultiNodeConflict') + portal_sync = self.getSynchronizationTool() + person_server = self.getPersonServer() + person1_s = person_server._getOb(self.id1) + kw = {'language':self.lang2,'description':self.description2, + 'format':self.format2} + person1_s.edit(**kw) + person_client1 = self.getPersonClient1() + person1_c1 = person_client1._getOb(self.id1) + kw = {'language':self.lang3,'description':self.description3, + 'format':self.format3} + person1_c1.edit(**kw) + person_client2 = self.getPersonClient2() + person1_c2 = person_client2._getOb(self.id1) + kw = {'language':self.lang4,'description':self.description4, + 'format':self.format4} + person1_c2.edit(**kw) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + conflict_list = portal_sync.getConflictList() + self.failUnless(len(conflict_list)==6) + # we will take : + # description on person_server + # language on person_client1 + # format on person_client2 + for conflict in conflict_list: + subscriber = conflict.getSubscriber() + property = conflict.getPropertyId() + resolve = 0 + if property == 'language': + if subscriber.getSubscriptionUrl()==self.subscription_url1: + resolve = 1 + conflict.applySubscriberValue() + if property == 'format': + if subscriber.getSubscriptionUrl()==self.subscription_url2: + resolve = 1 + conflict.applySubscriberValue() + if not resolve: + conflict.applyPublisherValue() + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(person1_s.getDescription()==self.description2) + self.failUnless(person1_c1.getDescription()==self.description2) + self.failUnless(person1_c2.getDescription()==self.description2) + self.failUnless(person1_s.getLanguage()==self.lang3) + self.failUnless(person1_c1.getLanguage()==self.lang3) + self.failUnless(person1_c2.getLanguage()==self.lang3) + self.failUnless(person1_s.getFormat()==self.format4) + self.failUnless(person1_c1.getFormat()==self.format4) + self.failUnless(person1_c2.getFormat()==self.format4) + + def testSynchronizeWorkflowHistory(self, quiet=0, run=run_all_test): + """ + We will do a synchronization, then we will edit two times + the object on the server, then two times the object on the + client, and see if the global history as 4 more actions. + """ + if not run: return + self.testFirstSynchronization(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Synchronize WorkflowHistory ') + LOG('Testing... ',0,'testSynchronizeWorkflowHistory') + person_server = self.getPersonServer() + person1_s = person_server._getOb(self.id1) + person_client1 = self.getPersonClient1() + person1_c = person_client1._getOb(self.id1) + kw1 = {'description':self.description1} + kw2 = {'description':self.description2} + len_wf = len(person1_s.workflow_history[self.workflow_id]) + person1_s.edit(**kw2) + person1_c.edit(**kw2) + person1_s.edit(**kw1) + person1_c.edit(**kw1) + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + self.failUnless(len(person1_s.workflow_history[self.workflow_id])==len_wf+4) + self.failUnless(len(person1_c.workflow_history[self.workflow_id])==len_wf+4) + + def testUpdateLocalRole(self, quiet=0, run=run_all_test): + """ + We will do a first synchronization, then modify, add and delete + an user role and see if it is correctly synchronized + """ + if not run: return + self.testFirstSynchronization(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Update Local Role ') + LOG('Testing... ',0,'testUpdateLocalRole') + # First, Create a new user + uf = self.getPortal().acl_users + uf._doAddUser('jp', '', ['Manager'], []) + user = uf.getUserById('jp').__of__(uf) + # then update create and delete roles + person_server = self.getPersonServer() + person1_s = person_server._getOb(self.id1) + person2_s = person_server._getOb(self.id2) + person_client1 = self.getPersonClient1() + person1_c = person_client1._getOb(self.id1) + person2_c = person_client1._getOb(self.id2) + person1_s.manage_setLocalRoles('seb',['Manager','Owner']) + person2_s.manage_setLocalRoles('jp',['Manager','Owner']) + person2_s.manage_delLocalRoles(['seb']) + self.synchronize(self.sub_id1) + self.synchronize(self.sub_id2) + role_1_s = person1_s.get_local_roles() + role_2_s = person2_s.get_local_roles() + role_1_c = person1_c.get_local_roles() + role_2_c = person2_c.get_local_roles() + self.assertEqual(role_1_s,role_1_c) + self.assertEqual(role_2_s,role_2_c) + if __name__ == '__main__': framework() else: