unittest_views_basecontrollers.py 53.5 KB
Newer Older
1
# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
3
4
5
6
7
8
9
10
# 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.
#
11
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
12
13
14
15
16
17
# 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/>.
18
"""cubicweb.web.views.basecontrollers unit tests"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
19

20
21
import time

22
from six import text_type
23
from six.moves.urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs
24
25
26

import lxml

27
from logilab.common.testlib import unittest_main
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28

29
from cubicweb import Binary, NoSelectableObject, ValidationError, transaction as tx
30
from cubicweb.schema import RRQLExpression
31
from cubicweb.predicates import is_instance
32
from cubicweb.devtools.testlib import CubicWebTC
33
from cubicweb.devtools.webtest import CubicWebTestTC
34
from cubicweb.devtools.httptest import CubicWebServerTC
35
from cubicweb.utils import json_dumps
Sylvain Thénault's avatar
Sylvain Thénault committed
36
from cubicweb.uilib import rql_for_eid
37
from cubicweb.web import Redirect, RemoteCallFailed, http_headers, formfields as ff
Sylvain Thénault's avatar
Sylvain Thénault committed
38
from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
39
from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
40
from cubicweb.server.session import Connection
41
from cubicweb.server.hook import Hook, Operation
42

43
44
45

class ViewControllerTC(CubicWebTestTC):
    def test_view_ctrl_with_valid_cache_headers(self):
46
        now = time.time()
47
48
49
        resp = self.webapp.get('/manage')
        self.assertEqual(resp.etag, 'manage/guests')
        self.assertEqual(resp.status_code, 200)
50
        self.assertGreaterEqual(http_headers.parseDateTime(resp.headers['Last-Modified']), int(now))
51
52
53
54
55
56
57
        cache_headers = {'if-modified-since': resp.headers['Last-Modified'],
                         'if-none-match': resp.etag}
        resp = self.webapp.get('/manage', headers=cache_headers)
        self.assertEqual(resp.status_code, 304)
        self.assertEqual(len(resp.body), 0)


Sylvain Thénault's avatar
Sylvain Thénault committed
58
59
def req_form(user):
    return {'eid': [str(user.eid)],
60
            '_cw_entity_fields:%s' % user.eid: '_cw_generic_field',
Sylvain Thénault's avatar
Sylvain Thénault committed
61
62
            '__type:%s' % user.eid: user.__regid__
            }
Adrien Di Mascio's avatar
Adrien Di Mascio committed
63

64

65
class EditControllerTC(CubicWebTC):
66

Adrien Di Mascio's avatar
Adrien Di Mascio committed
67
    def setUp(self):
68
        CubicWebTC.setUp(self)
69
        self.assertIn('users', self.schema.eschema('CWGroup').get_groups('read'))
70

Adrien Di Mascio's avatar
Adrien Di Mascio committed
71
    def tearDown(self):
72
        CubicWebTC.tearDown(self)
73
        self.assertIn('users', self.schema.eschema('CWGroup').get_groups('read'))
74

Adrien Di Mascio's avatar
Adrien Di Mascio committed
75
76
77
    def test_noparam_edit(self):
        """check behaviour of this controller without any form parameter
        """
78
79
80
81
        with self.admin_access.web_request() as req:
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
            self.assertEqual(cm.exception.errors, {None: u'no selected entities'})
82

Adrien Di Mascio's avatar
Adrien Di Mascio committed
83
84
    def test_validation_unique(self):
        """test creation of two linked entities
