|
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() |