cubicweb/server/test/unittest_undo.py
changeset 11057 0b59724cb3f2
parent 10788 416840faf119
child 11200 8ddfed7a5981
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # -*- coding: utf-8 -*-
       
     2 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     3 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     4 #
       
     5 # This file is part of CubicWeb.
       
     6 #
       
     7 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     8 # terms of the GNU Lesser General Public License as published by the Free
       
     9 # Software Foundation, either version 2.1 of the License, or (at your option)
       
    10 # any later version.
       
    11 #
       
    12 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    14 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    15 # details.
       
    16 #
       
    17 # You should have received a copy of the GNU Lesser General Public License along
       
    18 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    19 
       
    20 from six import text_type
       
    21 
       
    22 from cubicweb import ValidationError
       
    23 from cubicweb.devtools.testlib import CubicWebTC
       
    24 import cubicweb.server.session
       
    25 from cubicweb.server.session import Connection as OldConnection
       
    26 
       
    27 from cubicweb.server.sources.native import UndoTransactionException, _UndoException
       
    28 
       
    29 from cubicweb.transaction import NoSuchTransaction
       
    30 
       
    31 class UndoableTransactionTC(CubicWebTC):
       
    32 
       
    33     def setup_database(self):
       
    34         with self.admin_access.repo_cnx() as cnx:
       
    35             self.totoeid = self.create_user(cnx, 'toto',
       
    36                                             password='toto',
       
    37                                             groups=('users',),
       
    38                                             commit=False).eid
       
    39             self.txuuid = cnx.commit()
       
    40 
       
    41     def toto(self, cnx):
       
    42         return cnx.entity_from_eid(self.totoeid)
       
    43 
       
    44     def setUp(self):
       
    45         class Connection(OldConnection):
       
    46             """Force undo feature to be turned on in all case"""
       
    47             undo_actions = property(lambda tx: True, lambda x, y:None)
       
    48         cubicweb.server.session.Connection = Connection
       
    49         super(UndoableTransactionTC, self).setUp()
       
    50 
       
    51     def tearDown(self):
       
    52         cubicweb.server.session.Connection = OldConnection
       
    53         super(UndoableTransactionTC, self).tearDown()
       
    54 
       
    55     def check_transaction_deleted(self, cnx, txuuid):
       
    56         # also check transaction actions have been properly deleted
       
    57         cu = cnx.system_sql(
       
    58             "SELECT * from tx_entity_actions WHERE tx_uuid='%s'" % txuuid)
       
    59         self.assertFalse(cu.fetchall())
       
    60         cu = cnx.system_sql(
       
    61             "SELECT * from tx_relation_actions WHERE tx_uuid='%s'" % txuuid)
       
    62         self.assertFalse(cu.fetchall())
       
    63 
       
    64     def assertUndoTransaction(self, cnx, txuuid, expected_errors=None):
       
    65         if expected_errors is None :
       
    66             expected_errors = []
       
    67         try:
       
    68             cnx.undo_transaction(txuuid)
       
    69         except UndoTransactionException as exn:
       
    70             errors = exn.errors
       
    71         else:
       
    72             errors = []
       
    73         self.assertEqual(errors, expected_errors)
       
    74 
       
    75     def test_undo_api(self):
       
    76         self.assertTrue(self.txuuid)
       
    77         # test transaction api
       
    78         with self.admin_access.client_cnx() as cnx:
       
    79             tx_actions = cnx.transaction_actions(self.txuuid)
       
    80             self.assertEqual(len(tx_actions), 2, tx_actions)
       
    81             self.assertRaises(NoSuchTransaction,
       
    82                               cnx.transaction_info, 'hop')
       
    83             self.assertRaises(NoSuchTransaction,
       
    84                               cnx.transaction_actions, 'hop')
       
    85             self.assertRaises(NoSuchTransaction,
       
    86                               cnx.undo_transaction, 'hop')
       
    87             txinfo = cnx.transaction_info(self.txuuid)
       
    88             self.assertTrue(txinfo.datetime)
       
    89             self.assertEqual(txinfo.user_eid, cnx.user.eid)
       
    90             self.assertEqual(txinfo.user().login, 'admin')
       
    91             actions = txinfo.actions_list()
       
    92             self.assertEqual(len(actions), 2)
       
    93             actions = txinfo.actions_list(public=False)
       
    94             self.assertEqual(len(actions), 6)
       
    95             a1 = actions[0]
       
    96             self.assertEqual(a1.action, 'C')
       
    97             self.assertEqual(a1.eid, self.totoeid)
       
    98             self.assertEqual(a1.etype,'CWUser')
       
    99             self.assertEqual(a1.ertype, 'CWUser')
       
   100             self.assertEqual(a1.changes, None)
       
   101             self.assertEqual(a1.public, True)
       
   102             self.assertEqual(a1.order, 1)
       
   103             a4 = actions[3]
       
   104             self.assertEqual(a4.action, 'A')
       
   105             self.assertEqual(a4.rtype, 'in_group')
       
   106             self.assertEqual(a4.ertype, 'in_group')
       
   107             self.assertEqual(a4.eid_from, self.totoeid)
       
   108             self.assertEqual(a4.eid_to, self.toto(cnx).in_group[0].eid)
       
   109             self.assertEqual(a4.order, 4)
       
   110             for i, rtype in ((1, 'owned_by'), (2, 'owned_by')):
       
   111                 a = actions[i]
       
   112                 self.assertEqual(a.action, 'A')
       
   113                 self.assertEqual(a.eid_from, self.totoeid)
       
   114                 self.assertEqual(a.rtype, rtype)
       
   115                 self.assertEqual(a.order, i+1)
       
   116             self.assertEqual(set((actions[4].rtype, actions[5].rtype)),
       
   117                              set(('in_state', 'created_by')))
       
   118             for i in (4, 5):
       
   119                 a = actions[i]
       
   120                 self.assertEqual(a.action, 'A')
       
   121                 self.assertEqual(a.eid_from, self.totoeid)
       
   122                 self.assertEqual(a.order, i+1)
       
   123 
       
   124             # test undoable_transactions
       
   125             txs = cnx.undoable_transactions()
       
   126             self.assertEqual(len(txs), 1)
       
   127             self.assertEqual(txs[0].uuid, self.txuuid)
       
   128             # test transaction_info / undoable_transactions security
       
   129         with self.new_access('anon').client_cnx() as cnx:
       
   130             self.assertRaises(NoSuchTransaction,
       
   131                               cnx.transaction_info, self.txuuid)
       
   132             self.assertRaises(NoSuchTransaction,
       
   133                               cnx.transaction_actions, self.txuuid)
       
   134             self.assertRaises(NoSuchTransaction,
       
   135                               cnx.undo_transaction, self.txuuid)
       
   136             txs = cnx.undoable_transactions()
       
   137             self.assertEqual(len(txs), 0)
       
   138 
       
   139     def test_undoable_transactions(self):
       
   140         with self.admin_access.client_cnx() as cnx:
       
   141             toto = self.toto(cnx)
       
   142             e = cnx.create_entity('EmailAddress',
       
   143                                   address=u'toto@logilab.org',
       
   144                                   reverse_use_email=toto)
       
   145             txuuid1 = cnx.commit()
       
   146             toto.cw_delete()
       
   147             txuuid2 = cnx.commit()
       
   148             undoable_transactions = cnx.undoable_transactions
       
   149             txs = undoable_transactions(action='D')
       
   150             self.assertEqual(len(txs), 1, txs)
       
   151             self.assertEqual(txs[0].uuid, txuuid2)
       
   152             txs = undoable_transactions(action='C')
       
   153             self.assertEqual(len(txs), 2, txs)
       
   154             self.assertEqual(txs[0].uuid, txuuid1)
       
   155             self.assertEqual(txs[1].uuid, self.txuuid)
       
   156             txs = undoable_transactions(eid=toto.eid)
       
   157             self.assertEqual(len(txs), 3)
       
   158             self.assertEqual(txs[0].uuid, txuuid2)
       
   159             self.assertEqual(txs[1].uuid, txuuid1)
       
   160             self.assertEqual(txs[2].uuid, self.txuuid)
       
   161             txs = undoable_transactions(etype='CWUser')
       
   162             self.assertEqual(len(txs), 2)
       
   163             txs = undoable_transactions(etype='CWUser', action='C')
       
   164             self.assertEqual(len(txs), 1)
       
   165             self.assertEqual(txs[0].uuid, self.txuuid)
       
   166             txs = undoable_transactions(etype='EmailAddress', action='D')
       
   167             self.assertEqual(len(txs), 0)
       
   168             txs = undoable_transactions(etype='EmailAddress', action='D',
       
   169                                         public=False)
       
   170             self.assertEqual(len(txs), 1)
       
   171             self.assertEqual(txs[0].uuid, txuuid2)
       
   172             txs = undoable_transactions(eid=toto.eid, action='R', public=False)
       
   173             self.assertEqual(len(txs), 1)
       
   174             self.assertEqual(txs[0].uuid, txuuid2)
       
   175 
       
   176     def test_undo_deletion_base(self):
       
   177         with self.admin_access.client_cnx() as cnx:
       
   178             toto = self.toto(cnx)
       
   179             e = cnx.create_entity('EmailAddress',
       
   180                                   address=u'toto@logilab.org',
       
   181                                   reverse_use_email=toto)
       
   182             # entity with inlined relation
       
   183             p = cnx.create_entity('CWProperty',
       
   184                                   pkey=u'ui.default-text-format',
       
   185                                   value=u'text/rest',
       
   186                                   for_user=toto)
       
   187             cnx.commit()
       
   188             txs = cnx.undoable_transactions()
       
   189             self.assertEqual(len(txs), 2)
       
   190             toto.cw_delete()
       
   191             txuuid = cnx.commit()
       
   192             actions = cnx.transaction_info(txuuid).actions_list()
       
   193             self.assertEqual(len(actions), 1)
       
   194             toto.cw_clear_all_caches()
       
   195             e.cw_clear_all_caches()
       
   196             self.assertUndoTransaction(cnx, txuuid)
       
   197             undotxuuid = cnx.commit()
       
   198             self.assertEqual(undotxuuid, None) # undo not undoable
       
   199             self.assertTrue(cnx.execute('Any X WHERE X eid %(x)s', {'x': toto.eid}))
       
   200             self.assertTrue(cnx.execute('Any X WHERE X eid %(x)s', {'x': e.eid}))
       
   201             self.assertTrue(cnx.execute('Any X WHERE X has_text "toto@logilab"'))
       
   202             self.assertEqual(toto.cw_adapt_to('IWorkflowable').state, 'activated')
       
   203             self.assertEqual(toto.cw_adapt_to('IEmailable').get_email(), 'toto@logilab.org')
       
   204             self.assertEqual([(p.pkey, p.value) for p in toto.reverse_for_user],
       
   205                               [('ui.default-text-format', 'text/rest')])
       
   206             self.assertEqual([g.name for g in toto.in_group],
       
   207                               ['users'])
       
   208             self.assertEqual([et.name for et in toto.related('is', entities=True)],
       
   209                               ['CWUser'])
       
   210             self.assertEqual([et.name for et in toto.is_instance_of],
       
   211                               ['CWUser'])
       
   212             # undoing shouldn't be visble in undoable transaction, and the undone
       
   213             # transaction should be removed
       
   214             txs = cnx.undoable_transactions()
       
   215             self.assertEqual(len(txs), 2)
       
   216             self.assertRaises(NoSuchTransaction,
       
   217                               cnx.transaction_info, txuuid)
       
   218         with self.admin_access.repo_cnx() as cnx:
       
   219             self.check_transaction_deleted(cnx, txuuid)
       
   220             # the final test: check we can login with the previously deleted user
       
   221         with self.new_access('toto').client_cnx():
       
   222             pass
       
   223 
       
   224     def test_undo_deletion_integrity_1(self):
       
   225         with self.admin_access.client_cnx() as cnx:
       
   226             # 'Personne fiche Card with' '??' cardinality
       
   227             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   228             p = cnx.create_entity('Personne', nom=u'louis', fiche=c)
       
   229             cnx.commit()
       
   230             c.cw_delete()
       
   231             txuuid = cnx.commit()
       
   232             c2 = cnx.create_entity('Card', title=u'hip', content=u'hip')
       
   233             p.cw_set(fiche=c2)
       
   234             cnx.commit()
       
   235             self.assertUndoTransaction(cnx, txuuid, [
       
   236                 "Can't restore object relation fiche to entity "
       
   237                 "%s which is already linked using this relation." % p.eid])
       
   238             cnx.commit()
       
   239             p.cw_clear_all_caches()
       
   240             self.assertEqual(p.fiche[0].eid, c2.eid)
       
   241             # we restored the card
       
   242             self.assertTrue(cnx.entity_from_eid(c.eid))
       
   243 
       
   244     def test_undo_deletion_integrity_2(self):
       
   245         with self.admin_access.client_cnx() as cnx:
       
   246             # test validation error raised if we can't restore a required relation
       
   247             g = cnx.create_entity('CWGroup', name=u'staff')
       
   248             cnx.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.totoeid})
       
   249             self.toto(cnx).cw_set(in_group=g)
       
   250             cnx.commit()
       
   251             self.toto(cnx).cw_delete()
       
   252             txuuid = cnx.commit()
       
   253             g.cw_delete()
       
   254             cnx.commit()
       
   255             self.assertUndoTransaction(cnx, txuuid, [
       
   256                 u"Can't restore relation in_group, object entity "
       
   257                 "%s doesn't exist anymore." % g.eid])
       
   258             with self.assertRaises(ValidationError) as cm:
       
   259                 cnx.commit()
       
   260             cm.exception.translate(text_type)
       
   261             self.assertEqual(cm.exception.entity, self.totoeid)
       
   262             self.assertEqual(cm.exception.errors,
       
   263                               {'in_group-subject': u'at least one relation in_group is '
       
   264                                'required on CWUser (%s)' % self.totoeid})
       
   265 
       
   266     def test_undo_creation_1(self):
       
   267         with self.admin_access.client_cnx() as cnx:
       
   268             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   269             p = cnx.create_entity('Personne', nom=u'louis', fiche=c)
       
   270             txuuid = cnx.commit()
       
   271             self.assertUndoTransaction(cnx, txuuid)
       
   272             cnx.commit()
       
   273             self.assertFalse(cnx.execute('Any X WHERE X eid %(x)s', {'x': c.eid}))
       
   274             self.assertFalse(cnx.execute('Any X WHERE X eid %(x)s', {'x': p.eid}))
       
   275             self.assertFalse(cnx.execute('Any X,Y WHERE X fiche Y'))
       
   276         with self.admin_access.repo_cnx() as cnx:
       
   277             for eid in (p.eid, c.eid):
       
   278                 self.assertFalse(cnx.system_sql(
       
   279                     'SELECT * FROM entities WHERE eid=%s' % eid).fetchall())
       
   280                 self.assertFalse(cnx.system_sql(
       
   281                     'SELECT 1 FROM owned_by_relation WHERE eid_from=%s' % eid).fetchall())
       
   282                 # added by sql in hooks (except when using dataimport)
       
   283                 self.assertFalse(cnx.system_sql(
       
   284                     'SELECT 1 FROM is_relation WHERE eid_from=%s' % eid).fetchall())
       
   285                 self.assertFalse(cnx.system_sql(
       
   286                     'SELECT 1 FROM is_instance_of_relation WHERE eid_from=%s' % eid).fetchall())
       
   287             self.check_transaction_deleted(cnx, txuuid)
       
   288 
       
   289     def test_undo_creation_integrity_1(self):
       
   290         with self.admin_access.client_cnx() as cnx:
       
   291             tutu = self.create_user(cnx, 'tutu', commit=False)
       
   292             txuuid = cnx.commit()
       
   293             email = cnx.create_entity('EmailAddress', address=u'tutu@cubicweb.org')
       
   294             prop = cnx.create_entity('CWProperty', pkey=u'ui.default-text-format',
       
   295                                      value=u'text/html')
       
   296             tutu.cw_set(use_email=email, reverse_for_user=prop)
       
   297             cnx.commit()
       
   298             with self.assertRaises(ValidationError) as cm:
       
   299                 cnx.undo_transaction(txuuid)
       
   300             self.assertEqual(cm.exception.entity, tutu.eid)
       
   301             self.assertEqual(cm.exception.errors,
       
   302                              {None: 'some later transaction(s) touch entity, undo them first'})
       
   303 
       
   304     def test_undo_creation_integrity_2(self):
       
   305         with self.admin_access.client_cnx() as cnx:
       
   306             g = cnx.create_entity('CWGroup', name=u'staff')
       
   307             txuuid = cnx.commit()
       
   308             cnx.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.totoeid})
       
   309             self.toto(cnx).cw_set(in_group=g)
       
   310             cnx.commit()
       
   311             with self.assertRaises(ValidationError) as cm:
       
   312                 cnx.undo_transaction(txuuid)
       
   313             self.assertEqual(cm.exception.entity, g.eid)
       
   314             self.assertEqual(cm.exception.errors,
       
   315                              {None: 'some later transaction(s) touch entity, undo them first'})
       
   316         # self.assertEqual(errors,
       
   317         #                   [u"Can't restore relation in_group, object entity "
       
   318         #                   "%s doesn't exist anymore." % g.eid])
       
   319         # with self.assertRaises(ValidationError) as cm: cnx.commit()
       
   320         # self.assertEqual(cm.exception.entity, self.totoeid)
       
   321         # self.assertEqual(cm.exception.errors,
       
   322         #                   {'in_group-subject': u'at least one relation in_group is '
       
   323         #                    'required on CWUser (%s)' % self.totoeid})
       
   324 
       
   325     # test implicit 'replacement' of an inlined relation
       
   326 
       
   327     def test_undo_inline_rel_remove_ok(self):
       
   328         """Undo remove relation  Personne (?) fiche (?) Card
       
   329 
       
   330         NB: processed by `_undo_r` as expected"""
       
   331         with self.admin_access.client_cnx() as cnx:
       
   332             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   333             p = cnx.create_entity('Personne', nom=u'louis', fiche=c)
       
   334             cnx.commit()
       
   335             p.cw_set(fiche=None)
       
   336             txuuid = cnx.commit()
       
   337             self.assertUndoTransaction(cnx, txuuid)
       
   338             cnx.commit()
       
   339             p.cw_clear_all_caches()
       
   340             self.assertEqual(p.fiche[0].eid, c.eid)
       
   341 
       
   342     def test_undo_inline_rel_remove_ko(self):
       
   343         """Restore an inlined relation to a deleted entity, with an error.
       
   344 
       
   345         NB: processed by `_undo_r` as expected"""
       
   346         with self.admin_access.client_cnx() as cnx:
       
   347             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   348             p = cnx.create_entity('Personne', nom=u'louis', fiche=c)
       
   349             cnx.commit()
       
   350             p.cw_set(fiche=None)
       
   351             txuuid = cnx.commit()
       
   352             c.cw_delete()
       
   353             cnx.commit()
       
   354             self.assertUndoTransaction(cnx, txuuid, [
       
   355                 "Can't restore relation fiche, object entity %d doesn't exist anymore." % c.eid])
       
   356             cnx.commit()
       
   357             p.cw_clear_all_caches()
       
   358             self.assertFalse(p.fiche)
       
   359         with self.admin_access.repo_cnx() as cnx:
       
   360             self.assertIsNone(cnx.system_sql(
       
   361                 'SELECT cw_fiche FROM cw_Personne WHERE cw_eid=%s' % p.eid).fetchall()[0][0])
       
   362 
       
   363     def test_undo_inline_rel_add_ok(self):
       
   364         """Undo add relation  Personne (?) fiche (?) Card
       
   365 
       
   366         Caution processed by `_undo_u`, not `_undo_a` !"""
       
   367         with self.admin_access.client_cnx() as cnx:
       
   368             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   369             p = cnx.create_entity('Personne', nom=u'louis')
       
   370             cnx.commit()
       
   371             p.cw_set(fiche=c)
       
   372             txuuid = cnx.commit()
       
   373             self.assertUndoTransaction(cnx, txuuid)
       
   374             cnx.commit()
       
   375             p.cw_clear_all_caches()
       
   376             self.assertFalse(p.fiche)
       
   377 
       
   378     def test_undo_inline_rel_delete_ko(self):
       
   379         with self.admin_access.client_cnx() as cnx:
       
   380             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   381             txuuid = cnx.commit()
       
   382             p = cnx.create_entity('Personne', nom=u'louis', fiche=c)
       
   383             cnx.commit()
       
   384             integrityerror = self.repo.sources_by_uri['system'].dbhelper.dbapi_module.IntegrityError
       
   385             with self.assertRaises(integrityerror):
       
   386                 cnx.undo_transaction(txuuid)
       
   387 
       
   388 
       
   389     def test_undo_inline_rel_add_ko(self):
       
   390         """Undo add relation  Personne (?) fiche (?) Card
       
   391 
       
   392         Caution processed by `_undo_u`, not `_undo_a` !"""
       
   393         with self.admin_access.client_cnx() as cnx:
       
   394             c = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   395             p = cnx.create_entity('Personne', nom=u'louis')
       
   396             cnx.commit()
       
   397             p.cw_set(fiche=c)
       
   398             txuuid = cnx.commit()
       
   399             c.cw_delete()
       
   400             cnx.commit()
       
   401             self.assertUndoTransaction(cnx, txuuid)
       
   402 
       
   403     def test_undo_inline_rel_replace_ok(self):
       
   404         """Undo changing relation  Personne (?) fiche (?) Card
       
   405 
       
   406         Caution processed by `_undo_u` """
       
   407         with self.admin_access.client_cnx() as cnx:
       
   408             c1 = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   409             c2 = cnx.create_entity('Card', title=u'hip', content=u'hip')
       
   410             p = cnx.create_entity('Personne', nom=u'louis', fiche=c1)
       
   411             cnx.commit()
       
   412             p.cw_set(fiche=c2)
       
   413             txuuid = cnx.commit()
       
   414             self.assertUndoTransaction(cnx, txuuid)
       
   415             cnx.commit()
       
   416             p.cw_clear_all_caches()
       
   417             self.assertEqual(p.fiche[0].eid, c1.eid)
       
   418 
       
   419     def test_undo_inline_rel_replace_ko(self):
       
   420         """Undo changing relation  Personne (?) fiche (?) Card, with an error
       
   421 
       
   422         Caution processed by `_undo_u` """
       
   423         with self.admin_access.client_cnx() as cnx:
       
   424             c1 = cnx.create_entity('Card', title=u'hop', content=u'hop')
       
   425             c2 = cnx.create_entity('Card', title=u'hip', content=u'hip')
       
   426             p = cnx.create_entity('Personne', nom=u'louis', fiche=c1)
       
   427             cnx.commit()
       
   428             p.cw_set(fiche=c2)
       
   429             txuuid = cnx.commit()
       
   430             c1.cw_delete()
       
   431             cnx.commit()
       
   432             self.assertUndoTransaction(cnx, txuuid, [
       
   433                 "can't restore entity %s of type Personne, target of fiche (eid %s)"
       
   434                 " does not exist any longer" % (p.eid, c1.eid)])
       
   435             cnx.commit()
       
   436             p.cw_clear_all_caches()
       
   437             self.assertFalse(p.fiche)
       
   438 
       
   439     def test_undo_attr_update_ok(self):
       
   440         with self.admin_access.client_cnx() as cnx:
       
   441             p = cnx.create_entity('Personne', nom=u'toto')
       
   442             cnx.commit()
       
   443             p.cw_set(nom=u'titi')
       
   444             txuuid = cnx.commit()
       
   445             self.assertUndoTransaction(cnx, txuuid)
       
   446             p.cw_clear_all_caches()
       
   447             self.assertEqual(p.nom, u'toto')
       
   448 
       
   449     def test_undo_attr_update_ko(self):
       
   450         with self.admin_access.client_cnx() as cnx:
       
   451             p = cnx.create_entity('Personne', nom=u'toto')
       
   452             cnx.commit()
       
   453             p.cw_set(nom=u'titi')
       
   454             txuuid = cnx.commit()
       
   455             p.cw_delete()
       
   456             cnx.commit()
       
   457             self.assertUndoTransaction(cnx, txuuid, [
       
   458                 u"can't restore state of entity %s, it has been deleted inbetween" % p.eid])
       
   459 
       
   460 
       
   461 class UndoExceptionInUnicode(CubicWebTC):
       
   462 
       
   463     # problem occurs in string manipulation for python < 2.6
       
   464     def test___unicode__method(self):
       
   465         u = _UndoException(u"voilĂ ")
       
   466         self.assertIsInstance(text_type(u), text_type)
       
   467 
       
   468 
       
   469 if __name__ == '__main__':
       
   470     from logilab.common.testlib import unittest_main
       
   471     unittest_main()