85
        """
86
87
88
89
90
91
        with self.admin_access.web_request() as req:
            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',
92
                    }
93
94
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
95
96
97
98
99
100
            cm.exception.translate(text_type)
            expected = {
                '': u'some relations violate a unicity constraint',
                'login': u'login is part of violated unicity constraint',
            }
            self.assertEqual(cm.exception.errors, expected)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
101

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    def test_simultaneous_edition_only_one_commit(self):
        """ Allow two simultaneous edit view of the same entity as long as only one commits
        """
        with self.admin_access.web_request() as req:
            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]

        with self.admin_access.web_request() as req2:
            form2 = self.vreg['views'].select('edition', req, rset=e.as_rset(), row=0)

        with self.admin_access.web_request(**dict(html_form.form_values())) as req:
            path, args = self.expect_redirect_handle_request(req, path='edit')
            self.assertEqual(path, expected_path)

    def test_simultaneous_edition_refuse_second_commit(self):
        """ Disallow committing changes to an entity edited in between """
        with self.admin_access.web_request() as req:
            e = req.create_entity('BlogEntry', title=u'cubicweb.org', content=u"hop")
            eid = e.eid
            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]

        with self.admin_access.web_request() as req2:
            e = req2.entity_from_eid(eid)
            e.cw_set(content = u"hip")
            req2.cnx.commit()

        form_field_name = "content-subject:%d" % eid
        form_values = dict(html_form.form_values())
        assert form_field_name in form_values
        form_values[form_field_name] = u'yep'
        with self.admin_access.web_request(**form_values) as req:
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
            reported_eid, dict_info = cm.exception.args
            self.assertEqual(reported_eid, eid)
            self.assertIn(None, dict_info)
            self.assertIn("has changed since you started to edit it.", dict_info[None])

Adrien Di Mascio's avatar
Adrien Di Mascio committed
145
146
147
    def test_user_editing_itself(self):
        """checking that a manager user can edit itself
        """
148
149
150
151
        with self.admin_access.web_request() as req:
            user = req.user
            groupeids = [eid for eid, in req.execute('CWGroup G WHERE G name '
                                                     'in ("managers", "users")')]
152
153
            groups = [text_type(eid) for eid in groupeids]
            eid = text_type(user.eid)
154
155
156
            req.form = {
                'eid': eid, '__type:'+eid: 'CWUser',
                '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
157
                'login-subject:'+eid:     text_type(user.login),
158
159
160
161
162
163
164
165
166
167
168
                '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.eid for g in e.in_group], groupeids)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
169
170

    def test_user_can_change_its_password(self):
171
        with self.admin_access.repo_cnx() as cnx:
172
            self.create_user(cnx, u'user')
173
            cnx.commit()
174
        with self.new_access(u'user').web_request() as req:
175
            eid = text_type(req.user.eid)
176
177
178
179
180
181
182
183
184
185
186
            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 instance
            self.assertEqual(path, 'cwuser/user')
            self.assertNotIn('vid', params)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
187

Sylvain Thénault's avatar
Sylvain Thénault committed
188
    def test_user_editing_itself_no_relation(self):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
189
190
191
        """checking we can edit an entity without specifying some required
        relations (meaning no changes)
        """
192
193
194
        with self.admin_access.web_request() as req:
            user = req.user
            groupeids = [g.eid for g in user.in_group]
195
            eid = text_type(user.eid)
196
197
198
199
            req.form = {
                'eid':       eid,
                '__type:'+eid:    'CWUser',
                '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
200
                'login-subject:'+eid:     text_type(user.login),
201
202
203
204
205
206
207
208
209
210
211
                '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.eid for g in e.in_group], groupeids)
            self.assertEqual(e.cw_adapt_to('IWorkflowable').state, 'activated')
212
213


Adrien Di Mascio's avatar
Adrien Di Mascio committed
214
    def test_create_multiple_linked(self):
215
216
217
218
219
220
221
222
        with self.admin_access.web_request() as req:
            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',
223
                        'in_group-subject:X': text_type(gueid),
224
225
226
227
228
229
230
231
232
233
234
235
236

                        '__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 person
            self.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')
237

238
    def test_create_mandatory_inlined(self):
239
240
        with self.admin_access.web_request() as req:
            req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
241

242
243
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': '',
244

245
246
                        '__type:Y': 'File',
                        '_cw_entity_fields:Y': 'data-subject,described_by_test-object',
247
                        'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
248
249
250
251
252
253
254
255
256
257
258
259
260
                        '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)
261
262

    def test_create_mandatory_inlined2(self):
263
264
        with self.admin_access.web_request() as req:
            req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
265

266
267
268
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': 'described_by_test-subject',
                        'described_by_test-subject:X': 'Y',
269

270
271
                        '__type:Y': 'File',
                        '_cw_entity_fields:Y': 'data-subject',
272
                        'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
273
274
275
276
277
278
279
280
281
282
283
284
                        }
            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)
285

286
287
288
    def test_edit_mandatory_inlined3_object(self):
        # non regression test for #3120495. Without the fix, leads to
        # "unhashable type: 'list'" error
289
        with self.admin_access.web_request() as req:
290
            cwrelation = text_type(req.execute('CWEType X WHERE X name "CWSource"')[0][0])
291
            req.form = {'eid': [cwrelation], '__maineid' : cwrelation,
292

293
294
295
296
297
298
299
                        '__type:'+cwrelation: 'CWEType',
                        '_cw_entity_fields:'+cwrelation: 'to_entity-object',
                        'to_entity-object:'+cwrelation: [9999, 9998],
                        }
            with req.cnx.deny_all_hooks_but():
                path, _params = self.expect_redirect_handle_request(req, 'edit')
            self.assertTrue(path.startswith('cwetype/CWSource'), path)
300

Adrien Di Mascio's avatar
Adrien Di Mascio committed
301
    def test_edit_multiple_linked(self):
302
        with self.admin_access.web_request() as req:
303
            peid = text_type(self.create_user(req, u'adim').eid)
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
            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 person
            self.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:
323
            emaileid = text_type(email.eid)
324
325
326
327
328
329
330
331
332
333
334
335
336
337
            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')
338

Adrien Di Mascio's avatar
Adrien Di Mascio committed
339
340
    def test_password_confirm(self):
        """test creation of two linked entities
