[server] fix checkintegrity with pyodbc
"msg % row" dies with "not enough arguments for format string" if row is
a pyodbc.Row.
# -*- coding: utf-8 -*-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.fromcubicwebimportValidationErrorfromcubicweb.devtools.testlibimportCubicWebTCimportcubicweb.server.sessionfromcubicweb.server.sessionimportConnectionasOldConnectionfromcubicweb.server.sources.nativeimportUndoTransactionException,_UndoExceptionfromcubicweb.transactionimportNoSuchTransactionclassUndoableTransactionTC(CubicWebTC):defsetup_database(self):withself.admin_access.repo_cnx()ascnx:self.totoeid=self.create_user(cnx,'toto',password='toto',groups=('users',),commit=False).eidself.txuuid=cnx.commit()deftoto(self,cnx):returncnx.entity_from_eid(self.totoeid)defsetUp(self):classConnection(OldConnection):"""Force undo feature to be turned on in all case"""undo_actions=property(lambdatx:True,lambdax,y:None)cubicweb.server.session.Connection=Connectionsuper(UndoableTransactionTC,self).setUp()deftearDown(self):cubicweb.server.session.Connection=OldConnectionsuper(UndoableTransactionTC,self).tearDown()defcheck_transaction_deleted(self,cnx,txuuid):# also check transaction actions have been properly deletedcu=cnx.system_sql("SELECT * from tx_entity_actions WHERE tx_uuid='%s'"%txuuid)self.assertFalse(cu.fetchall())cu=cnx.system_sql("SELECT * from tx_relation_actions WHERE tx_uuid='%s'"%txuuid)self.assertFalse(cu.fetchall())defassertUndoTransaction(self,cnx,txuuid,expected_errors=None):ifexpected_errorsisNone:expected_errors=[]try:cnx.undo_transaction(txuuid)exceptUndoTransactionExceptionasexn:errors=exn.errorselse:errors=[]self.assertEqual(errors,expected_errors)deftest_undo_api(self):self.assertTrue(self.txuuid)# test transaction apiwithself.admin_access.client_cnx()ascnx:tx_actions=cnx.transaction_actions(self.txuuid)self.assertEqual(len(tx_actions),2,tx_actions)self.assertRaises(NoSuchTransaction,cnx.transaction_info,'hop')self.assertRaises(NoSuchTransaction,cnx.transaction_actions,'hop')self.assertRaises(NoSuchTransaction,cnx.undo_transaction,'hop')txinfo=cnx.transaction_info(self.txuuid)self.assertTrue(txinfo.datetime)self.assertEqual(txinfo.user_eid,cnx.user.eid)self.assertEqual(txinfo.user().login,'admin')actions=txinfo.actions_list()self.assertEqual(len(actions),2)actions=txinfo.actions_list(public=False)self.assertEqual(len(actions),6)a1=actions[0]self.assertEqual(a1.action,'C')self.assertEqual(a1.eid,self.totoeid)self.assertEqual(a1.etype,'CWUser')self.assertEqual(a1.ertype,'CWUser')self.assertEqual(a1.changes,None)self.assertEqual(a1.public,True)self.assertEqual(a1.order,1)a4=actions[3]self.assertEqual(a4.action,'A')self.assertEqual(a4.rtype,'in_group')self.assertEqual(a4.ertype,'in_group')self.assertEqual(a4.eid_from,self.totoeid)self.assertEqual(a4.eid_to,self.toto(cnx).in_group[0].eid)self.assertEqual(a4.order,4)fori,rtypein((1,'owned_by'),(2,'owned_by')):a=actions[i]self.assertEqual(a.action,'A')self.assertEqual(a.eid_from,self.totoeid)self.assertEqual(a.rtype,rtype)self.assertEqual(a.order,i+1)self.assertEqual(set((actions[4].rtype,actions[5].rtype)),set(('in_state','created_by')))foriin(4,5):a=actions[i]self.assertEqual(a.action,'A')self.assertEqual(a.eid_from,self.totoeid)self.assertEqual(a.order,i+1)# test undoable_transactionstxs=cnx.undoable_transactions()self.assertEqual(len(txs),1)self.assertEqual(txs[0].uuid,self.txuuid)# test transaction_info / undoable_transactions securitywithself.new_access('anon').client_cnx()ascnx:self.assertRaises(NoSuchTransaction,cnx.transaction_info,self.txuuid)self.assertRaises(NoSuchTransaction,cnx.transaction_actions,self.txuuid)self.assertRaises(NoSuchTransaction,cnx.undo_transaction,self.txuuid)txs=cnx.undoable_transactions()self.assertEqual(len(txs),0)deftest_undoable_transactions(self):withself.admin_access.client_cnx()ascnx:toto=self.toto(cnx)e=cnx.create_entity('EmailAddress',address=u'toto@logilab.org',reverse_use_email=toto)txuuid1=cnx.commit()toto.cw_delete()txuuid2=cnx.commit()undoable_transactions=cnx.undoable_transactionstxs=undoable_transactions(action='D')self.assertEqual(len(txs),1,txs)self.assertEqual(txs[0].uuid,txuuid2)txs=undoable_transactions(action='C')self.assertEqual(len(txs),2,txs)self.assertEqual(txs[0].uuid,txuuid1)self.assertEqual(txs[1].uuid,self.txuuid)txs=undoable_transactions(eid=toto.eid)self.assertEqual(len(txs),3)self.assertEqual(txs[0].uuid,txuuid2)self.assertEqual(txs[1].uuid,txuuid1)self.assertEqual(txs[2].uuid,self.txuuid)txs=undoable_transactions(etype='CWUser')self.assertEqual(len(txs),2)txs=undoable_transactions(etype='CWUser',action='C')self.assertEqual(len(txs),1)self.assertEqual(txs[0].uuid,self.txuuid)txs=undoable_transactions(etype='EmailAddress',action='D')self.assertEqual(len(txs),0)txs=undoable_transactions(etype='EmailAddress',action='D',public=False)self.assertEqual(len(txs),1)self.assertEqual(txs[0].uuid,txuuid2)txs=undoable_transactions(eid=toto.eid,action='R',public=False)self.assertEqual(len(txs),1)self.assertEqual(txs[0].uuid,txuuid2)deftest_undo_deletion_base(self):withself.admin_access.client_cnx()ascnx:toto=self.toto(cnx)e=cnx.create_entity('EmailAddress',address=u'toto@logilab.org',reverse_use_email=toto)# entity with inlined relationp=cnx.create_entity('CWProperty',pkey=u'ui.default-text-format',value=u'text/rest',for_user=toto)cnx.commit()txs=cnx.undoable_transactions()self.assertEqual(len(txs),2)toto.cw_delete()txuuid=cnx.commit()actions=cnx.transaction_info(txuuid).actions_list()self.assertEqual(len(actions),1)toto.cw_clear_all_caches()e.cw_clear_all_caches()self.assertUndoTransaction(cnx,txuuid)undotxuuid=cnx.commit()self.assertEqual(undotxuuid,None)# undo not undoableself.assertTrue(cnx.execute('Any X WHERE X eid %(x)s',{'x':toto.eid}))self.assertTrue(cnx.execute('Any X WHERE X eid %(x)s',{'x':e.eid}))self.assertTrue(cnx.execute('Any X WHERE X has_text "toto@logilab"'))self.assertEqual(toto.cw_adapt_to('IWorkflowable').state,'activated')self.assertEqual(toto.cw_adapt_to('IEmailable').get_email(),'toto@logilab.org')self.assertEqual([(p.pkey,p.value)forpintoto.reverse_for_user],[('ui.default-text-format','text/rest')])self.assertEqual([g.nameforgintoto.in_group],['users'])self.assertEqual([et.nameforetintoto.related('is',entities=True)],['CWUser'])self.assertEqual([et.nameforetintoto.is_instance_of],['CWUser'])# undoing shouldn't be visble in undoable transaction, and the undone# transaction should be removedtxs=cnx.undoable_transactions()self.assertEqual(len(txs),2)self.assertRaises(NoSuchTransaction,cnx.transaction_info,txuuid)withself.admin_access.repo_cnx()ascnx:self.check_transaction_deleted(cnx,txuuid)# the final test: check we can login with the previously deleted userwithself.new_access('toto').client_cnx():passdeftest_undo_deletion_integrity_1(self):withself.admin_access.client_cnx()ascnx:# 'Personne fiche Card with' '??' cardinalityc=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis',fiche=c)cnx.commit()c.cw_delete()txuuid=cnx.commit()c2=cnx.create_entity('Card',title=u'hip',content=u'hip')p.cw_set(fiche=c2)cnx.commit()self.assertUndoTransaction(cnx,txuuid,["Can't restore object relation fiche to entity ""%s which is already linked using this relation."%p.eid])cnx.commit()p.cw_clear_all_caches()self.assertEqual(p.fiche[0].eid,c2.eid)# we restored the cardself.assertTrue(cnx.entity_from_eid(c.eid))deftest_undo_deletion_integrity_2(self):withself.admin_access.client_cnx()ascnx:# test validation error raised if we can't restore a required relationg=cnx.create_entity('CWGroup',name=u'staff')cnx.execute('DELETE U in_group G WHERE U eid %(x)s',{'x':self.totoeid})self.toto(cnx).cw_set(in_group=g)cnx.commit()self.toto(cnx).cw_delete()txuuid=cnx.commit()g.cw_delete()cnx.commit()self.assertUndoTransaction(cnx,txuuid,[u"Can't restore relation in_group, object entity ""%s doesn't exist anymore."%g.eid])withself.assertRaises(ValidationError)ascm:cnx.commit()cm.exception.translate(unicode)self.assertEqual(cm.exception.entity,self.totoeid)self.assertEqual(cm.exception.errors,{'in_group-subject':u'at least one relation in_group is ''required on CWUser (%s)'%self.totoeid})deftest_undo_creation_1(self):withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis',fiche=c)txuuid=cnx.commit()self.assertUndoTransaction(cnx,txuuid)cnx.commit()self.assertFalse(cnx.execute('Any X WHERE X eid %(x)s',{'x':c.eid}))self.assertFalse(cnx.execute('Any X WHERE X eid %(x)s',{'x':p.eid}))self.assertFalse(cnx.execute('Any X,Y WHERE X fiche Y'))withself.admin_access.repo_cnx()ascnx:foreidin(p.eid,c.eid):self.assertFalse(cnx.system_sql('SELECT * FROM entities WHERE eid=%s'%eid).fetchall())self.assertFalse(cnx.system_sql('SELECT 1 FROM owned_by_relation WHERE eid_from=%s'%eid).fetchall())# added by sql in hooks (except when using dataimport)self.assertFalse(cnx.system_sql('SELECT 1 FROM is_relation WHERE eid_from=%s'%eid).fetchall())self.assertFalse(cnx.system_sql('SELECT 1 FROM is_instance_of_relation WHERE eid_from=%s'%eid).fetchall())self.check_transaction_deleted(cnx,txuuid)deftest_undo_creation_integrity_1(self):withself.admin_access.client_cnx()ascnx:tutu=self.create_user(cnx,'tutu',commit=False)txuuid=cnx.commit()email=cnx.create_entity('EmailAddress',address=u'tutu@cubicweb.org')prop=cnx.create_entity('CWProperty',pkey=u'ui.default-text-format',value=u'text/html')tutu.cw_set(use_email=email,reverse_for_user=prop)cnx.commit()withself.assertRaises(ValidationError)ascm:cnx.undo_transaction(txuuid)self.assertEqual(cm.exception.entity,tutu.eid)self.assertEqual(cm.exception.errors,{None:'some later transaction(s) touch entity, undo them first'})deftest_undo_creation_integrity_2(self):withself.admin_access.client_cnx()ascnx:g=cnx.create_entity('CWGroup',name=u'staff')txuuid=cnx.commit()cnx.execute('DELETE U in_group G WHERE U eid %(x)s',{'x':self.totoeid})self.toto(cnx).cw_set(in_group=g)cnx.commit()withself.assertRaises(ValidationError)ascm:cnx.undo_transaction(txuuid)self.assertEqual(cm.exception.entity,g.eid)self.assertEqual(cm.exception.errors,{None:'some later transaction(s) touch entity, undo them first'})# self.assertEqual(errors,# [u"Can't restore relation in_group, object entity "# "%s doesn't exist anymore." % g.eid])# with self.assertRaises(ValidationError) as cm: cnx.commit()# self.assertEqual(cm.exception.entity, self.totoeid)# self.assertEqual(cm.exception.errors,# {'in_group-subject': u'at least one relation in_group is '# 'required on CWUser (%s)' % self.totoeid})# test implicit 'replacement' of an inlined relationdeftest_undo_inline_rel_remove_ok(self):"""Undo remove relation Personne (?) fiche (?) Card NB: processed by `_undo_r` as expected"""withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis',fiche=c)cnx.commit()p.cw_set(fiche=None)txuuid=cnx.commit()self.assertUndoTransaction(cnx,txuuid)cnx.commit()p.cw_clear_all_caches()self.assertEqual(p.fiche[0].eid,c.eid)deftest_undo_inline_rel_remove_ko(self):"""Restore an inlined relation to a deleted entity, with an error. NB: processed by `_undo_r` as expected"""withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis',fiche=c)cnx.commit()p.cw_set(fiche=None)txuuid=cnx.commit()c.cw_delete()cnx.commit()self.assertUndoTransaction(cnx,txuuid,["Can't restore relation fiche, object entity %d doesn't exist anymore."%c.eid])cnx.commit()p.cw_clear_all_caches()self.assertFalse(p.fiche)withself.admin_access.repo_cnx()ascnx:self.assertIsNone(cnx.system_sql('SELECT cw_fiche FROM cw_Personne WHERE cw_eid=%s'%p.eid).fetchall()[0][0])deftest_undo_inline_rel_add_ok(self):"""Undo add relation Personne (?) fiche (?) Card Caution processed by `_undo_u`, not `_undo_a` !"""withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis')cnx.commit()p.cw_set(fiche=c)txuuid=cnx.commit()self.assertUndoTransaction(cnx,txuuid)cnx.commit()p.cw_clear_all_caches()self.assertFalse(p.fiche)deftest_undo_inline_rel_delete_ko(self):withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')txuuid=cnx.commit()p=cnx.create_entity('Personne',nom=u'louis',fiche=c)cnx.commit()integrityerror=self.repo.sources_by_uri['system'].dbhelper.dbapi_module.IntegrityErrorwithself.assertRaises(integrityerror):cnx.undo_transaction(txuuid)deftest_undo_inline_rel_add_ko(self):"""Undo add relation Personne (?) fiche (?) Card Caution processed by `_undo_u`, not `_undo_a` !"""withself.admin_access.client_cnx()ascnx:c=cnx.create_entity('Card',title=u'hop',content=u'hop')p=cnx.create_entity('Personne',nom=u'louis')cnx.commit()p.cw_set(fiche=c)txuuid=cnx.commit()c.cw_delete()cnx.commit()self.assertUndoTransaction(cnx,txuuid)deftest_undo_inline_rel_replace_ok(self):"""Undo changing relation Personne (?) fiche (?) Card Caution processed by `_undo_u` """withself.admin_access.client_cnx()ascnx:c1=cnx.create_entity('Card',title=u'hop',content=u'hop')c2=cnx.create_entity('Card',title=u'hip',content=u'hip')p=cnx.create_entity('Personne',nom=u'louis',fiche=c1)cnx.commit()p.cw_set(fiche=c2)txuuid=cnx.commit()self.assertUndoTransaction(cnx,txuuid)cnx.commit()p.cw_clear_all_caches()self.assertEqual(p.fiche[0].eid,c1.eid)deftest_undo_inline_rel_replace_ko(self):"""Undo changing relation Personne (?) fiche (?) Card, with an error Caution processed by `_undo_u` """withself.admin_access.client_cnx()ascnx:c1=cnx.create_entity('Card',title=u'hop',content=u'hop')c2=cnx.create_entity('Card',title=u'hip',content=u'hip')p=cnx.create_entity('Personne',nom=u'louis',fiche=c1)cnx.commit()p.cw_set(fiche=c2)txuuid=cnx.commit()c1.cw_delete()cnx.commit()self.assertUndoTransaction(cnx,txuuid,["can't restore entity %s of type Personne, target of fiche (eid %s)"" does not exist any longer"%(p.eid,c1.eid)])cnx.commit()p.cw_clear_all_caches()self.assertFalse(p.fiche)deftest_undo_attr_update_ok(self):withself.admin_access.client_cnx()ascnx:p=cnx.create_entity('Personne',nom=u'toto')cnx.commit()p.cw_set(nom=u'titi')txuuid=cnx.commit()self.assertUndoTransaction(cnx,txuuid)p.cw_clear_all_caches()self.assertEqual(p.nom,u'toto')deftest_undo_attr_update_ko(self):withself.admin_access.client_cnx()ascnx:p=cnx.create_entity('Personne',nom=u'toto')cnx.commit()p.cw_set(nom=u'titi')txuuid=cnx.commit()p.cw_delete()cnx.commit()self.assertUndoTransaction(cnx,txuuid,[u"can't restore state of entity %s, it has been deleted inbetween"%p.eid])classUndoExceptionInUnicode(CubicWebTC):# problem occurs in string manipulation for python < 2.6deftest___unicode__method(self):u=_UndoException(u"voilà")self.assertIsInstance(unicode(u),unicode)if__name__=='__main__':fromlogilab.common.testlibimportunittest_mainunittest_main()