cubicweb/web/test/unittest_views_basecontrollers.py
changeset 11057 0b59724cb3f2
parent 10968 9c6c3e68422e
child 11200 8ddfed7a5981
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """cubicweb.web.views.basecontrollers unit tests"""
       
    19 
       
    20 from six import text_type
       
    21 from six.moves.urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs
       
    22 
       
    23 import lxml
       
    24 
       
    25 from logilab.common.testlib import unittest_main
       
    26 from logilab.common.decorators import monkeypatch
       
    27 
       
    28 from cubicweb import Binary, NoSelectableObject, ValidationError
       
    29 from cubicweb.schema import RRQLExpression
       
    30 from cubicweb.devtools.testlib import CubicWebTC
       
    31 from cubicweb.devtools.webtest import CubicWebTestTC
       
    32 from cubicweb.utils import json_dumps
       
    33 from cubicweb.uilib import rql_for_eid
       
    34 from cubicweb.web import Redirect, RemoteCallFailed
       
    35 import cubicweb.server.session
       
    36 from cubicweb.server.session import Connection as OldConnection
       
    37 from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
       
    38 from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize
       
    39 from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
       
    40 import cubicweb.transaction as tx
       
    41 from cubicweb.server.hook import Hook, Operation
       
    42 from cubicweb.predicates import is_instance
       
    43 
       
    44 
       
    45 class ViewControllerTC(CubicWebTestTC):
       
    46     def test_view_ctrl_with_valid_cache_headers(self):
       
    47         resp = self.webapp.get('/manage')
       
    48         self.assertEqual(resp.etag, 'manage/guests')
       
    49         self.assertEqual(resp.status_code, 200)
       
    50         cache_headers = {'if-modified-since': resp.headers['Last-Modified'],
       
    51                          'if-none-match': resp.etag}
       
    52         resp = self.webapp.get('/manage', headers=cache_headers)
       
    53         self.assertEqual(resp.status_code, 304)
       
    54         self.assertEqual(len(resp.body), 0)
       
    55 
       
    56 
       
    57 def req_form(user):
       
    58     return {'eid': [str(user.eid)],
       
    59             '_cw_entity_fields:%s' % user.eid: '_cw_generic_field',
       
    60             '__type:%s' % user.eid: user.__regid__
       
    61             }
       
    62 
       
    63 
       
    64 class EditControllerTC(CubicWebTC):
       
    65 
       
    66     def setUp(self):
       
    67         CubicWebTC.setUp(self)
       
    68         self.assertIn('users', self.schema.eschema('CWGroup').get_groups('read'))
       
    69 
       
    70     def tearDown(self):
       
    71         CubicWebTC.tearDown(self)
       
    72         self.assertIn('users', self.schema.eschema('CWGroup').get_groups('read'))
       
    73 
       
    74     def test_noparam_edit(self):
       
    75         """check behaviour of this controller without any form parameter
       
    76         """
       
    77         with self.admin_access.web_request() as req:
       
    78             with self.assertRaises(ValidationError) as cm:
       
    79                 self.ctrl_publish(req)
       
    80             self.assertEqual(cm.exception.errors, {None: u'no selected entities'})
       
    81 
       
    82     def test_validation_unique(self):
       
    83         """test creation of two linked entities
       
    84         """
       
    85         with self.admin_access.web_request() as req:
       
    86             req.form = {'eid': 'X', '__type:X': 'CWUser',
       
    87                         '_cw_entity_fields:X': 'login-subject,upassword-subject',
       
    88                         'login-subject:X': u'admin',
       
    89                         'upassword-subject:X': u'toto',
       
    90                         'upassword-subject-confirm:X': u'toto',
       
    91                     }
       
    92             with self.assertRaises(ValidationError) as cm:
       
    93                 self.ctrl_publish(req)
       
    94                 cm.exception.translate(text_type)
       
    95                 self.assertEqual({'login-subject': 'the value "admin" is already used, use another one'},
       
    96                                  cm.exception.errors)
       
    97 
       
    98     def test_simultaneous_edition_only_one_commit(self):
       
    99         """ Allow two simultaneous edit view of the same entity as long as only one commits
       
   100         """
       
   101         with self.admin_access.web_request() as req:
       
   102             e = req.create_entity('BlogEntry', title=u'cubicweb.org', content=u"hop")
       
   103             expected_path = e.rest_path()
       
   104             req.cnx.commit()
       
   105             form = self.vreg['views'].select('edition', req, rset=e.as_rset(), row=0)
       
   106             html_form = lxml.html.fromstring(form.render(w=None, action='edit')).forms[0]
       
   107 
       
   108         with self.admin_access.web_request() as req2:
       
   109             form2 = self.vreg['views'].select('edition', req, rset=e.as_rset(), row=0)
       
   110 
       
   111         with self.admin_access.web_request(**dict(html_form.form_values())) as req:
       
   112             path, args = self.expect_redirect_handle_request(req, path='edit')
       
   113             self.assertEqual(path, expected_path)
       
   114 
       
   115     def test_simultaneous_edition_refuse_second_commit(self):
       
   116         """ Disallow committing changes to an entity edited in between """
       
   117         with self.admin_access.web_request() as req:
       
   118             e = req.create_entity('BlogEntry', title=u'cubicweb.org', content=u"hop")
       
   119             eid = e.eid
       
   120             req.cnx.commit()
       
   121             form = self.vreg['views'].select('edition', req, rset=e.as_rset(), row=0)
       
   122             html_form = lxml.html.fromstring(form.render(w=None, action='edit')).forms[0]
       
   123 
       
   124         with self.admin_access.web_request() as req2:
       
   125             e = req2.entity_from_eid(eid)
       
   126             e.cw_set(content = u"hip")
       
   127             req2.cnx.commit()
       
   128 
       
   129         form_field_name = "content-subject:%d" % eid
       
   130         form_values = dict(html_form.form_values())
       
   131         assert form_field_name in form_values
       
   132         form_values[form_field_name] = u'yep'
       
   133         with self.admin_access.web_request(**form_values) as req:
       
   134             with self.assertRaises(ValidationError) as cm:
       
   135                 self.ctrl_publish(req)
       
   136             reported_eid, dict_info = cm.exception.args
       
   137             self.assertEqual(reported_eid, eid)
       
   138             self.assertIn(None, dict_info)
       
   139             self.assertIn("has changed since you started to edit it.", dict_info[None])
       
   140 
       
   141     def test_user_editing_itself(self):
       
   142         """checking that a manager user can edit itself
       
   143         """
       
   144         with self.admin_access.web_request() as req:
       
   145             user = req.user
       
   146             groupeids = [eid for eid, in req.execute('CWGroup G WHERE G name '
       
   147                                                      'in ("managers", "users")')]
       
   148             groups = [text_type(eid) for eid in groupeids]
       
   149             eid = text_type(user.eid)
       
   150             req.form = {
       
   151                 'eid': eid, '__type:'+eid: 'CWUser',
       
   152                 '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
       
   153                 'login-subject:'+eid:     text_type(user.login),
       
   154                 'surname-subject:'+eid: u'Th\xe9nault',
       
   155                 'firstname-subject:'+eid:   u'Sylvain',
       
   156                 'in_group-subject:'+eid:  groups,
       
   157                 }
       
   158             self.expect_redirect_handle_request(req, 'edit')
       
   159             e = req.execute('Any X WHERE X eid %(x)s',
       
   160                             {'x': user.eid}).get_entity(0, 0)
       
   161             self.assertEqual(e.firstname, u'Sylvain')
       
   162             self.assertEqual(e.surname, u'Th\xe9nault')
       
   163             self.assertEqual(e.login, user.login)
       
   164             self.assertEqual([g.eid for g in e.in_group], groupeids)
       
   165 
       
   166     def test_user_can_change_its_password(self):
       
   167         with self.admin_access.repo_cnx() as cnx:
       
   168             self.create_user(cnx, u'user')
       
   169             cnx.commit()
       
   170         with self.new_access(u'user').web_request() as req:
       
   171             eid = text_type(req.user.eid)
       
   172             req.form = {
       
   173                 'eid': eid, '__maineid' : eid,
       
   174                 '__type:'+eid: 'CWUser',
       
   175                 '_cw_entity_fields:'+eid: 'upassword-subject',
       
   176                 'upassword-subject:'+eid: 'tournicoton',
       
   177                 'upassword-subject-confirm:'+eid: 'tournicoton',
       
   178                 }
       
   179             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   180             req.cnx.commit() # commit to check we don't get late validation error for instance
       
   181             self.assertEqual(path, 'cwuser/user')
       
   182             self.assertNotIn('vid', params)
       
   183 
       
   184     def test_user_editing_itself_no_relation(self):
       
   185         """checking we can edit an entity without specifying some required
       
   186         relations (meaning no changes)
       
   187         """
       
   188         with self.admin_access.web_request() as req:
       
   189             user = req.user
       
   190             groupeids = [g.eid for g in user.in_group]
       
   191             eid = text_type(user.eid)
       
   192             req.form = {
       
   193                 'eid':       eid,
       
   194                 '__type:'+eid:    'CWUser',
       
   195                 '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
       
   196                 'login-subject:'+eid:     text_type(user.login),
       
   197                 'firstname-subject:'+eid: u'Th\xe9nault',
       
   198                 'surname-subject:'+eid:   u'Sylvain',
       
   199                 }
       
   200             self.expect_redirect_handle_request(req, 'edit')
       
   201             e = req.execute('Any X WHERE X eid %(x)s',
       
   202                             {'x': user.eid}).get_entity(0, 0)
       
   203             self.assertEqual(e.login, user.login)
       
   204             self.assertEqual(e.firstname, u'Th\xe9nault')
       
   205             self.assertEqual(e.surname, u'Sylvain')
       
   206             self.assertEqual([g.eid for g in e.in_group], groupeids)
       
   207             self.assertEqual(e.cw_adapt_to('IWorkflowable').state, 'activated')
       
   208 
       
   209 
       
   210     def test_create_multiple_linked(self):
       
   211         with self.admin_access.web_request() as req:
       
   212             gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
       
   213             req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
       
   214                         '__type:X': 'CWUser',
       
   215                         '_cw_entity_fields:X': 'login-subject,upassword-subject,surname-subject,in_group-subject',
       
   216                         'login-subject:X': u'adim',
       
   217                         'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
       
   218                         'surname-subject:X': u'Di Mascio',
       
   219                         'in_group-subject:X': text_type(gueid),
       
   220 
       
   221                         '__type:Y': 'EmailAddress',
       
   222                         '_cw_entity_fields:Y': 'address-subject,use_email-object',
       
   223                         'address-subject:Y': u'dima@logilab.fr',
       
   224                         'use_email-object:Y': 'X',
       
   225                         }
       
   226             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   227             # should be redirected on the created person
       
   228             self.assertEqual(path, 'cwuser/adim')
       
   229             e = req.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
       
   230             self.assertEqual(e.surname, 'Di Mascio')
       
   231             email = e.use_email[0]
       
   232             self.assertEqual(email.address, 'dima@logilab.fr')
       
   233 
       
   234     def test_create_mandatory_inlined(self):
       
   235         with self.admin_access.web_request() as req:
       
   236             req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
       
   237 
       
   238                         '__type:X': 'Salesterm',
       
   239                         '_cw_entity_fields:X': '',
       
   240 
       
   241                         '__type:Y': 'File',
       
   242                         '_cw_entity_fields:Y': 'data-subject,described_by_test-object',
       
   243                         'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
       
   244                         'described_by_test-object:Y': 'X',
       
   245                         }
       
   246             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   247             self.assertTrue(path.startswith('salesterm/'), path)
       
   248             eid = path.split('/')[1]
       
   249             salesterm = req.entity_from_eid(eid)
       
   250             # The NOT NULL constraint of mandatory relation implies that the File
       
   251             # must be created before the Salesterm, otherwise Salesterm insertion
       
   252             # will fail.
       
   253             # NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the
       
   254             # insertion does not fail and we have to check dumbly that File is
       
   255             # created before.
       
   256             self.assertGreater(salesterm.eid, salesterm.described_by_test[0].eid)
       
   257 
       
   258     def test_create_mandatory_inlined2(self):
       
   259         with self.admin_access.web_request() as req:
       
   260             req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
       
   261 
       
   262                         '__type:X': 'Salesterm',
       
   263                         '_cw_entity_fields:X': 'described_by_test-subject',
       
   264                         'described_by_test-subject:X': 'Y',
       
   265 
       
   266                         '__type:Y': 'File',
       
   267                         '_cw_entity_fields:Y': 'data-subject',
       
   268                         'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
       
   269                         }
       
   270             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   271             self.assertTrue(path.startswith('salesterm/'), path)
       
   272             eid = path.split('/')[1]
       
   273             salesterm = req.entity_from_eid(eid)
       
   274             # The NOT NULL constraint of mandatory relation implies that the File
       
   275             # must be created before the Salesterm, otherwise Salesterm insertion
       
   276             # will fail.
       
   277             # NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the
       
   278             # insertion does not fail and we have to check dumbly that File is
       
   279             # created before.
       
   280             self.assertGreater(salesterm.eid, salesterm.described_by_test[0].eid)
       
   281 
       
   282     def test_edit_mandatory_inlined3_object(self):
       
   283         # non regression test for #3120495. Without the fix, leads to
       
   284         # "unhashable type: 'list'" error
       
   285         with self.admin_access.web_request() as req:
       
   286             cwrelation = text_type(req.execute('CWEType X WHERE X name "CWSource"')[0][0])
       
   287             req.form = {'eid': [cwrelation], '__maineid' : cwrelation,
       
   288 
       
   289                         '__type:'+cwrelation: 'CWEType',
       
   290                         '_cw_entity_fields:'+cwrelation: 'to_entity-object',
       
   291                         'to_entity-object:'+cwrelation: [9999, 9998],
       
   292                         }
       
   293             with req.cnx.deny_all_hooks_but():
       
   294                 path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   295             self.assertTrue(path.startswith('cwetype/CWSource'), path)
       
   296 
       
   297     def test_edit_multiple_linked(self):
       
   298         with self.admin_access.web_request() as req:
       
   299             peid = text_type(self.create_user(req, u'adim').eid)
       
   300             req.form = {'eid': [peid, 'Y'], '__maineid': peid,
       
   301 
       
   302                         '__type:'+peid: u'CWUser',
       
   303                         '_cw_entity_fields:'+peid: u'surname-subject',
       
   304                         'surname-subject:'+peid: u'Di Masci',
       
   305 
       
   306                         '__type:Y': u'EmailAddress',
       
   307                         '_cw_entity_fields:Y': u'address-subject,use_email-object',
       
   308                         'address-subject:Y': u'dima@logilab.fr',
       
   309                         'use_email-object:Y': peid,
       
   310                         }
       
   311             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   312             # should be redirected on the created person
       
   313             self.assertEqual(path, 'cwuser/adim')
       
   314             e = req.execute('Any P WHERE P surname "Di Masci"').get_entity(0, 0)
       
   315             email = e.use_email[0]
       
   316             self.assertEqual(email.address, 'dima@logilab.fr')
       
   317 
       
   318         # with self.admin_access.web_request() as req:
       
   319             emaileid = text_type(email.eid)
       
   320             req.form = {'eid': [peid, emaileid],
       
   321 
       
   322                         '__type:'+peid: u'CWUser',
       
   323                         '_cw_entity_fields:'+peid: u'surname-subject',
       
   324                         'surname-subject:'+peid: u'Di Masci',
       
   325 
       
   326                         '__type:'+emaileid: u'EmailAddress',
       
   327                         '_cw_entity_fields:'+emaileid: u'address-subject,use_email-object',
       
   328                         'address-subject:'+emaileid: u'adim@logilab.fr',
       
   329                         'use_email-object:'+emaileid: peid,
       
   330                         }
       
   331             self.expect_redirect_handle_request(req, 'edit')
       
   332             email.cw_clear_all_caches()
       
   333             self.assertEqual(email.address, 'adim@logilab.fr')
       
   334 
       
   335     def test_password_confirm(self):
       
   336         """test creation of two linked entities
       
   337         """
       
   338         with self.admin_access.web_request() as req:
       
   339             user = req.user
       
   340             req.form = {'eid': 'X',
       
   341                         '__cloned_eid:X': text_type(user.eid), '__type:X': 'CWUser',
       
   342                         '_cw_entity_fields:X': 'login-subject,upassword-subject',
       
   343                         'login-subject:X': u'toto',
       
   344                         'upassword-subject:X': u'toto',
       
   345                         }
       
   346             with self.assertRaises(ValidationError) as cm:
       
   347                 self.ctrl_publish(req)
       
   348             self.assertEqual({'upassword-subject': u'password and confirmation don\'t match'},
       
   349                              cm.exception.errors)
       
   350             req.form = {'__cloned_eid:X': text_type(user.eid),
       
   351                         'eid': 'X', '__type:X': 'CWUser',
       
   352                         '_cw_entity_fields:X': 'login-subject,upassword-subject',
       
   353                         'login-subject:X': u'toto',
       
   354                         'upassword-subject:X': u'toto',
       
   355                         'upassword-subject-confirm:X': u'tutu',
       
   356                         }
       
   357             with self.assertRaises(ValidationError) as cm:
       
   358                 self.ctrl_publish(req)
       
   359             self.assertEqual({'upassword-subject': u'password and confirmation don\'t match'},
       
   360                              cm.exception.errors)
       
   361 
       
   362 
       
   363     def test_interval_bound_constraint_success(self):
       
   364         with self.admin_access.repo_cnx() as cnx:
       
   365             feid = cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
       
   366                                {'data': Binary(b'yo')})[0][0]
       
   367             cnx.commit()
       
   368 
       
   369         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   370             req.form = {'eid': ['X'],
       
   371                         '__type:X': 'Salesterm',
       
   372                         '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
       
   373                         'amount-subject:X': u'-10',
       
   374                         'described_by_test-subject:X': text_type(feid),
       
   375                     }
       
   376             with self.assertRaises(ValidationError) as cm:
       
   377                 self.ctrl_publish(req)
       
   378             cm.exception.translate(text_type)
       
   379             self.assertEqual({'amount-subject': 'value -10 must be >= 0'},
       
   380                              cm.exception.errors)
       
   381 
       
   382         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   383             req.form = {'eid': ['X'],
       
   384                         '__type:X': 'Salesterm',
       
   385                         '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
       
   386                         'amount-subject:X': u'110',
       
   387                         'described_by_test-subject:X': text_type(feid),
       
   388                         }
       
   389             with self.assertRaises(ValidationError) as cm:
       
   390                 self.ctrl_publish(req)
       
   391             cm.exception.translate(text_type)
       
   392             self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
       
   393 
       
   394         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   395             req.form = {'eid': ['X'],
       
   396                         '__type:X': 'Salesterm',
       
   397                         '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
       
   398                         'amount-subject:X': u'10',
       
   399                         'described_by_test-subject:X': text_type(feid),
       
   400                         }
       
   401             self.expect_redirect_handle_request(req, 'edit')
       
   402             # should be redirected on the created
       
   403             #eid = params['rql'].split()[-1]
       
   404             e = req.execute('Salesterm X').get_entity(0, 0)
       
   405             self.assertEqual(e.amount, 10)
       
   406 
       
   407     def test_interval_bound_constraint_validateform(self):
       
   408         """Test the FormValidatorController controller on entity with
       
   409         constrained attributes"""
       
   410         with self.admin_access.repo_cnx() as cnx:
       
   411             feid = cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
       
   412                                {'data': Binary(b'yo')})[0][0]
       
   413             seid = cnx.create_entity('Salesterm', amount=0, described_by_test=feid).eid
       
   414             cnx.commit()
       
   415 
       
   416         # ensure a value that violate a constraint is properly detected
       
   417         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   418             req.form = {'eid': [text_type(seid)],
       
   419                         '__type:%s'%seid: 'Salesterm',
       
   420                         '_cw_entity_fields:%s'%seid: 'amount-subject',
       
   421                         'amount-subject:%s'%seid: u'-10',
       
   422                     }
       
   423             self.assertMultiLineEqual('''<script type="text/javascript">
       
   424  window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
       
   425 </script>'''%seid, self.ctrl_publish(req, 'validateform').decode('ascii'))
       
   426 
       
   427         # ensure a value that comply a constraint is properly processed
       
   428         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   429             req.form = {'eid': [text_type(seid)],
       
   430                         '__type:%s'%seid: 'Salesterm',
       
   431                         '_cw_entity_fields:%s'%seid: 'amount-subject',
       
   432                         'amount-subject:%s'%seid: u'20',
       
   433                     }
       
   434             self.assertMultiLineEqual('''<script type="text/javascript">
       
   435  window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
       
   436 </script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
       
   437             self.assertEqual(20, req.execute('Any V WHERE X amount V, X eid %(eid)s',
       
   438                                              {'eid': seid})[0][0])
       
   439 
       
   440         with self.admin_access.web_request(rollbackfirst=True) as req:
       
   441             req.form = {'eid': ['X'],
       
   442                         '__type:X': 'Salesterm',
       
   443                         '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
       
   444                         'amount-subject:X': u'0',
       
   445                         'described_by_test-subject:X': text_type(feid),
       
   446                     }
       
   447 
       
   448             # ensure a value that is modified in an operation on a modify
       
   449             # hook works as it should (see
       
   450             # https://www.cubicweb.org/ticket/2509729 )
       
   451             class MyOperation(Operation):
       
   452                 def precommit_event(self):
       
   453                     self.entity.cw_set(amount=-10)
       
   454             class ValidationErrorInOpAfterHook(Hook):
       
   455                 __regid__ = 'valerror-op-after-hook'
       
   456                 __select__ = Hook.__select__ & is_instance('Salesterm')
       
   457                 events = ('after_add_entity',)
       
   458                 def __call__(self):
       
   459                     MyOperation(self._cw, entity=self.entity)
       
   460 
       
   461             with self.temporary_appobjects(ValidationErrorInOpAfterHook):
       
   462                 self.assertMultiLineEqual('''<script type="text/javascript">
       
   463  window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
       
   464 </script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
       
   465 
       
   466             self.assertMultiLineEqual('''<script type="text/javascript">
       
   467  window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
       
   468 </script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
       
   469 
       
   470     def test_req_pending_insert(self):
       
   471         """make sure req's pending insertions are taken into account"""
       
   472         with self.admin_access.web_request() as req:
       
   473             tmpgroup = req.create_entity('CWGroup', name=u"test")
       
   474             user = req.user
       
   475             req.cnx.commit()
       
   476         with self.admin_access.web_request(**req_form(user)) as req:
       
   477             req.session.data['pending_insert'] = set([(user.eid, 'in_group', tmpgroup.eid)])
       
   478             self.expect_redirect_handle_request(req, 'edit')
       
   479             usergroups = [gname for gname, in
       
   480                           req.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',
       
   481                                       {'u': user.eid})]
       
   482             self.assertCountEqual(usergroups, ['managers', 'test'])
       
   483             self.assertEqual(get_pending_inserts(req), [])
       
   484 
       
   485     def test_req_pending_delete(self):
       
   486         """make sure req's pending deletions are taken into account"""
       
   487         with self.admin_access.web_request() as req:
       
   488             user = req.user
       
   489             groupeid = req.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',
       
   490                                     {'x': user.eid})[0][0]
       
   491             usergroups = [gname for gname, in
       
   492                           req.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',
       
   493                                       {'u': user.eid})]
       
   494             # just make sure everything was set correctly
       
   495             self.assertCountEqual(usergroups, ['managers', 'test'])
       
   496             req.cnx.commit()
       
   497             # now try to delete the relation
       
   498         with self.admin_access.web_request(**req_form(user)) as req:
       
   499             req.session.data['pending_delete'] = set([(user.eid, 'in_group', groupeid)])
       
   500             self.expect_redirect_handle_request(req, 'edit')
       
   501             usergroups = [gname for gname, in
       
   502                           req.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',
       
   503                                       {'u': user.eid})]
       
   504             self.assertCountEqual(usergroups, ['managers'])
       
   505             self.assertEqual(get_pending_deletes(req), [])
       
   506 
       
   507     def test_redirect_apply_button(self):
       
   508         with self.admin_access.web_request() as req:
       
   509             redirectrql = rql_for_eid(4012) # whatever
       
   510             req.form = {
       
   511                 'eid': 'A', '__maineid' : 'A',
       
   512                 '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
       
   513                 'content-subject:A': u'"13:03:43"',
       
   514                 'title-subject:A': u'huuu',
       
   515                 '__redirectrql': redirectrql,
       
   516                 '__redirectvid': 'primary',
       
   517                 '__redirectparams': 'toto=tutu&tata=titi',
       
   518                 '__form_id': 'edition',
       
   519                 '__action_apply': '',
       
   520                 }
       
   521             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   522             self.assertTrue(path.startswith('blogentry/'))
       
   523             eid = path.split('/')[1]
       
   524             self.assertEqual(params['vid'], 'edition')
       
   525             self.assertNotEqual(int(eid), 4012)
       
   526             self.assertEqual(params['__redirectrql'], redirectrql)
       
   527             self.assertEqual(params['__redirectvid'], 'primary')
       
   528             self.assertEqual(params['__redirectparams'], 'toto=tutu&tata=titi')
       
   529 
       
   530     def test_redirect_ok_button(self):
       
   531         with self.admin_access.web_request() as req:
       
   532             redirectrql = rql_for_eid(4012) # whatever
       
   533             req.form = {
       
   534                 'eid': 'A', '__maineid' : 'A',
       
   535                 '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
       
   536                 'content-subject:A': u'"13:03:43"',
       
   537                 'title-subject:A': u'huuu',
       
   538                 '__redirectrql': redirectrql,
       
   539                 '__redirectvid': 'primary',
       
   540                 '__redirectparams': 'toto=tutu&tata=titi',
       
   541                 '__form_id': 'edition',
       
   542                 }
       
   543             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   544             self.assertEqual(path, 'view')
       
   545             self.assertEqual(params['rql'], redirectrql)
       
   546             self.assertEqual(params['vid'], 'primary')
       
   547             self.assertEqual(params['tata'], 'titi')
       
   548             self.assertEqual(params['toto'], 'tutu')
       
   549 
       
   550     def test_redirect_delete_button(self):
       
   551         with self.admin_access.web_request() as req:
       
   552             eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
       
   553             req.form = {'eid': text_type(eid), '__type:%s'%eid: 'BlogEntry',
       
   554                         '__action_delete': ''}
       
   555             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   556             self.assertEqual(path, 'blogentry')
       
   557             self.assertIn('_cwmsgid', params)
       
   558             eid = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
       
   559             req.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
       
   560                         {'x': req.user.eid, 'e': eid})
       
   561             req.cnx.commit()
       
   562             req.form = {'eid': text_type(eid), '__type:%s'%eid: 'EmailAddress',
       
   563                         '__action_delete': ''}
       
   564             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   565             self.assertEqual(path, 'cwuser/admin')
       
   566             self.assertIn('_cwmsgid', params)
       
   567             eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
       
   568             eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
       
   569             req.form = {'eid': [text_type(eid1), text_type(eid2)],
       
   570                         '__type:%s'%eid1: 'BlogEntry',
       
   571                         '__type:%s'%eid2: 'EmailAddress',
       
   572                         '__action_delete': ''}
       
   573             path, params = self.expect_redirect_handle_request(req, 'edit')
       
   574             self.assertEqual(path, 'view')
       
   575             self.assertIn('_cwmsgid', params)
       
   576 
       
   577     def test_simple_copy(self):
       
   578         with self.admin_access.web_request() as req:
       
   579             blog = req.create_entity('Blog', title=u'my-blog')
       
   580             blogentry = req.create_entity('BlogEntry', title=u'entry1',
       
   581                                           content=u'content1', entry_of=blog)
       
   582             req.form = {'__maineid' : 'X', 'eid': 'X',
       
   583                         '__cloned_eid:X': blogentry.eid, '__type:X': 'BlogEntry',
       
   584                         '_cw_entity_fields:X': 'title-subject,content-subject',
       
   585                         'title-subject:X': u'entry1-copy',
       
   586                         'content-subject:X': u'content1',
       
   587                         }
       
   588             self.expect_redirect_handle_request(req, 'edit')
       
   589             blogentry2 = req.find('BlogEntry', title=u'entry1-copy').one()
       
   590             self.assertEqual(blogentry2.entry_of[0].eid, blog.eid)
       
   591 
       
   592     def test_skip_copy_for(self):
       
   593         with self.admin_access.web_request() as req:
       
   594             blog = req.create_entity('Blog', title=u'my-blog')
       
   595             blogentry = req.create_entity('BlogEntry', title=u'entry1',
       
   596                                           content=u'content1', entry_of=blog)
       
   597             blogentry.__class__.cw_skip_copy_for = [('entry_of', 'subject')]
       
   598             try:
       
   599                 req.form = {'__maineid' : 'X', 'eid': 'X',
       
   600                             '__cloned_eid:X': blogentry.eid, '__type:X': 'BlogEntry',
       
   601                             '_cw_entity_fields:X': 'title-subject,content-subject',
       
   602                             'title-subject:X': u'entry1-copy',
       
   603                             'content-subject:X': u'content1',
       
   604                             }
       
   605                 self.expect_redirect_handle_request(req, 'edit')
       
   606                 blogentry2 = req.find('BlogEntry', title=u'entry1-copy').one()
       
   607                 # entry_of should not be copied
       
   608                 self.assertEqual(len(blogentry2.entry_of), 0)
       
   609             finally:
       
   610                 blogentry.__class__.cw_skip_copy_for = []
       
   611 
       
   612     def test_nonregr_eetype_etype_editing(self):
       
   613         """non-regression test checking that a manager user can edit a CWEType entity
       
   614         """
       
   615         with self.admin_access.web_request() as req:
       
   616             groupeids = sorted(eid
       
   617                                for eid, in req.execute('CWGroup G '
       
   618                                                        'WHERE G name in ("managers", "users")'))
       
   619             groups = [text_type(eid) for eid in groupeids]
       
   620             cwetypeeid = req.execute('CWEType X WHERE X name "CWEType"')[0][0]
       
   621             basegroups = [text_type(eid)
       
   622                           for eid, in req.execute('CWGroup G '
       
   623                                                   'WHERE X read_permission G, X eid %(x)s',
       
   624                                                   {'x': cwetypeeid})]
       
   625             cwetypeeid = text_type(cwetypeeid)
       
   626             req.form = {
       
   627                 'eid':      cwetypeeid,
       
   628                 '__type:'+cwetypeeid:  'CWEType',
       
   629                 '_cw_entity_fields:'+cwetypeeid: 'name-subject,final-subject,description-subject,read_permission-subject',
       
   630                 'name-subject:'+cwetypeeid:     u'CWEType',
       
   631                 'final-subject:'+cwetypeeid:    '',
       
   632                 'description-subject:'+cwetypeeid:     u'users group',
       
   633                 'read_permission-subject:'+cwetypeeid:  groups,
       
   634             }
       
   635             try:
       
   636                 self.expect_redirect_handle_request(req, 'edit')
       
   637                 e = req.execute('Any X WHERE X eid %(x)s', {'x': cwetypeeid}).get_entity(0, 0)
       
   638                 self.assertEqual(e.name, 'CWEType')
       
   639                 self.assertEqual(sorted(g.eid for g in e.read_permission), groupeids)
       
   640             finally:
       
   641                 # restore
       
   642                 req.execute('SET X read_permission Y WHERE X name "CWEType", '
       
   643                             'Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
       
   644                 req.cnx.commit()
       
   645 
       
   646     def test_nonregr_strange_text_input(self):
       
   647         """non-regression test checking text input containing "13:03:43"
       
   648 
       
   649         this seems to be postgres (tsearch?) specific
       
   650         """
       
   651         with self.admin_access.web_request() as req:
       
   652             req.form = {
       
   653                 'eid': 'A', '__maineid' : 'A',
       
   654                 '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'title-subject,content-subject',
       
   655                 'title-subject:A': u'"13:03:40"',
       
   656                 'content-subject:A': u'"13:03:43"',}
       
   657             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   658             self.assertTrue(path.startswith('blogentry/'))
       
   659             eid = path.split('/')[1]
       
   660             e = req.execute('Any C, T WHERE C eid %(x)s, C content T', {'x': eid}).get_entity(0, 0)
       
   661             self.assertEqual(e.title, '"13:03:40"')
       
   662             self.assertEqual(e.content, '"13:03:43"')
       
   663 
       
   664 
       
   665     def test_nonregr_multiple_empty_email_addr(self):
       
   666         with self.admin_access.web_request() as req:
       
   667             gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
       
   668             req.form = {'eid': ['X', 'Y'],
       
   669 
       
   670                         '__type:X': 'CWUser',
       
   671                         '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
       
   672                         'login-subject:X': u'adim',
       
   673                         'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
       
   674                         'in_group-subject:X': repr(gueid),
       
   675 
       
   676                         '__type:Y': 'EmailAddress',
       
   677                         '_cw_entity_fields:Y': 'address-subject,alias-subject,use_email-object',
       
   678                         'address-subject:Y': u'',
       
   679                         'alias-subject:Y': u'',
       
   680                         'use_email-object:Y': 'X',
       
   681                         }
       
   682             with self.assertRaises(ValidationError) as cm:
       
   683                 self.ctrl_publish(req)
       
   684             self.assertEqual(cm.exception.errors, {'address-subject': u'required field'})
       
   685 
       
   686     def test_nonregr_copy(self):
       
   687         with self.admin_access.web_request() as req:
       
   688             user = req.user
       
   689             req.form = {'__maineid' : 'X', 'eid': 'X',
       
   690                         '__cloned_eid:X': user.eid, '__type:X': 'CWUser',
       
   691                         '_cw_entity_fields:X': 'login-subject,upassword-subject',
       
   692                         'login-subject:X': u'toto',
       
   693                         'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
       
   694                         }
       
   695             path, _params = self.expect_redirect_handle_request(req, 'edit')
       
   696             self.assertEqual(path, 'cwuser/toto')
       
   697             e = req.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
       
   698             self.assertEqual(e.login, 'toto')
       
   699             self.assertEqual(e.in_group[0].name, 'managers')
       
   700 
       
   701 
       
   702     def test_nonregr_rollback_on_validation_error(self):
       
   703         with self.admin_access.web_request() as req:
       
   704             p = self.create_user(req, u"doe")
       
   705             # do not try to skip 'primary_email' for this test
       
   706             old_skips = p.__class__.skip_copy_for
       
   707             p.__class__.skip_copy_for = ()
       
   708             try:
       
   709                 e = req.create_entity('EmailAddress', address=u'doe@doe.com')
       
   710                 req.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',
       
   711                             {'p' : p.eid, 'e' : e.eid})
       
   712                 req.form = {'eid': 'X',
       
   713                             '__cloned_eid:X': p.eid, '__type:X': 'CWUser',
       
   714                             '_cw_entity_fields:X': 'login-subject,surname-subject',
       
   715                             'login-subject': u'dodo',
       
   716                             'surname-subject:X': u'Boom',
       
   717                             '__errorurl' : "whatever but required",
       
   718                             }
       
   719                 # try to emulate what really happens in the web application
       
   720                 # 1/ validate form => EditController.publish raises a ValidationError
       
   721                 #    which fires a Redirect
       
   722                 # 2/ When re-publishing the copy form, the publisher implicitly commits
       
   723                 try:
       
   724                     self.app_handle_request(req, 'edit')
       
   725                 except Redirect:
       
   726                     req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
       
   727                     req.form['vid'] = 'copy'
       
   728                     self.app_handle_request(req, 'view')
       
   729                 rset = req.execute('CWUser P WHERE P surname "Boom"')
       
   730                 self.assertEqual(len(rset), 0)
       
   731             finally:
       
   732                 p.__class__.skip_copy_for = old_skips
       
   733 
       
   734     def test_regr_inlined_forms(self):
       
   735         with self.admin_access.web_request() as req:
       
   736             self.schema['described_by_test'].inlined = False
       
   737             try:
       
   738                 req.data['eidmap'] = {}
       
   739                 req.data['pending_others'] = set()
       
   740                 req.data['pending_inlined'] = {}
       
   741                 req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
       
   742 
       
   743                             '__type:X': 'Salesterm',
       
   744                             '_cw_entity_fields:X': 'described_by_test-subject',
       
   745                             'described_by_test-subject:X': 'Y',
       
   746 
       
   747                             '__type:Y': 'File',
       
   748                             '_cw_entity_fields:Y': 'data-subject',
       
   749                             'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
       
   750                             }
       
   751                 values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2))
       
   752                                      for eid in req.edited_eids())
       
   753                 editctrl = self.vreg['controllers'].select('edit', req)
       
   754                 # don't call publish to enforce select order
       
   755                 editctrl.errors = []
       
   756                 editctrl._to_create = {}
       
   757                 editctrl.edit_entity(values_by_eid['X']) # #3064653 raise ValidationError
       
   758                 editctrl.edit_entity(values_by_eid['Y'])
       
   759             finally:
       
   760                 self.schema['described_by_test'].inlined = False
       
   761 
       
   762 
       
   763 class ReportBugControllerTC(CubicWebTC):
       
   764 
       
   765     def test_usable_by_guest(self):
       
   766         with self.new_access(u'anon').web_request() as req:
       
   767             self.assertRaises(NoSelectableObject,
       
   768                               self.vreg['controllers'].select, 'reportbug', req)
       
   769         with self.new_access(u'anon').web_request(description='hop') as req:
       
   770             self.vreg['controllers'].select('reportbug', req)
       
   771 
       
   772 
       
   773 class AjaxControllerTC(CubicWebTC):
       
   774     tested_controller = 'ajax'
       
   775 
       
   776     def ctrl(self, req=None):
       
   777         req = req or self.request(url='http://whatever.fr/')
       
   778         return self.vreg['controllers'].select(self.tested_controller, req)
       
   779 
       
   780     def setup_database(self):
       
   781         with self.admin_access.repo_cnx() as cnx:
       
   782             self.pytag = cnx.create_entity('Tag', name=u'python')
       
   783             self.cubicwebtag = cnx.create_entity('Tag', name=u'cubicweb')
       
   784             self.john = self.create_user(cnx, u'John')
       
   785             cnx.commit()
       
   786 
       
   787     ## tests ##################################################################
       
   788     def test_simple_exec(self):
       
   789         with self.admin_access.web_request(rql='CWUser P WHERE P login "John"',
       
   790                                            pageid='123', fname='view') as req:
       
   791             ctrl = self.ctrl(req)
       
   792             rset = self.john.as_rset()
       
   793             rset.req = req
       
   794             source = ctrl.publish()
       
   795             self.assertTrue(source.startswith(b'<div>'))
       
   796 
       
   797 #     def test_json_exec(self):
       
   798 #         rql = 'Any T,N WHERE T is Tag, T name N'
       
   799 #         ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
       
   800 #         self.assertEqual(ctrl.publish(),
       
   801 #                           json_dumps(self.execute(rql).rows))
       
   802 
       
   803     def test_remote_add_existing_tag(self):
       
   804         with self.remote_calling('tag_entity', self.john.eid, ['python']) as (_, req):
       
   805             self.assertCountEqual(
       
   806                 [tname for tname, in req.execute('Any N WHERE T is Tag, T name N')],
       
   807                 ['python', 'cubicweb'])
       
   808             self.assertEqual(
       
   809                 req.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
       
   810                 [['python']])
       
   811 
       
   812     def test_remote_add_new_tag(self):
       
   813         with self.remote_calling('tag_entity', self.john.eid, ['javascript']) as (_, req):
       
   814             self.assertCountEqual(
       
   815                 [tname for tname, in req.execute('Any N WHERE T is Tag, T name N')],
       
   816                 ['python', 'cubicweb', 'javascript'])
       
   817             self.assertEqual(
       
   818                 req.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
       
   819                 [['javascript']])
       
   820 
       
   821     def test_maydel_perms(self):
       
   822         """Check that AjaxEditRelationCtxComponent calls rdef.check with a
       
   823         sufficient context"""
       
   824         with self.remote_calling('tag_entity', self.john.eid, ['python']) as (_, req):
       
   825             req.cnx.commit()
       
   826         with self.temporary_permissions(
       
   827                 (self.schema['tags'].rdefs['Tag', 'CWUser'],
       
   828                  {'delete': (RRQLExpression('S owned_by U'), )}, )):
       
   829             with self.admin_access.web_request(rql='CWUser P WHERE P login "John"',
       
   830                                    pageid='123', fname='view') as req:
       
   831                 ctrl = self.ctrl(req)
       
   832                 rset = self.john.as_rset()
       
   833                 rset.req = req
       
   834                 source = ctrl.publish()
       
   835                 # maydel jscall
       
   836                 self.assertIn(b'ajaxBoxRemoveLinkedEntity', source)
       
   837 
       
   838     def test_pending_insertion(self):
       
   839         with self.remote_calling('add_pending_inserts', [['12', 'tags', '13']]) as (_, req):
       
   840             deletes = get_pending_deletes(req)
       
   841             self.assertEqual(deletes, [])
       
   842             inserts = get_pending_inserts(req)
       
   843             self.assertEqual(inserts, ['12:tags:13'])
       
   844         with self.remote_calling('add_pending_inserts', [['12', 'tags', '14']]) as (_, req):
       
   845             deletes = get_pending_deletes(req)
       
   846             self.assertEqual(deletes, [])
       
   847             inserts = get_pending_inserts(req)
       
   848             self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
       
   849             inserts = get_pending_inserts(req, 12)
       
   850             self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
       
   851             inserts = get_pending_inserts(req, 13)
       
   852             self.assertEqual(inserts, ['12:tags:13'])
       
   853             inserts = get_pending_inserts(req, 14)
       
   854             self.assertEqual(inserts, ['12:tags:14'])
       
   855             req.remove_pending_operations()
       
   856 
       
   857     def test_pending_deletion(self):
       
   858         with self.remote_calling('add_pending_delete', ['12', 'tags', '13']) as (_, req):
       
   859             inserts = get_pending_inserts(req)
       
   860             self.assertEqual(inserts, [])
       
   861             deletes = get_pending_deletes(req)
       
   862             self.assertEqual(deletes, ['12:tags:13'])
       
   863         with self.remote_calling('add_pending_delete', ['12', 'tags', '14']) as (_, req):
       
   864             inserts = get_pending_inserts(req)
       
   865             self.assertEqual(inserts, [])
       
   866             deletes = get_pending_deletes(req)
       
   867             self.assertCountEqual(deletes, ['12:tags:13', '12:tags:14'])
       
   868             deletes = get_pending_deletes(req, 12)
       
   869             self.assertCountEqual(deletes, ['12:tags:13', '12:tags:14'])
       
   870             deletes = get_pending_deletes(req, 13)
       
   871             self.assertEqual(deletes, ['12:tags:13'])
       
   872             deletes = get_pending_deletes(req, 14)
       
   873             self.assertEqual(deletes, ['12:tags:14'])
       
   874             req.remove_pending_operations()
       
   875 
       
   876     def test_remove_pending_operations(self):
       
   877         with self.remote_calling('add_pending_delete', ['12', 'tags', '13']):
       
   878             pass
       
   879         with self.remote_calling('add_pending_inserts', [['12', 'tags', '14']]) as (_, req):
       
   880             inserts = get_pending_inserts(req)
       
   881             self.assertEqual(inserts, ['12:tags:14'])
       
   882             deletes = get_pending_deletes(req)
       
   883             self.assertEqual(deletes, ['12:tags:13'])
       
   884             req.remove_pending_operations()
       
   885             self.assertEqual(get_pending_deletes(req), [])
       
   886             self.assertEqual(get_pending_inserts(req), [])
       
   887 
       
   888     def test_add_inserts(self):
       
   889         with self.remote_calling('add_pending_inserts',
       
   890                                  [('12', 'tags', '13'), ('12', 'tags', '14')]) as (_, req):
       
   891             inserts = get_pending_inserts(req)
       
   892             self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
       
   893             req.remove_pending_operations()
       
   894 
       
   895 
       
   896     # silly tests
       
   897     def test_external_resource(self):
       
   898         with self.remote_calling('external_resource', 'RSS_LOGO') as (res, _):
       
   899             self.assertEqual(json_dumps(self.config.uiprops['RSS_LOGO']).encode('ascii'),
       
   900                              res)
       
   901 
       
   902     def test_i18n(self):
       
   903         with self.remote_calling('i18n', ['bimboom']) as (res, _):
       
   904             self.assertEqual(json_dumps(['bimboom']).encode('ascii'), res)
       
   905 
       
   906     def test_format_date(self):
       
   907         with self.remote_calling('format_date', '2007-01-01 12:00:00') as (res, _):
       
   908             self.assertEqual(json_dumps('2007/01/01').encode('ascii'), res)
       
   909 
       
   910     def test_ajaxfunc_noparameter(self):
       
   911         @ajaxfunc
       
   912         def foo(self, x, y):
       
   913             return 'hello'
       
   914         self.assertEqual(foo(object, 1, 2), 'hello')
       
   915         appobject = foo.__appobject__
       
   916         self.assertTrue(issubclass(appobject, AjaxFunction))
       
   917         self.assertEqual(appobject.__regid__, 'foo')
       
   918         self.assertEqual(appobject.check_pageid, False)
       
   919         self.assertEqual(appobject.output_type, None)
       
   920         with self.admin_access.web_request() as req:
       
   921             f = appobject(req)
       
   922             self.assertEqual(f(12, 13), 'hello')
       
   923 
       
   924     def test_ajaxfunc_checkpageid(self):
       
   925         @ajaxfunc(check_pageid=True)
       
   926         def foo(self, x, y):
       
   927             return 'hello'
       
   928         self.assertEqual(foo(object, 1, 2), 'hello')
       
   929         appobject = foo.__appobject__
       
   930         self.assertTrue(issubclass(appobject, AjaxFunction))
       
   931         self.assertEqual(appobject.__regid__, 'foo')
       
   932         self.assertEqual(appobject.check_pageid, True)
       
   933         self.assertEqual(appobject.output_type, None)
       
   934         # no pageid
       
   935         with self.admin_access.web_request() as req:
       
   936             f = appobject(req)
       
   937             self.assertRaises(RemoteCallFailed, f, 12, 13)
       
   938 
       
   939     def test_ajaxfunc_json(self):
       
   940         @ajaxfunc(output_type='json')
       
   941         def foo(self, x, y):
       
   942             return x + y
       
   943         self.assertEqual(foo(object, 1, 2), 3)
       
   944         appobject = foo.__appobject__
       
   945         self.assertTrue(issubclass(appobject, AjaxFunction))
       
   946         self.assertEqual(appobject.__regid__, 'foo')
       
   947         self.assertEqual(appobject.check_pageid, False)
       
   948         self.assertEqual(appobject.output_type, 'json')
       
   949         # no pageid
       
   950         with self.admin_access.web_request() as req:
       
   951             f = appobject(req)
       
   952             self.assertEqual(f(12, 13), '25')
       
   953 
       
   954 
       
   955 class JSonControllerTC(AjaxControllerTC):
       
   956     # NOTE: this class performs the same tests as AjaxController but with
       
   957     #       deprecated 'json' controller (i.e. check backward compatibility)
       
   958     tested_controller = 'json'
       
   959 
       
   960     def setUp(self):
       
   961         super(JSonControllerTC, self).setUp()
       
   962         self.exposed_remote_funcs = [fname for fname in dir(JSonController)
       
   963                                      if fname.startswith('js_')]
       
   964 
       
   965     def tearDown(self):
       
   966         super(JSonControllerTC, self).tearDown()
       
   967         for funcname in dir(JSonController):
       
   968             # remove functions added dynamically during tests
       
   969             if funcname.startswith('js_') and funcname not in self.exposed_remote_funcs:
       
   970                 delattr(JSonController, funcname)
       
   971 
       
   972     def test_monkeypatch_jsoncontroller(self):
       
   973         with self.assertRaises(RemoteCallFailed):
       
   974             with self.remote_calling('foo'):
       
   975                 pass
       
   976         @monkeypatch(JSonController)
       
   977         def js_foo(self):
       
   978             return u'hello'
       
   979         with self.remote_calling('foo') as (res, _):
       
   980             self.assertEqual(res, b'hello')
       
   981 
       
   982     def test_monkeypatch_jsoncontroller_xhtmlize(self):
       
   983         with self.assertRaises(RemoteCallFailed):
       
   984             with self.remote_calling('foo'):
       
   985                 pass
       
   986         @monkeypatch(JSonController)
       
   987         @xhtmlize
       
   988         def js_foo(self):
       
   989             return u'hello'
       
   990         with self.remote_calling('foo') as (res, _):
       
   991             self.assertEqual(b'<div>hello</div>', res)
       
   992 
       
   993     def test_monkeypatch_jsoncontroller_jsonize(self):
       
   994         with self.assertRaises(RemoteCallFailed):
       
   995             with self.remote_calling('foo'):
       
   996                 pass
       
   997         @monkeypatch(JSonController)
       
   998         @jsonize
       
   999         def js_foo(self):
       
  1000             return 12
       
  1001         with self.remote_calling('foo') as (res, _):
       
  1002             self.assertEqual(res, b'12')
       
  1003 
       
  1004     def test_monkeypatch_jsoncontroller_stdfunc(self):
       
  1005         @monkeypatch(JSonController)
       
  1006         @jsonize
       
  1007         def js_reledit_form(self):
       
  1008             return 12
       
  1009         with self.remote_calling('reledit_form') as (res, _):
       
  1010             self.assertEqual(res, b'12')
       
  1011 
       
  1012 
       
  1013 class UndoControllerTC(CubicWebTC):
       
  1014 
       
  1015     def setUp(self):
       
  1016         class Connection(OldConnection):
       
  1017             """Force undo feature to be turned on in all case"""
       
  1018             undo_actions = property(lambda tx: True, lambda x, y:None)
       
  1019         cubicweb.server.session.Connection = Connection
       
  1020         super(UndoControllerTC, self).setUp()
       
  1021 
       
  1022     def tearDown(self):
       
  1023         super(UndoControllerTC, self).tearDown()
       
  1024         cubicweb.server.session.Connection = OldConnection
       
  1025 
       
  1026     def setup_database(self):
       
  1027         with self.admin_access.repo_cnx() as cnx:
       
  1028             self.toto = self.create_user(cnx, u'toto',
       
  1029                                          password=u'toto',
       
  1030                                          groups=('users',),
       
  1031                                          commit=False)
       
  1032             self.txuuid_toto = cnx.commit()
       
  1033             self.toto_email = cnx.create_entity('EmailAddress',
       
  1034                                                 address=u'toto@logilab.org',
       
  1035                                                 reverse_use_email=self.toto)
       
  1036             self.txuuid_toto_email = cnx.commit()
       
  1037 
       
  1038     def test_no_such_transaction(self):
       
  1039         with self.admin_access.web_request() as req:
       
  1040             txuuid = u"12345acbd"
       
  1041             req.form['txuuid'] = txuuid
       
  1042             controller = self.vreg['controllers'].select('undo', req)
       
  1043             with self.assertRaises(tx.NoSuchTransaction) as cm:
       
  1044                 result = controller.publish(rset=None)
       
  1045             self.assertEqual(cm.exception.txuuid, txuuid)
       
  1046 
       
  1047     def assertURLPath(self, url, expected_path, expected_params=None):
       
  1048         """ This assert that the path part of `url` matches  expected path
       
  1049 
       
  1050         TODO : implement assertion on the expected_params too
       
  1051         """
       
  1052         with self.admin_access.web_request() as req:
       
  1053             scheme, netloc, path, query, fragment = urlsplit(url)
       
  1054             query_dict = parse_qs(query)
       
  1055             expected_url = urljoin(req.base_url(), expected_path)
       
  1056             self.assertEqual( urlunsplit((scheme, netloc, path, None, None)), expected_url)
       
  1057 
       
  1058     def test_redirect_redirectpath(self):
       
  1059         "Check that the potential __redirectpath is honored"
       
  1060         with self.admin_access.web_request() as req:
       
  1061             txuuid = self.txuuid_toto_email
       
  1062             req.form['txuuid'] = txuuid
       
  1063             rpath = "toto"
       
  1064             req.form['__redirectpath'] = rpath
       
  1065             controller = self.vreg['controllers'].select('undo', req)
       
  1066             with self.assertRaises(Redirect) as cm:
       
  1067                 result = controller.publish(rset=None)
       
  1068             self.assertURLPath(cm.exception.location, rpath)
       
  1069 
       
  1070 
       
  1071 class LoginControllerTC(CubicWebTC):
       
  1072 
       
  1073     def test_login_with_dest(self):
       
  1074         with self.admin_access.web_request() as req:
       
  1075             req.form = {'postlogin_path': 'elephants/babar'}
       
  1076             with self.assertRaises(Redirect) as cm:
       
  1077                 self.ctrl_publish(req, ctrl='login')
       
  1078             self.assertEqual(req.build_url('elephants/babar'), cm.exception.location)
       
  1079 
       
  1080     def test_login_no_dest(self):
       
  1081         with self.admin_access.web_request() as req:
       
  1082             with self.assertRaises(Redirect) as cm:
       
  1083                 self.ctrl_publish(req, ctrl='login')
       
  1084             self.assertEqual(req.base_url(), cm.exception.location)
       
  1085 
       
  1086 if __name__ == '__main__':
       
  1087     unittest_main()