341
        """
342
343
344
        with self.admin_access.web_request() as req:
            user = req.user
            req.form = {'eid': 'X',
345
                        '__cloned_eid:X': text_type(user.eid), '__type:X': 'CWUser',
346
347
348
349
350
351
352
353
                        '_cw_entity_fields:X': 'login-subject,upassword-subject',
                        'login-subject:X': u'toto',
                        'upassword-subject:X': u'toto',
                        }
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
            self.assertEqual({'upassword-subject': u'password and confirmation don\'t match'},
                             cm.exception.errors)
354
            req.form = {'__cloned_eid:X': text_type(user.eid),
355
356
357
358
359
360
361
362
363
364
                        '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',
                        }
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
            self.assertEqual({'upassword-subject': u'password and confirmation don\'t match'},
                             cm.exception.errors)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
365
366
367


    def test_interval_bound_constraint_success(self):
368
369
        with self.admin_access.repo_cnx() as cnx:
            feid = cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
370
                               {'data': Binary(b'yo')})[0][0]
371
372
373
374
375
376
377
            cnx.commit()

        with self.admin_access.web_request(rollbackfirst=True) as req:
            req.form = {'eid': ['X'],
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
                        'amount-subject:X': u'-10',
378
                        'described_by_test-subject:X': text_type(feid),
379
                    }
380
381
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
382
            cm.exception.translate(text_type)
383
384
385
386
387
388
389
390
            self.assertEqual({'amount-subject': 'value -10 must be >= 0'},
                             cm.exception.errors)

        with self.admin_access.web_request(rollbackfirst=True) as req:
            req.form = {'eid': ['X'],
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
                        'amount-subject:X': u'110',
391
                        'described_by_test-subject:X': text_type(feid),
392
393
394
                        }
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
395
            cm.exception.translate(text_type)
396
397
398
399
400
401
402
            self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})

        with self.admin_access.web_request(rollbackfirst=True) as req:
            req.form = {'eid': ['X'],
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
                        'amount-subject:X': u'10',
403
                        'described_by_test-subject:X': text_type(feid),
404
405
406
407
408
409
                        }
            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)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
410

411
412
413
    def test_interval_bound_constraint_validateform(self):
        """Test the FormValidatorController controller on entity with
        constrained attributes"""
414
415
        with self.admin_access.repo_cnx() as cnx:
            feid = cnx.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
416
                               {'data': Binary(b'yo')})[0][0]
417
418
            seid = cnx.create_entity('Salesterm', amount=0, described_by_test=feid).eid
            cnx.commit()
419
420

        # ensure a value that violate a constraint is properly detected
421
        with self.admin_access.web_request(rollbackfirst=True) as req:
422
            req.form = {'eid': [text_type(seid)],
423
424
425
426
427
                        '__type:%s'%seid: 'Salesterm',
                        '_cw_entity_fields:%s'%seid: 'amount-subject',
                        'amount-subject:%s'%seid: u'-10',
                    }
            self.assertMultiLineEqual('''<script type="text/javascript">
428
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
Julien Cristau's avatar
Julien Cristau committed
429
</script>'''%seid, self.ctrl_publish(req, 'validateform').decode('ascii'))
430
431

        # ensure a value that comply a constraint is properly processed
432
        with self.admin_access.web_request(rollbackfirst=True) as req:
433
            req.form = {'eid': [text_type(seid)],
434
435
436
437
438
                        '__type:%s'%seid: 'Salesterm',
                        '_cw_entity_fields:%s'%seid: 'amount-subject',
                        'amount-subject:%s'%seid: u'20',
                    }
            self.assertMultiLineEqual('''<script type="text/javascript">
439
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
Julien Cristau's avatar
Julien Cristau committed
440
</script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
441
442
            self.assertEqual(20, req.execute('Any V WHERE X amount V, X eid %(eid)s',
                                             {'eid': seid})[0][0])
443

444
445
446
447
448
        with self.admin_access.web_request(rollbackfirst=True) as req:
            req.form = {'eid': ['X'],
                        '__type:X': 'Salesterm',
                        '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
                        'amount-subject:X': u'0',
449
                        'described_by_test-subject:X': text_type(feid),
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
                    }

            # ensure a value that is modified in an operation on a modify
            # hook works as it should (see
            # https://www.cubicweb.org/ticket/2509729 )
            class MyOperation(Operation):
                def precommit_event(self):
                    self.entity.cw_set(amount=-10)
            class ValidationErrorInOpAfterHook(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)

            with self.temporary_appobjects(ValidationErrorInOpAfterHook):
                self.assertMultiLineEqual('''<script type="text/javascript">
467
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
Julien Cristau's avatar
Julien Cristau committed
468
</script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
469

470
            self.assertMultiLineEqual('''<script type="text/javascript">
471
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
Julien Cristau's avatar
Julien Cristau committed
472
</script>''', self.ctrl_publish(req, 'validateform').decode('ascii'))
473

Adrien Di Mascio's avatar
Adrien Di Mascio committed
474
475
    def test_req_pending_insert(self):
        """make sure req's pending insertions are taken into account"""
