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