# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""cubicweb.web.views.basecontrollers unit tests"""fromsiximporttext_typefromsix.moves.urllib.parseimporturlsplit,urlunsplit,urljoin,parse_qsimportlxmlfromlogilab.common.testlibimportunittest_mainfromlogilab.common.decoratorsimportmonkeypatchfromcubicwebimportBinary,NoSelectableObject,ValidationErrorfromcubicweb.schemaimportRRQLExpressionfromcubicweb.devtools.testlibimportCubicWebTCfromcubicweb.utilsimportjson_dumpsfromcubicweb.uilibimportrql_for_eidfromcubicweb.webimportRedirect,RemoteCallFailedimportcubicweb.server.sessionfromcubicweb.server.sessionimportConnectionasOldConnectionfromcubicweb.web.views.autoformimportget_pending_inserts,get_pending_deletesfromcubicweb.web.views.basecontrollersimportJSonController,xhtmlize,jsonizefromcubicweb.web.views.ajaxcontrollerimportajaxfunc,AjaxFunctionimportcubicweb.transactionastxfromcubicweb.server.hookimportHook,Operationfromcubicweb.predicatesimportis_instancedefreq_form(user):return{'eid':[str(user.eid)],'_cw_entity_fields:%s'%user.eid:'_cw_generic_field','__type:%s'%user.eid:user.__regid__}classEditControllerTC(CubicWebTC):defsetUp(self):CubicWebTC.setUp(self)self.assertIn('users',self.schema.eschema('CWGroup').get_groups('read'))deftearDown(self):CubicWebTC.tearDown(self)self.assertIn('users',self.schema.eschema('CWGroup').get_groups('read'))deftest_noparam_edit(self):"""check behaviour of this controller without any form parameter """withself.admin_access.web_request()asreq:withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)self.assertEqual(cm.exception.errors,{None:u'no selected entities'})deftest_validation_unique(self):"""test creation of two linked entities """withself.admin_access.web_request()asreq:req.form={'eid':'X','__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject','login-subject:X':u'admin','upassword-subject:X':u'toto','upassword-subject-confirm:X':u'toto',}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)cm.exception.translate(text_type)self.assertEqual({'login-subject':'the value "admin" is already used, use another one'},cm.exception.errors)deftest_simultaneous_edition_only_one_commit(self):""" Allow two simultaneous edit view of the same entity as long as only one commits """withself.admin_access.web_request()asreq:e=req.create_entity('BlogEntry',title=u'cubicweb.org',content=u"hop")expected_path=e.rest_path()req.cnx.commit()form=self.vreg['views'].select('edition',req,rset=e.as_rset(),row=0)html_form=lxml.html.fromstring(form.render(w=None,action='edit')).forms[0]withself.admin_access.web_request()asreq2:form2=self.vreg['views'].select('edition',req,rset=e.as_rset(),row=0)withself.admin_access.web_request(**dict(html_form.form_values()))asreq:path,args=self.expect_redirect_handle_request(req,path='edit')self.assertEqual(path,expected_path)deftest_simultaneous_edition_refuse_second_commit(self):""" Disallow committing changes to an entity edited in between """withself.admin_access.web_request()asreq:e=req.create_entity('BlogEntry',title=u'cubicweb.org',content=u"hop")eid=e.eidreq.cnx.commit()form=self.vreg['views'].select('edition',req,rset=e.as_rset(),row=0)html_form=lxml.html.fromstring(form.render(w=None,action='edit')).forms[0]withself.admin_access.web_request()asreq2:e=req2.entity_from_eid(eid)e.cw_set(content=u"hip")req2.cnx.commit()form_field_name="content-subject:%d"%eidform_values=dict(html_form.form_values())assertform_field_nameinform_valuesform_values[form_field_name]=u'yep'withself.admin_access.web_request(**form_values)asreq:withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)reported_eid,dict_info=cm.exception.argsself.assertEqual(reported_eid,eid)self.assertIn(None,dict_info)self.assertIn("has changed since you started to edit it.",dict_info[None])deftest_user_editing_itself(self):"""checking that a manager user can edit itself """withself.admin_access.web_request()asreq:user=req.usergroupeids=[eidforeid,inreq.execute('CWGroup G WHERE G name ''in ("managers", "users")')]groups=[text_type(eid)foreidingroupeids]eid=text_type(user.eid)req.form={'eid':eid,'__type:'+eid:'CWUser','_cw_entity_fields:'+eid:'login-subject,firstname-subject,surname-subject,in_group-subject','login-subject:'+eid:text_type(user.login),'surname-subject:'+eid:u'Th\xe9nault','firstname-subject:'+eid:u'Sylvain','in_group-subject:'+eid:groups,}self.expect_redirect_handle_request(req,'edit')e=req.execute('Any X WHERE X eid %(x)s',{'x':user.eid}).get_entity(0,0)self.assertEqual(e.firstname,u'Sylvain')self.assertEqual(e.surname,u'Th\xe9nault')self.assertEqual(e.login,user.login)self.assertEqual([g.eidforgine.in_group],groupeids)deftest_user_can_change_its_password(self):withself.admin_access.repo_cnx()ascnx:self.create_user(cnx,u'user')cnx.commit()withself.new_access(u'user').web_request()asreq:eid=text_type(req.user.eid)req.form={'eid':eid,'__maineid':eid,'__type:'+eid:'CWUser','_cw_entity_fields:'+eid:'upassword-subject','upassword-subject:'+eid:'tournicoton','upassword-subject-confirm:'+eid:'tournicoton',}path,params=self.expect_redirect_handle_request(req,'edit')req.cnx.commit()# commit to check we don't get late validation error for instanceself.assertEqual(path,'cwuser/user')self.assertNotIn('vid',params)deftest_user_editing_itself_no_relation(self):"""checking we can edit an entity without specifying some required relations (meaning no changes) """withself.admin_access.web_request()asreq:user=req.usergroupeids=[g.eidforginuser.in_group]eid=text_type(user.eid)req.form={'eid':eid,'__type:'+eid:'CWUser','_cw_entity_fields:'+eid:'login-subject,firstname-subject,surname-subject','login-subject:'+eid:text_type(user.login),'firstname-subject:'+eid:u'Th\xe9nault','surname-subject:'+eid:u'Sylvain',}self.expect_redirect_handle_request(req,'edit')e=req.execute('Any X WHERE X eid %(x)s',{'x':user.eid}).get_entity(0,0)self.assertEqual(e.login,user.login)self.assertEqual(e.firstname,u'Th\xe9nault')self.assertEqual(e.surname,u'Sylvain')self.assertEqual([g.eidforgine.in_group],groupeids)self.assertEqual(e.cw_adapt_to('IWorkflowable').state,'activated')deftest_create_multiple_linked(self):withself.admin_access.web_request()asreq:gueid=req.execute('CWGroup G WHERE G name "users"')[0][0]req.form={'eid':['X','Y'],'__maineid':'X','__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject,surname-subject,in_group-subject','login-subject:X':u'adim','upassword-subject:X':u'toto','upassword-subject-confirm:X':u'toto','surname-subject:X':u'Di Mascio','in_group-subject:X':text_type(gueid),'__type:Y':'EmailAddress','_cw_entity_fields:Y':'address-subject,use_email-object','address-subject:Y':u'dima@logilab.fr','use_email-object:Y':'X',}path,_params=self.expect_redirect_handle_request(req,'edit')# should be redirected on the created personself.assertEqual(path,'cwuser/adim')e=req.execute('Any P WHERE P surname "Di Mascio"').get_entity(0,0)self.assertEqual(e.surname,'Di Mascio')email=e.use_email[0]self.assertEqual(email.address,'dima@logilab.fr')deftest_create_mandatory_inlined(self):withself.admin_access.web_request()asreq:req.form={'eid':['X','Y'],'__maineid':'X','__type:X':'Salesterm','_cw_entity_fields:X':'','__type:Y':'File','_cw_entity_fields:Y':'data-subject,described_by_test-object','data-subject:Y':(u'coucou.txt',Binary(b'coucou')),'described_by_test-object:Y':'X',}path,_params=self.expect_redirect_handle_request(req,'edit')self.assertTrue(path.startswith('salesterm/'),path)eid=path.split('/')[1]salesterm=req.entity_from_eid(eid)# The NOT NULL constraint of mandatory relation implies that the File# must be created before the Salesterm, otherwise Salesterm insertion# will fail.# NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the# insertion does not fail and we have to check dumbly that File is# created before.self.assertGreater(salesterm.eid,salesterm.described_by_test[0].eid)deftest_create_mandatory_inlined2(self):withself.admin_access.web_request()asreq:req.form={'eid':['X','Y'],'__maineid':'X','__type:X':'Salesterm','_cw_entity_fields:X':'described_by_test-subject','described_by_test-subject:X':'Y','__type:Y':'File','_cw_entity_fields:Y':'data-subject','data-subject:Y':(u'coucou.txt',Binary(b'coucou')),}path,_params=self.expect_redirect_handle_request(req,'edit')self.assertTrue(path.startswith('salesterm/'),path)eid=path.split('/')[1]salesterm=req.entity_from_eid(eid)# The NOT NULL constraint of mandatory relation implies that the File# must be created before the Salesterm, otherwise Salesterm insertion# will fail.# NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the# insertion does not fail and we have to check dumbly that File is# created before.self.assertGreater(salesterm.eid,salesterm.described_by_test[0].eid)deftest_edit_mandatory_inlined3_object(self):# non regression test for #3120495. Without the fix, leads to# "unhashable type: 'list'" errorwithself.admin_access.web_request()asreq:cwrelation=text_type(req.execute('CWEType X WHERE X name "CWSource"')[0][0])req.form={'eid':[cwrelation],'__maineid':cwrelation,'__type:'+cwrelation:'CWEType','_cw_entity_fields:'+cwrelation:'to_entity-object','to_entity-object:'+cwrelation:[9999,9998],}withreq.cnx.deny_all_hooks_but():path,_params=self.expect_redirect_handle_request(req,'edit')self.assertTrue(path.startswith('cwetype/CWSource'),path)deftest_edit_multiple_linked(self):withself.admin_access.web_request()asreq:peid=text_type(self.create_user(req,u'adim').eid)req.form={'eid':[peid,'Y'],'__maineid':peid,'__type:'+peid:u'CWUser','_cw_entity_fields:'+peid:u'surname-subject','surname-subject:'+peid:u'Di Masci','__type:Y':u'EmailAddress','_cw_entity_fields:Y':u'address-subject,use_email-object','address-subject:Y':u'dima@logilab.fr','use_email-object:Y':peid,}path,_params=self.expect_redirect_handle_request(req,'edit')# should be redirected on the created personself.assertEqual(path,'cwuser/adim')e=req.execute('Any P WHERE P surname "Di Masci"').get_entity(0,0)email=e.use_email[0]self.assertEqual(email.address,'dima@logilab.fr')# with self.admin_access.web_request() as req:emaileid=text_type(email.eid)req.form={'eid':[peid,emaileid],'__type:'+peid:u'CWUser','_cw_entity_fields:'+peid:u'surname-subject','surname-subject:'+peid:u'Di Masci','__type:'+emaileid:u'EmailAddress','_cw_entity_fields:'+emaileid:u'address-subject,use_email-object','address-subject:'+emaileid:u'adim@logilab.fr','use_email-object:'+emaileid:peid,}self.expect_redirect_handle_request(req,'edit')email.cw_clear_all_caches()self.assertEqual(email.address,'adim@logilab.fr')deftest_password_confirm(self):"""test creation of two linked entities """withself.admin_access.web_request()asreq:user=req.userreq.form={'eid':'X','__cloned_eid:X':text_type(user.eid),'__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject','login-subject:X':u'toto','upassword-subject:X':u'toto',}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)self.assertEqual({'upassword-subject':u'password and confirmation don\'t match'},cm.exception.errors)req.form={'__cloned_eid:X':text_type(user.eid),'eid':'X','__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject','login-subject:X':u'toto','upassword-subject:X':u'toto','upassword-subject-confirm:X':u'tutu',}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)self.assertEqual({'upassword-subject':u'password and confirmation don\'t match'},cm.exception.errors)deftest_interval_bound_constraint_success(self):withself.admin_access.repo_cnx()ascnx:feid=cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',{'data':Binary(b'yo')})[0][0]cnx.commit()withself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':['X'],'__type:X':'Salesterm','_cw_entity_fields:X':'amount-subject,described_by_test-subject','amount-subject:X':u'-10','described_by_test-subject:X':text_type(feid),}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)cm.exception.translate(text_type)self.assertEqual({'amount-subject':'value -10 must be >= 0'},cm.exception.errors)withself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':['X'],'__type:X':'Salesterm','_cw_entity_fields:X':'amount-subject,described_by_test-subject','amount-subject:X':u'110','described_by_test-subject:X':text_type(feid),}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)cm.exception.translate(text_type)self.assertEqual(cm.exception.errors,{'amount-subject':'value 110 must be <= 100'})withself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':['X'],'__type:X':'Salesterm','_cw_entity_fields:X':'amount-subject,described_by_test-subject','amount-subject:X':u'10','described_by_test-subject:X':text_type(feid),}self.expect_redirect_handle_request(req,'edit')# should be redirected on the created#eid = params['rql'].split()[-1]e=req.execute('Salesterm X').get_entity(0,0)self.assertEqual(e.amount,10)deftest_interval_bound_constraint_validateform(self):"""Test the FormValidatorController controller on entity with constrained attributes"""withself.admin_access.repo_cnx()ascnx:feid=cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',{'data':Binary(b'yo')})[0][0]seid=cnx.create_entity('Salesterm',amount=0,described_by_test=feid).eidcnx.commit()# ensure a value that violate a constraint is properly detectedwithself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':[text_type(seid)],'__type:%s'%seid:'Salesterm','_cw_entity_fields:%s'%seid:'amount-subject','amount-subject:%s'%seid:u'-10',}self.assertMultiLineEqual('''<script type="text/javascript"> window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);</script>'''%seid,self.ctrl_publish(req,'validateform').decode('ascii'))# ensure a value that comply a constraint is properly processedwithself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':[text_type(seid)],'__type:%s'%seid:'Salesterm','_cw_entity_fields:%s'%seid:'amount-subject','amount-subject:%s'%seid:u'20',}self.assertMultiLineEqual('''<script type="text/javascript"> window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);</script>''',self.ctrl_publish(req,'validateform').decode('ascii'))self.assertEqual(20,req.execute('Any V WHERE X amount V, X eid %(eid)s',{'eid':seid})[0][0])withself.admin_access.web_request(rollbackfirst=True)asreq:req.form={'eid':['X'],'__type:X':'Salesterm','_cw_entity_fields:X':'amount-subject,described_by_test-subject','amount-subject:X':u'0','described_by_test-subject:X':text_type(feid),}# ensure a value that is modified in an operation on a modify# hook works as it should (see# https://www.cubicweb.org/ticket/2509729 )classMyOperation(Operation):defprecommit_event(self):self.entity.cw_set(amount=-10)classValidationErrorInOpAfterHook(Hook):__regid__='valerror-op-after-hook'__select__=Hook.__select__&is_instance('Salesterm')events=('after_add_entity',)def__call__(self):MyOperation(self._cw,entity=self.entity)withself.temporary_appobjects(ValidationErrorInOpAfterHook):self.assertMultiLineEqual('''<script type="text/javascript"> window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);</script>''',self.ctrl_publish(req,'validateform').decode('ascii'))self.assertMultiLineEqual('''<script type="text/javascript"> window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);</script>''',self.ctrl_publish(req,'validateform').decode('ascii'))deftest_req_pending_insert(self):"""make sure req's pending insertions are taken into account"""withself.admin_access.web_request()asreq:tmpgroup=req.create_entity('CWGroup',name=u"test")user=req.userreq.cnx.commit()withself.admin_access.web_request(**req_form(user))asreq:req.session.data['pending_insert']=set([(user.eid,'in_group',tmpgroup.eid)])self.expect_redirect_handle_request(req,'edit')usergroups=[gnameforgname,inreq.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',{'u':user.eid})]self.assertCountEqual(usergroups,['managers','test'])self.assertEqual(get_pending_inserts(req),[])deftest_req_pending_delete(self):"""make sure req's pending deletions are taken into account"""withself.admin_access.web_request()asreq:user=req.usergroupeid=req.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',{'x':user.eid})[0][0]usergroups=[gnameforgname,inreq.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',{'u':user.eid})]# just make sure everything was set correctlyself.assertCountEqual(usergroups,['managers','test'])req.cnx.commit()# now try to delete the relationwithself.admin_access.web_request(**req_form(user))asreq:req.session.data['pending_delete']=set([(user.eid,'in_group',groupeid)])self.expect_redirect_handle_request(req,'edit')usergroups=[gnameforgname,inreq.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',{'u':user.eid})]self.assertCountEqual(usergroups,['managers'])self.assertEqual(get_pending_deletes(req),[])deftest_redirect_apply_button(self):withself.admin_access.web_request()asreq:redirectrql=rql_for_eid(4012)# whateverreq.form={'eid':'A','__maineid':'A','__type:A':'BlogEntry','_cw_entity_fields:A':'content-subject,title-subject','content-subject:A':u'"13:03:43"','title-subject:A':u'huuu','__redirectrql':redirectrql,'__redirectvid':'primary','__redirectparams':'toto=tutu&tata=titi','__form_id':'edition','__action_apply':'',}path,params=self.expect_redirect_handle_request(req,'edit')self.assertTrue(path.startswith('blogentry/'))eid=path.split('/')[1]self.assertEqual(params['vid'],'edition')self.assertNotEqual(int(eid),4012)self.assertEqual(params['__redirectrql'],redirectrql)self.assertEqual(params['__redirectvid'],'primary')self.assertEqual(params['__redirectparams'],'toto=tutu&tata=titi')deftest_redirect_ok_button(self):withself.admin_access.web_request()asreq:redirectrql=rql_for_eid(4012)# whateverreq.form={'eid':'A','__maineid':'A','__type:A':'BlogEntry','_cw_entity_fields:A':'content-subject,title-subject','content-subject:A':u'"13:03:43"','title-subject:A':u'huuu','__redirectrql':redirectrql,'__redirectvid':'primary','__redirectparams':'toto=tutu&tata=titi','__form_id':'edition',}path,params=self.expect_redirect_handle_request(req,'edit')self.assertEqual(path,'view')self.assertEqual(params['rql'],redirectrql)self.assertEqual(params['vid'],'primary')self.assertEqual(params['tata'],'titi')self.assertEqual(params['toto'],'tutu')deftest_redirect_delete_button(self):withself.admin_access.web_request()asreq:eid=req.create_entity('BlogEntry',title=u'hop',content=u'hop').eidreq.form={'eid':text_type(eid),'__type:%s'%eid:'BlogEntry','__action_delete':''}path,params=self.expect_redirect_handle_request(req,'edit')self.assertEqual(path,'blogentry')self.assertIn('_cwmsgid',params)eid=req.create_entity('EmailAddress',address=u'hop@logilab.fr').eidreq.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',{'x':req.user.eid,'e':eid})req.cnx.commit()req.form={'eid':text_type(eid),'__type:%s'%eid:'EmailAddress','__action_delete':''}path,params=self.expect_redirect_handle_request(req,'edit')self.assertEqual(path,'cwuser/admin')self.assertIn('_cwmsgid',params)eid1=req.create_entity('BlogEntry',title=u'hop',content=u'hop').eideid2=req.create_entity('EmailAddress',address=u'hop@logilab.fr').eidreq.form={'eid':[text_type(eid1),text_type(eid2)],'__type:%s'%eid1:'BlogEntry','__type:%s'%eid2:'EmailAddress','__action_delete':''}path,params=self.expect_redirect_handle_request(req,'edit')self.assertEqual(path,'view')self.assertIn('_cwmsgid',params)deftest_simple_copy(self):withself.admin_access.web_request()asreq:blog=req.create_entity('Blog',title=u'my-blog')blogentry=req.create_entity('BlogEntry',title=u'entry1',content=u'content1',entry_of=blog)req.form={'__maineid':'X','eid':'X','__cloned_eid:X':blogentry.eid,'__type:X':'BlogEntry','_cw_entity_fields:X':'title-subject,content-subject','title-subject:X':u'entry1-copy','content-subject:X':u'content1',}self.expect_redirect_handle_request(req,'edit')blogentry2=req.find('BlogEntry',title=u'entry1-copy').one()self.assertEqual(blogentry2.entry_of[0].eid,blog.eid)deftest_skip_copy_for(self):withself.admin_access.web_request()asreq:blog=req.create_entity('Blog',title=u'my-blog')blogentry=req.create_entity('BlogEntry',title=u'entry1',content=u'content1',entry_of=blog)blogentry.__class__.cw_skip_copy_for=[('entry_of','subject')]try:req.form={'__maineid':'X','eid':'X','__cloned_eid:X':blogentry.eid,'__type:X':'BlogEntry','_cw_entity_fields:X':'title-subject,content-subject','title-subject:X':u'entry1-copy','content-subject:X':u'content1',}self.expect_redirect_handle_request(req,'edit')blogentry2=req.find('BlogEntry',title=u'entry1-copy').one()# entry_of should not be copiedself.assertEqual(len(blogentry2.entry_of),0)finally:blogentry.__class__.cw_skip_copy_for=[]deftest_nonregr_eetype_etype_editing(self):"""non-regression test checking that a manager user can edit a CWEType entity """withself.admin_access.web_request()asreq:groupeids=sorted(eidforeid,inreq.execute('CWGroup G ''WHERE G name in ("managers", "users")'))groups=[text_type(eid)foreidingroupeids]cwetypeeid=req.execute('CWEType X WHERE X name "CWEType"')[0][0]basegroups=[text_type(eid)foreid,inreq.execute('CWGroup G ''WHERE X read_permission G, X eid %(x)s',{'x':cwetypeeid})]cwetypeeid=text_type(cwetypeeid)req.form={'eid':cwetypeeid,'__type:'+cwetypeeid:'CWEType','_cw_entity_fields:'+cwetypeeid:'name-subject,final-subject,description-subject,read_permission-subject','name-subject:'+cwetypeeid:u'CWEType','final-subject:'+cwetypeeid:'','description-subject:'+cwetypeeid:u'users group','read_permission-subject:'+cwetypeeid:groups,}try:self.expect_redirect_handle_request(req,'edit')e=req.execute('Any X WHERE X eid %(x)s',{'x':cwetypeeid}).get_entity(0,0)self.assertEqual(e.name,'CWEType')self.assertEqual(sorted(g.eidforgine.read_permission),groupeids)finally:# restorereq.execute('SET X read_permission Y WHERE X name "CWEType", ''Y eid IN (%s), NOT X read_permission Y'%(','.join(basegroups)))req.cnx.commit()deftest_nonregr_strange_text_input(self):"""non-regression test checking text input containing "13:03:43" this seems to be postgres (tsearch?) specific """withself.admin_access.web_request()asreq:req.form={'eid':'A','__maineid':'A','__type:A':'BlogEntry','_cw_entity_fields:A':'title-subject,content-subject','title-subject:A':u'"13:03:40"','content-subject:A':u'"13:03:43"',}path,_params=self.expect_redirect_handle_request(req,'edit')self.assertTrue(path.startswith('blogentry/'))eid=path.split('/')[1]e=req.execute('Any C, T WHERE C eid %(x)s, C content T',{'x':eid}).get_entity(0,0)self.assertEqual(e.title,'"13:03:40"')self.assertEqual(e.content,'"13:03:43"')deftest_nonregr_multiple_empty_email_addr(self):withself.admin_access.web_request()asreq:gueid=req.execute('CWGroup G WHERE G name "users"')[0][0]req.form={'eid':['X','Y'],'__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject,in_group-subject','login-subject:X':u'adim','upassword-subject:X':u'toto','upassword-subject-confirm:X':u'toto','in_group-subject:X':repr(gueid),'__type:Y':'EmailAddress','_cw_entity_fields:Y':'address-subject,alias-subject,use_email-object','address-subject:Y':u'','alias-subject:Y':u'','use_email-object:Y':'X',}withself.assertRaises(ValidationError)ascm:self.ctrl_publish(req)self.assertEqual(cm.exception.errors,{'address-subject':u'required field'})deftest_nonregr_copy(self):withself.admin_access.web_request()asreq:user=req.userreq.form={'__maineid':'X','eid':'X','__cloned_eid:X':user.eid,'__type:X':'CWUser','_cw_entity_fields:X':'login-subject,upassword-subject','login-subject:X':u'toto','upassword-subject:X':u'toto','upassword-subject-confirm:X':u'toto',}path,_params=self.expect_redirect_handle_request(req,'edit')self.assertEqual(path,'cwuser/toto')e=req.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0,0)self.assertEqual(e.login,'toto')self.assertEqual(e.in_group[0].name,'managers')deftest_nonregr_rollback_on_validation_error(self):withself.admin_access.web_request()asreq:p=self.create_user(req,u"doe")# do not try to skip 'primary_email' for this testold_skips=p.__class__.skip_copy_forp.__class__.skip_copy_for=()try:e=req.create_entity('EmailAddress',address=u'doe@doe.com')req.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',{'p':p.eid,'e':e.eid})req.form={'eid':'X','__cloned_eid:X':p.eid,'__type:X':'CWUser','_cw_entity_fields:X':'login-subject,surname-subject','login-subject':u'dodo','surname-subject:X':u'Boom','__errorurl':"whatever but required",}# try to emulate what really happens in the web application# 1/ validate form => EditController.publish raises a ValidationError# which fires a Redirect# 2/ When re-publishing the copy form, the publisher implicitly commitstry:self.app_handle_request(req,'edit')exceptRedirect:req.form['rql']='Any X WHERE X eid %s'%p.eidreq.form['vid']='copy'self.app_handle_request(req,'view')rset=req.execute('CWUser P WHERE P surname "Boom"')self.assertEqual(len(rset),0)finally:p.__class__.skip_copy_for=old_skipsdeftest_regr_inlined_forms(self):withself.admin_access.web_request()asreq:self.schema['described_by_test'].inlined=Falsetry:req.data['eidmap']={}req.data['pending_others']=set()req.data['pending_inlined']={}req.form={'eid':['X','Y'],'__maineid':'X','__type:X':'Salesterm','_cw_entity_fields:X':'described_by_test-subject','described_by_test-subject:X':'Y','__type:Y':'File','_cw_entity_fields:Y':'data-subject','data-subject:Y':(u'coucou.txt',Binary(b'coucou')),}values_by_eid=dict((eid,req.extract_entity_params(eid,minparams=2))foreidinreq.edited_eids())editctrl=self.vreg['controllers'].select('edit',req)# don't call publish to enforce select ordereditctrl.errors=[]editctrl._to_create={}editctrl.edit_entity(values_by_eid['X'])# #3064653 raise ValidationErroreditctrl.edit_entity(values_by_eid['Y'])finally:self.schema['described_by_test'].inlined=FalseclassReportBugControllerTC(CubicWebTC):deftest_usable_by_guest(self):withself.new_access(u'anon').web_request()asreq:self.assertRaises(NoSelectableObject,self.vreg['controllers'].select,'reportbug',req)withself.new_access(u'anon').web_request(description='hop')asreq:self.vreg['controllers'].select('reportbug',req)classAjaxControllerTC(CubicWebTC):tested_controller='ajax'defctrl(self,req=None):req=reqorself.request(url='http://whatever.fr/')returnself.vreg['controllers'].select(self.tested_controller,req)defsetup_database(self):withself.admin_access.repo_cnx()ascnx:self.pytag=cnx.create_entity('Tag',name=u'python')self.cubicwebtag=cnx.create_entity('Tag',name=u'cubicweb')self.john=self.create_user(cnx,u'John')cnx.commit()## tests ##################################################################deftest_simple_exec(self):withself.admin_access.web_request(rql='CWUser P WHERE P login "John"',pageid='123',fname='view')asreq:ctrl=self.ctrl(req)rset=self.john.as_rset()rset.req=reqsource=ctrl.publish()self.assertTrue(source.startswith(b'<div>'))# def test_json_exec(self):# rql = 'Any T,N WHERE T is Tag, T name N'# ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))# self.assertEqual(ctrl.publish(),# json_dumps(self.execute(rql).rows))deftest_remote_add_existing_tag(self):withself.remote_calling('tag_entity',self.john.eid,['python'])as(_,req):self.assertCountEqual([tnamefortname,inreq.execute('Any N WHERE T is Tag, T name N')],['python','cubicweb'])self.assertEqual(req.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,[['python']])deftest_remote_add_new_tag(self):withself.remote_calling('tag_entity',self.john.eid,['javascript'])as(_,req):self.assertCountEqual([tnamefortname,inreq.execute('Any N WHERE T is Tag, T name N')],['python','cubicweb','javascript'])self.assertEqual(req.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,[['javascript']])deftest_maydel_perms(self):"""Check that AjaxEditRelationCtxComponent calls rdef.check with a sufficient context"""withself.remote_calling('tag_entity',self.john.eid,['python'])as(_,req):req.cnx.commit()withself.temporary_permissions((self.schema['tags'].rdefs['Tag','CWUser'],{'delete':(RRQLExpression('S owned_by U'),)},)):withself.admin_access.web_request(rql='CWUser P WHERE P login "John"',pageid='123',fname='view')asreq:ctrl=self.ctrl(req)rset=self.john.as_rset()rset.req=reqsource=ctrl.publish()# maydel jscallself.assertIn(b'ajaxBoxRemoveLinkedEntity',source)deftest_pending_insertion(self):withself.remote_calling('add_pending_inserts',[['12','tags','13']])as(_,req):deletes=get_pending_deletes(req)self.assertEqual(deletes,[])inserts=get_pending_inserts(req)self.assertEqual(inserts,['12:tags:13'])withself.remote_calling('add_pending_inserts',[['12','tags','14']])as(_,req):deletes=get_pending_deletes(req)self.assertEqual(deletes,[])inserts=get_pending_inserts(req)self.assertCountEqual(inserts,['12:tags:13','12:tags:14'])inserts=get_pending_inserts(req,12)self.assertCountEqual(inserts,['12:tags:13','12:tags:14'])inserts=get_pending_inserts(req,13)self.assertEqual(inserts,['12:tags:13'])inserts=get_pending_inserts(req,14)self.assertEqual(inserts,['12:tags:14'])req.remove_pending_operations()deftest_pending_deletion(self):withself.remote_calling('add_pending_delete',['12','tags','13'])as(_,req):inserts=get_pending_inserts(req)self.assertEqual(inserts,[])deletes=get_pending_deletes(req)self.assertEqual(deletes,['12:tags:13'])withself.remote_calling('add_pending_delete',['12','tags','14'])as(_,req):inserts=get_pending_inserts(req)self.assertEqual(inserts,[])deletes=get_pending_deletes(req)self.assertCountEqual(deletes,['12:tags:13','12:tags:14'])deletes=get_pending_deletes(req,12)self.assertCountEqual(deletes,['12:tags:13','12:tags:14'])deletes=get_pending_deletes(req,13)self.assertEqual(deletes,['12:tags:13'])deletes=get_pending_deletes(req,14)self.assertEqual(deletes,['12:tags:14'])req.remove_pending_operations()deftest_remove_pending_operations(self):withself.remote_calling('add_pending_delete',['12','tags','13']):passwithself.remote_calling('add_pending_inserts',[['12','tags','14']])as(_,req):inserts=get_pending_inserts(req)self.assertEqual(inserts,['12:tags:14'])deletes=get_pending_deletes(req)self.assertEqual(deletes,['12:tags:13'])req.remove_pending_operations()self.assertEqual(get_pending_deletes(req),[])self.assertEqual(get_pending_inserts(req),[])deftest_add_inserts(self):withself.remote_calling('add_pending_inserts',[('12','tags','13'),('12','tags','14')])as(_,req):inserts=get_pending_inserts(req)self.assertCountEqual(inserts,['12:tags:13','12:tags:14'])req.remove_pending_operations()# silly testsdeftest_external_resource(self):withself.remote_calling('external_resource','RSS_LOGO')as(res,_):self.assertEqual(json_dumps(self.config.uiprops['RSS_LOGO']).encode('ascii'),res)deftest_i18n(self):withself.remote_calling('i18n',['bimboom'])as(res,_):self.assertEqual(json_dumps(['bimboom']).encode('ascii'),res)deftest_format_date(self):withself.remote_calling('format_date','2007-01-01 12:00:00')as(res,_):self.assertEqual(json_dumps('2007/01/01').encode('ascii'),res)deftest_ajaxfunc_noparameter(self):@ajaxfuncdeffoo(self,x,y):return'hello'self.assertEqual(foo(object,1,2),'hello')appobject=foo.__appobject__self.assertTrue(issubclass(appobject,AjaxFunction))self.assertEqual(appobject.__regid__,'foo')self.assertEqual(appobject.check_pageid,False)self.assertEqual(appobject.output_type,None)withself.admin_access.web_request()asreq:f=appobject(req)self.assertEqual(f(12,13),'hello')deftest_ajaxfunc_checkpageid(self):@ajaxfunc(check_pageid=True)deffoo(self,x,y):return'hello'self.assertEqual(foo(object,1,2),'hello')appobject=foo.__appobject__self.assertTrue(issubclass(appobject,AjaxFunction))self.assertEqual(appobject.__regid__,'foo')self.assertEqual(appobject.check_pageid,True)self.assertEqual(appobject.output_type,None)# no pageidwithself.admin_access.web_request()asreq:f=appobject(req)self.assertRaises(RemoteCallFailed,f,12,13)deftest_ajaxfunc_json(self):@ajaxfunc(output_type='json')deffoo(self,x,y):returnx+yself.assertEqual(foo(object,1,2),3)appobject=foo.__appobject__self.assertTrue(issubclass(appobject,AjaxFunction))self.assertEqual(appobject.__regid__,'foo')self.assertEqual(appobject.check_pageid,False)self.assertEqual(appobject.output_type,'json')# no pageidwithself.admin_access.web_request()asreq:f=appobject(req)self.assertEqual(f(12,13),'25')classJSonControllerTC(AjaxControllerTC):# NOTE: this class performs the same tests as AjaxController but with# deprecated 'json' controller (i.e. check backward compatibility)tested_controller='json'defsetUp(self):super(JSonControllerTC,self).setUp()self.exposed_remote_funcs=[fnameforfnameindir(JSonController)iffname.startswith('js_')]deftearDown(self):super(JSonControllerTC,self).tearDown()forfuncnameindir(JSonController):# remove functions added dynamically during testsiffuncname.startswith('js_')andfuncnamenotinself.exposed_remote_funcs:delattr(JSonController,funcname)deftest_monkeypatch_jsoncontroller(self):withself.assertRaises(RemoteCallFailed):withself.remote_calling('foo'):pass@monkeypatch(JSonController)defjs_foo(self):returnu'hello'withself.remote_calling('foo')as(res,_):self.assertEqual(res,b'hello')deftest_monkeypatch_jsoncontroller_xhtmlize(self):withself.assertRaises(RemoteCallFailed):withself.remote_calling('foo'):pass@monkeypatch(JSonController)@xhtmlizedefjs_foo(self):returnu'hello'withself.remote_calling('foo')as(res,_):self.assertEqual(b'<div>hello</div>',res)deftest_monkeypatch_jsoncontroller_jsonize(self):withself.assertRaises(RemoteCallFailed):withself.remote_calling('foo'):pass@monkeypatch(JSonController)@jsonizedefjs_foo(self):return12withself.remote_calling('foo')as(res,_):self.assertEqual(res,b'12')deftest_monkeypatch_jsoncontroller_stdfunc(self):@monkeypatch(JSonController)@jsonizedefjs_reledit_form(self):return12withself.remote_calling('reledit_form')as(res,_):self.assertEqual(res,b'12')classUndoControllerTC(CubicWebTC):defsetUp(self):classConnection(OldConnection):"""Force undo feature to be turned on in all case"""undo_actions=property(lambdatx:True,lambdax,y:None)cubicweb.server.session.Connection=Connectionsuper(UndoControllerTC,self).setUp()deftearDown(self):super(UndoControllerTC,self).tearDown()cubicweb.server.session.Connection=OldConnectiondefsetup_database(self):withself.admin_access.repo_cnx()ascnx:self.toto=self.create_user(cnx,u'toto',password=u'toto',groups=('users',),commit=False)self.txuuid_toto=cnx.commit()self.toto_email=cnx.create_entity('EmailAddress',address=u'toto@logilab.org',reverse_use_email=self.toto)self.txuuid_toto_email=cnx.commit()deftest_no_such_transaction(self):withself.admin_access.web_request()asreq:txuuid=u"12345acbd"req.form['txuuid']=txuuidcontroller=self.vreg['controllers'].select('undo',req)withself.assertRaises(tx.NoSuchTransaction)ascm:result=controller.publish(rset=None)self.assertEqual(cm.exception.txuuid,txuuid)defassertURLPath(self,url,expected_path,expected_params=None):""" This assert that the path part of `url` matches expected path TODO : implement assertion on the expected_params too """withself.admin_access.web_request()asreq:scheme,netloc,path,query,fragment=urlsplit(url)query_dict=parse_qs(query)expected_url=urljoin(req.base_url(),expected_path)self.assertEqual(urlunsplit((scheme,netloc,path,None,None)),expected_url)deftest_redirect_redirectpath(self):"Check that the potential __redirectpath is honored"withself.admin_access.web_request()asreq:txuuid=self.txuuid_toto_emailreq.form['txuuid']=txuuidrpath="toto"req.form['__redirectpath']=rpathcontroller=self.vreg['controllers'].select('undo',req)withself.assertRaises(Redirect)ascm:result=controller.publish(rset=None)self.assertURLPath(cm.exception.location,rpath)deftest_redirect_default(self):withself.admin_access.web_request()asreq:txuuid=self.txuuid_toto_emailreq.form['txuuid']=txuuidreq.session.data['breadcrumbs']=[urljoin(req.base_url(),path)forpathin('tata','toto',)]controller=self.vreg['controllers'].select('undo',req)withself.assertRaises(Redirect)ascm:result=controller.publish(rset=None)self.assertURLPath(cm.exception.location,'toto')classLoginControllerTC(CubicWebTC):deftest_login_with_dest(self):withself.admin_access.web_request()asreq:req.form={'postlogin_path':'elephants/babar'}withself.assertRaises(Redirect)ascm:self.ctrl_publish(req,ctrl='login')self.assertEqual(req.build_url('elephants/babar'),cm.exception.location)deftest_login_no_dest(self):withself.admin_access.web_request()asreq:withself.assertRaises(Redirect)ascm:self.ctrl_publish(req,ctrl='login')self.assertEqual(req.base_url(),cm.exception.location)if__name__=='__main__':unittest_main()