476
477
478
479
480
481
482
483
484
485
486
487
        with self.admin_access.web_request() as req:
            tmpgroup = req.create_entity('CWGroup', name=u"test")
            user = req.user
            req.cnx.commit()
        with self.admin_access.web_request(**req_form(user)) as req:
            req.session.data['pending_insert'] = set([(user.eid, 'in_group', tmpgroup.eid)])
            self.expect_redirect_handle_request(req, 'edit')
            usergroups = [gname for gname, in
                          req.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), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
488
489
490

    def test_req_pending_delete(self):
        """make sure req's pending deletions are taken into account"""
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
        with self.admin_access.web_request() as req:
            user = req.user
            groupeid = req.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',
                                    {'x': user.eid})[0][0]
            usergroups = [gname for gname, in
                          req.execute('Any N WHERE G name N, U in_group G, U eid %(u)s',
                                      {'u': user.eid})]
            # just make sure everything was set correctly
            self.assertCountEqual(usergroups, ['managers', 'test'])
            req.cnx.commit()
            # now try to delete the relation
        with self.admin_access.web_request(**req_form(user)) as req:
            req.session.data['pending_delete'] = set([(user.eid, 'in_group', groupeid)])
            self.expect_redirect_handle_request(req, 'edit')
            usergroups = [gname for gname, in
                          req.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), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
510
511

    def test_redirect_apply_button(self):
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
        with self.admin_access.web_request() as req:
            redirectrql = rql_for_eid(4012) # whatever
            req.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')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
533
534

    def test_redirect_ok_button(self):
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
        with self.admin_access.web_request() as req:
            redirectrql = rql_for_eid(4012) # whatever
            req.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')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
553
554

    def test_redirect_delete_button(self):
555
556
        with self.admin_access.web_request() as req:
            eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
557
            req.form = {'eid': text_type(eid), '__type:%s'%eid: 'BlogEntry',
558
559
560
561
562
563
                        '__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').eid
            req.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
564
                        {'x': req.user.eid, 'e': eid})
565
            req.cnx.commit()
566
            req.form = {'eid': text_type(eid), '__type:%s'%eid: 'EmailAddress',
567
568
569
570
571
572
                        '__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').eid
            eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
573
            req.form = {'eid': [text_type(eid1), text_type(eid2)],
574
575
576
577
578
579
                        '__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)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
580

581
    def test_simple_copy(self):
582
583
584
585
        with self.admin_access.web_request() as req:
            blog = req.create_entity('Blog', title=u'my-blog')
            blogentry = req.create_entity('BlogEntry', title=u'entry1',
                                          content=u'content1', entry_of=blog)
586
587
588
589
590
591
            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',
                        }
592
            self.expect_redirect_handle_request(req, 'edit')
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
            blogentry2 = req.find('BlogEntry', title=u'entry1-copy').one()
            self.assertEqual(blogentry2.entry_of[0].eid, blog.eid)

    def test_skip_copy_for(self):
        with self.admin_access.web_request() as req:
            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 copied
                self.assertEqual(len(blogentry2.entry_of), 0)
            finally:
                blogentry.__class__.cw_skip_copy_for = []
615

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
    def test_avoid_multiple_process_posted(self):
        # test that when some entity is being created and data include non-inlined relations, the
        # values for this relation are stored for later usage, without calling twice field's
        # process_form method, which may be unexpected for custom fields

        orig_process_posted = ff.RelationField.process_posted

        def count_process_posted(self, form):
            res = list(orig_process_posted(self, form))
            nb_process_posted_calls[0] += 1
            return res

        ff.RelationField.process_posted = count_process_posted

        try:
            with self.admin_access.web_request() as req:
                gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
                req.form = {
                    'eid': 'X',
                    '__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),
                }
                nb_process_posted_calls = [0]
                self.expect_redirect_handle_request(req, 'edit')
                self.assertEqual(nb_process_posted_calls[0], 1)
                user = req.find('CWUser', login=u'adim').one()
                self.assertEqual(set(g.eid for g in user.in_group), set([gueid]))
                req.form = {
                    'eid': ['X', 'Y'],
                    '__type:X': 'CWUser',
                    '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
                    'login-subject:X': u'dlax',
                    '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,use_email-object',
                    'address-subject:Y': u'dlax@cw.org',
                    'use_email-object:Y': 'X',
                }
                nb_process_posted_calls = [0]
                self.expect_redirect_handle_request(req, 'edit')
                self.assertEqual(nb_process_posted_calls[0], 3)  # 3 = 1 (in_group) + 2 (use_email)
                user = req.find('CWUser', login=u'dlax').one()
                self.assertEqual(set(e.address for e in user.use_email), set(['dlax@cw.org']))

        finally:
            ff.RelationField.process_posted = orig_process_posted

Adrien Di Mascio's avatar
Adrien Di Mascio committed
668
    def test_nonregr_eetype_etype_editing(self):
669
        """non-regression test checking that a manager user can edit a CWEType entity
Adrien Di Mascio's avatar
Adrien Di Mascio committed
670
        """
671
672
673
674
        with self.admin_access.web_request() as req:
            groupeids = sorted(eid
                               for eid, in req.execute('CWGroup G '
                                                       'WHERE G name in ("managers", "users")'))
675
            groups = [text_type(eid) for eid in groupeids]
676
            cwetypeeid = req.execute('CWEType X WHERE X name "CWEType"')[0][0]
677
            basegroups = [text_type(eid)
678
679
680
                          for eid, in req.execute('CWGroup G '
                                                  'WHERE X read_permission G, X eid %(x)s',
                                                  {'x': cwetypeeid})]
681
            cwetypeeid = text_type(cwetypeeid)
682
683
684
685
686
687
688
689
            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,
690
            }
691
692
693
694
695
696
697
698
699
700
            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.eid for g in e.read_permission), groupeids)
            finally:
                # restore
                req.execute('SET X read_permission Y WHERE X name "CWEType", '
                            'Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
                req.cnx.commit()
701

Adrien Di Mascio's avatar
Adrien Di Mascio committed
702
703
704
705
    def test_nonregr_strange_text_input(self):
        """non-regression test checking text input containing "13:03:43"

        this seems to be postgres (tsearch?) specific
706
        """
707
708
709
710
711
712
713
714
715
716
717
718
        with self.admin_access.web_request() as req:
            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"')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
719
720

    def test_nonregr_multiple_empty_email_addr(self):
721
722
723
724
725
726
727
728
        with self.admin_access.web_request() as req:
            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',
Samuel Trégouët's avatar
Samuel Trégouët committed
729
                        'in_group-subject:X': repr(gueid),
730
731
732
733
734
735
736
737
738
739

                        '__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',
                        }
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
            self.assertEqual(cm.exception.errors, {'address-subject': u'required field'})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
740
741

    def test_nonregr_copy(self):
742
743
744
745
746
747
748
749
750
751
752
753
754
        with self.admin_access.web_request() as req:
            user = req.user
            req.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')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
755
756
757


    def test_nonregr_rollback_on_validation_error(self):
758
        with self.admin_access.web_request(url='edit') as req:
759
            p = self.create_user(req, u"doe")
760
            # do not try to skip 'primary_email' for this test
761
762
            old_skips = p.__class__.cw_skip_copy_for
            p.__class__.cw_skip_copy_for = ()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
763
            try:
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
                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 commits
                try:
779
                    self.app_handle_request(req)
780
781
782
783
784
785
786
                except Redirect:
                    req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
                    req.form['vid'] = 'copy'
                    self.app_handle_request(req, 'view')
                rset = req.execute('CWUser P WHERE P surname "Boom"')
                self.assertEqual(len(rset), 0)
            finally:
787
                p.__class__.cw_skip_copy_for = old_skips
Adrien Di Mascio's avatar
Adrien Di Mascio committed
788

789
    def test_regr_inlined_forms(self):
790
        with self.admin_access.web_request() as req:
791
            self.schema['described_by_test'].inlined = False
792
793
794
795
796
797
798
799
800
801
802
803
            try:
                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',
804
                            'data-subject:Y': (u'coucou.txt', Binary(b'coucou')),
805
806
807
808
809
810
811
812
813
814
815
                            }
                values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2))
                                     for eid in req.edited_eids())
                editctrl = self.vreg['controllers'].select('edit', req)
                # don't call publish to enforce select order
                editctrl.errors = []
                editctrl._to_create = {}
                editctrl.edit_entity(values_by_eid['X']) # #3064653 raise ValidationError
                editctrl.edit_entity(values_by_eid['Y'])
            finally:
                self.schema['described_by_test'].inlined = False
816

Adrien Di Mascio's avatar
Adrien Di Mascio committed
817

818
class ReportBugControllerTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
819

820
    def test_usable_by_guest(self):
821
        with self.new_access(u'anon').web_request() as req:
822
823
            self.assertRaises(NoSelectableObject,
                              self.vreg['controllers'].select, 'reportbug', req)
824
        with self.new_access(u'anon').web_request(description='hop') as req:
825
            self.vreg['controllers'].select('reportbug', req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
826
827


828
829
class AjaxControllerTC(CubicWebTC):
    tested_controller = 'ajax'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
830
831
832

    def ctrl(self, req=None):
        req = req or self.request(url='http://whatever.fr/')
833
        return self.vreg['controllers'].select(self.tested_controller, req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
834
835

    def setup_database(self):
836
837
838
839
840
        with self.admin_access.repo_cnx() as cnx:
            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()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
841
842
843

    ## tests ##################################################################
    def test_simple_exec(self):
844
845
846
847
848
849
        with self.admin_access.web_request(rql='CWUser P WHERE P login "John"',
                                           pageid='123', fname='view') as req:
            ctrl = self.ctrl(req)
            rset = self.john.as_rset()
            rset.req = req
            source = ctrl.publish()
Julien Cristau's avatar
Julien Cristau committed
850
            self.assertTrue(source.startswith(b'<div>'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
851

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
852
853
854
#     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'))
855
#         self.assertEqual(ctrl.publish(),
856
#                           json_dumps(self.execute(rql).rows))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
857
858

    def test_remote_add_existing_tag(self):
859
860
861
862
863
864
865
        with self.remote_calling('tag_entity', self.john.eid, ['python']) as (_, req):
            self.assertCountEqual(
                [tname for tname, in req.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']])
866

Adrien Di Mascio's avatar
Adrien Di Mascio committed
867
    def test_remote_add_new_tag(self):
868
869
870
871
872
873
874
        with self.remote_calling('tag_entity', self.john.eid, ['javascript']) as (_, req):
            self.assertCountEqual(
                [tname for tname, in req.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']])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
875

876
877
878
    def test_maydel_perms(self):
        """Check that AjaxEditRelationCtxComponent calls rdef.check with a
        sufficient context"""
879
880
        with self.remote_calling('tag_entity', self.john.eid, ['python']) as (_, req):
            req.cnx.commit()
881
882
883
        with self.temporary_permissions(
                (self.schema['tags'].rdefs['Tag', 'CWUser'],
                 {'delete': (RRQLExpression('S owned_by U'), )}, )):
884
            with self.admin_access.web_request(rql='CWUser P WHERE P login "John"',
885
886
                                               pageid='123', fname='view',
                                               session=req.session) as req:
887
888
889
890
891
                ctrl = self.ctrl(req)
                rset = self.john.as_rset()
                rset.req = req
                source = ctrl.publish()
                # maydel jscall
Julien Cristau's avatar
Julien Cristau committed
892
                self.assertIn(b'ajaxBoxRemoveLinkedEntity', source)
893

Adrien Di Mascio's avatar
Adrien Di Mascio committed
894
    def test_pending_insertion(self):
895
896
897
898
899
        with self.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'])
900
901
        with self.remote_calling('add_pending_inserts', [['12', 'tags', '14']],
                                 session=req.session) as (_, req):
902
903
904
            deletes = get_pending_deletes(req)
            self.assertEqual(deletes, [])
            inserts = get_pending_inserts(req)
905
            self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
906
            inserts = get_pending_inserts(req, 12)
907
            self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
908
909
910
911
912
            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()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
913
914

    def test_pending_deletion(self):
915
916
917
918
919
        with self.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'])
920
921
        with self.remote_calling('add_pending_delete', ['12', 'tags', '14'],
                                 session=req.session) as (_, req):
922
923
924
            inserts = get_pending_inserts(req)
            self.assertEqual(inserts, [])
            deletes = get_pending_deletes(req)
925
            self.assertCountEqual(deletes, ['12:tags:13', '12:tags:14'])
926
            deletes = get_pending_deletes(req, 12)
927
            self.assertCountEqual(deletes, ['12:tags:13', '12:tags:14'])
928
929
930
931
932
            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()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
933
934

    def test_remove_pending_operations(self):
935
        with self.remote_calling('add_pending_delete', ['12', 'tags', '13']) as (_, req):
936
            pass
937
938
        with self.remote_calling('add_pending_inserts', [['12', 'tags', '14']],
                                 session=req.session) as (_, req):
939
940
941
942
943
944
945
            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), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
946
947

    def test_add_inserts(self):
948
949
950
        with self.remote_calling('add_pending_inserts',
                                 [('12', 'tags', '13'), ('12', 'tags', '14')]) as (_, req):
            inserts = get_pending_inserts(req)
951
            self.assertCountEqual(inserts, ['12:tags:13', '12:tags:14'])
952
            req.remove_pending_operations()
953

Adrien Di Mascio's avatar
Adrien Di Mascio committed
954
955
956

    # silly tests
    def test_external_resource(self):
957
        with self.remote_calling('external_resource', 'RSS_LOGO') as (res, _):
958
            self.assertEqual(json_dumps(self.config.uiprops['RSS_LOGO']).encode('ascii'),
959
960
                             res)

Adrien Di Mascio's avatar
Adrien Di Mascio committed
961
    def test_i18n(self):
962
        with self.remote_calling('i18n', ['bimboom']) as (res, _):
963
            self.assertEqual(json_dumps(['bimboom']).encode('ascii'), res)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
964
965

    def test_format_date(self):
966
        with self.remote_calling('format_date', '2007-01-01 12:00:00') as (res, _):
967
            self.assertEqual(json_dumps('2007/01/01').encode('ascii'), res)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
968

969
970
971
972
    def test_ajaxfunc_noparameter(self):
        @ajaxfunc
        def foo(self, x, y):
            return 'hello'
973
974
975
976
977
978
        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)
979
980
981
        with self.admin_access.web_request() as req:
            f = appobject(req)
            self.assertEqual(f(12, 13), 'hello')
982
983

    def test_ajaxfunc_checkpageid(self):
984
        @ajaxfunc(check_pageid=True)
985
        def foo(self, x, y):
986
987
988
989
990
991
992
            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)
993
        # no pageid
994
995
996
        with self.admin_access.web_request() as req:
            f = appobject(req)
            self.assertRaises(RemoteCallFailed, f, 12, 13)
997
998
999
1000
1001

    def test_ajaxfunc_json(self):
        @ajaxfunc(output_type='json')
        def foo(self, x, y):
            return x + y
1002
1003
1004
1005
1006
1007
        self.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')
1008
        # no pageid
1009
1010
1011
        with self.admin_access.web_request() as req:
            f = appobject(req)
            self.assertEqual(f(12, 13), '25')
1012

1013
1014
1015
1016
1017
1018
1019
    def test_badrequest(self):
        with self.assertRaises(RemoteCallFailed) as cm:
            with self.remote_calling('foo'):
                pass
        self.assertEqual(cm.exception.status, 400)
        self.assertEqual(cm.exception.reason, 'no foo method')

1020

1021
1022
class UndoControllerTC(CubicWebTC):

1023
    def setUp(self):
1024
1025
        # Force undo feature to be turned on
        Connection.undo_actions = property(lambda self: True, lambda self, v:None)
1026
1027
1028
1029
        super(UndoControllerTC, self).setUp()

    def tearDown(self):
        super(UndoControllerTC, self).tearDown()
1030
        del Connection.undo_actions
1031

1032
    def setup_database(self):
1033
        with self.admin_access.repo_cnx() as cnx:
1034
1035
            self.toto = self.create_user(cnx, u'toto',
                                         password=u'toto',
1036
1037
1038
1039
1040
1041
1042
                                         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()
1043
1044

    def test_no_such_transaction(self):
1045
1046
1047
1048
1049
1050
1051
        with self.admin_access.web_request() as req:
            txuuid = u"12345acbd"
            req.form['txuuid'] = txuuid
            controller = self.vreg['controllers'].select('undo', req)
            with self.assertRaises(tx.NoSuchTransaction) as cm:
                result = controller.publish(rset=None)
            self.assertEqual(cm.exception.txuuid, txuuid)
1052
1053
1054
1055
1056
1057

    def assertURLPath(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
        """
1058
1059
        with self.admin_access.web_request() as req:
            scheme, netloc, path, query, fragment = urlsplit(url)
1060
            query_dict = parse_qs(query)
1061
1062
            expected_url = urljoin(req.base_url(), expected_path)
            self.assertEqual( urlunsplit((scheme, netloc, path, None, None)), expected_url)
1063
1064
1065

    def test_redirect_redirectpath(self):
        "Check that the potential __redirectpath is honored"
1066
1067
1068
1069
1070
1071
1072
1073
1074
        with self.admin_access.web_request() as req:
            txuuid = self.txuuid_toto_email
            req.form['txuuid'] = txuuid
            rpath = "toto"
            req.form['__redirectpath'] = rpath
            controller = self.vreg['controllers'].select('undo', req)
            with self.assertRaises(Redirect) as cm:
                result = controller.publish(rset=None)
            self.assertURLPath(cm.exception.location, rpath)
1075
1076


1077
1078
1079
class LoginControllerTC(CubicWebTC):

    def test_login_with_dest(self):
1080
1081
1082
1083
1084
        with self.admin_access.web_request() as req:
            req.form = {'postlogin_path': 'elephants/babar'}
            with self.assertRaises(Redirect) as cm:
                self.ctrl_publish(req, ctrl='login')
            self.assertEqual(req.build_url('elephants/babar'), cm.exception.location)
1085
1086

    def test_login_no_dest(self):
1087
1088
1089
1090
        with self.admin_access.web_request() as req:
            with self.assertRaises(Redirect) as cm:
                self.ctrl_publish(req, ctrl='login')
            self.assertEqual(req.base_url(), cm.exception.location)
1091

1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109

class LoginControllerHTTPTC(CubicWebServerTC):

    anonymous_allowed = True
    # this TC depends on the auth mode being 'cookie' and not 'http'
    # (the former being the default, so everything works)

    def test_http_error_codes_auth_fail(self):
        url = 'login?__login=%s&__password=%s' % ('toto', 'pouetA')
        response = self.web_request(url, 'POST')
        self.assertEqual(response.status, 403)

    def test_http_error_codes_auth_succeed(self):
        url = 'login?__login=%s&__password=%s' % (self.admlogin, self.admpassword)
        response = self.web_request(url, 'POST')
        self.assertEqual(response.status, 303)


Adrien Di Mascio's avatar
Adrien Di Mascio committed
1110