unittest_views_basecontrollers.py 41.5 KB
Newer Older
1
# copyright 2003-2013 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
22
23
24
25
from urlparse import urlsplit, urlunsplit, urljoin
# parse_qs is deprecated in cgi and has been moved to urlparse in Python 2.6
try:
    from urlparse import parse_qs as url_parse_query
except ImportError:
    from cgi import parse_qs as url_parse_query
26
from logilab.common.testlib import unittest_main, mock_object
27
from logilab.common.decorators import monkeypatch
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28

29
30
from cubicweb import Binary, NoSelectableObject, ValidationError
from cubicweb.view import STRICT_DOCTYPE
31
from cubicweb.devtools.testlib import CubicWebTC
32
from cubicweb.utils import json_dumps
Sylvain Thénault's avatar
Sylvain Thénault committed
33
from cubicweb.uilib import rql_for_eid
34
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError, RemoteCallFailed
35
36
import cubicweb.server.session
from cubicweb.server.session import Transaction as OldTransaction
37
from cubicweb.entities.authobjs import CWUser
Sylvain Thénault's avatar
Sylvain Thénault committed
38
from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
39
40
from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize
from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
41
import cubicweb.transaction as tx
42
43
from cubicweb.server.hook import Hook, Operation
from cubicweb.predicates import is_instance
44

Sylvain Thénault's avatar
Sylvain Thénault committed
45
46
u = unicode

Sylvain Thénault's avatar
Sylvain Thénault committed
47
48
def req_form(user):
    return {'eid': [str(user.eid)],
49
            '_cw_entity_fields:%s' % user.eid: '_cw_generic_field',
Sylvain Thénault's avatar
Sylvain Thénault committed
50
51
            '__type:%s' % user.eid: user.__regid__
            }
Adrien Di Mascio's avatar
Adrien Di Mascio committed
52

53
class EditControllerTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
54
    def setUp(self):
55
        CubicWebTC.setUp(self)
56
        self.assertTrue('users' in self.schema.eschema('CWGroup').get_groups('read'))
57

Adrien Di Mascio's avatar
Adrien Di Mascio committed
58
    def tearDown(self):
59
        CubicWebTC.tearDown(self)
60
        self.assertTrue('users' in self.schema.eschema('CWGroup').get_groups('read'))
61

Adrien Di Mascio's avatar
Adrien Di Mascio committed
62
63
64
    def test_noparam_edit(self):
        """check behaviour of this controller without any form parameter
        """
65
66
67
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(self.request())
        self.assertEqual(cm.exception.errors, {None: u'no selected entities'})
68

Adrien Di Mascio's avatar
Adrien Di Mascio committed
69
70
    def test_validation_unique(self):
        """test creation of two linked entities
71
        """
Adrien Di Mascio's avatar
Adrien Di Mascio committed
72
        user = self.user()
73
74
        req = self.request()
        req.form = {'eid': 'X', '__type:X': 'CWUser',
75
                    '_cw_entity_fields:X': 'login-subject,upassword-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
76
77
78
                    'login-subject:X': u'admin',
                    'upassword-subject:X': u'toto',
                    'upassword-subject-confirm:X': u'toto',
79
                    }
80
81
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(req)
82
        cm.exception.translate(unicode)
83
        self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
84
85
86
87
88

    def test_user_editing_itself(self):
        """checking that a manager user can edit itself
        """
        user = self.user()
Sylvain Thénault's avatar
Sylvain Thénault committed
89
        basegroups = [u(eid) for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
90
        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
Sylvain Thénault's avatar
Sylvain Thénault committed
91
        groups = [u(eid) for eid in groupeids]
92
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
93
        eid = u(user.eid)
94
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
95
            'eid': eid, '__type:'+eid: 'CWUser',
96
            '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
97
98
99
100
            'login-subject:'+eid:     u(user.login),
            'surname-subject:'+eid: u'Th\xe9nault',
            'firstname-subject:'+eid:   u'Sylvain',
            'in_group-subject:'+eid:  groups,
Adrien Di Mascio's avatar
Adrien Di Mascio committed
101
            }
102
        path, params = self.expect_redirect_handle_request(req, 'edit')
103
        e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}).get_entity(0, 0)
104
105
106
107
        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
108
109
110

    def test_user_can_change_its_password(self):
        req = self.request()
111
112
        user = self.create_user(req, 'user')
        cnx = self.login('user')
Sylvain Thénault's avatar
Sylvain Thénault committed
113
        eid = u(user.eid)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
114
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
115
116
            'eid': eid, '__maineid' : eid,
            '__type:'+eid: 'CWUser',
117
            '_cw_entity_fields:'+eid: 'upassword-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
118
119
            'upassword-subject:'+eid: 'tournicoton',
            'upassword-subject-confirm:'+eid: 'tournicoton',
Adrien Di Mascio's avatar
Adrien Di Mascio committed
120
            }
121
        path, params = self.expect_redirect_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
122
        cnx.commit() # commit to check we don't get late validation error for instance
123
        self.assertEqual(path, 'cwuser/user')
124
        self.assertFalse('vid' in params)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
125

Sylvain Thénault's avatar
Sylvain Thénault committed
126
    def test_user_editing_itself_no_relation(self):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
127
128
129
130
        """checking we can edit an entity without specifying some required
        relations (meaning no changes)
        """
        user = self.user()
Sylvain Thénault's avatar
Sylvain Thénault committed
131
        groupeids = [g.eid for g in user.in_group]
132
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
133
        eid = u(user.eid)
134
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
135
136
            'eid':       eid,
            '__type:'+eid:    'CWUser',
137
            '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
138
139
140
            'login-subject:'+eid:     u(user.login),
            'firstname-subject:'+eid: u'Th\xe9nault',
            'surname-subject:'+eid:   u'Sylvain',
Adrien Di Mascio's avatar
Adrien Di Mascio committed
141
            }
142
        path, params = self.expect_redirect_handle_request(req, 'edit')
143
        e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}).get_entity(0, 0)
144
145
146
147
148
        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')
149
150


Adrien Di Mascio's avatar
Adrien Di Mascio committed
151
    def test_create_multiple_linked(self):
152
        gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
153
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
154
        req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
155

156
                    '__type:X': 'CWUser',
157
                    '_cw_entity_fields:X': 'login-subject,upassword-subject,surname-subject,in_group-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
158
159
160
161
                    'login-subject:X': u'adim',
                    'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
                    'surname-subject:X': u'Di Mascio',
                    'in_group-subject:X': u(gueid),
162

163
                    '__type:Y': 'EmailAddress',
164
                    '_cw_entity_fields:Y': 'address-subject,use_email-object',
Sylvain Thénault's avatar
Sylvain Thénault committed
165
166
                    'address-subject:Y': u'dima@logilab.fr',
                    'use_email-object:Y': 'X',
167
                    }
168
        path, params = self.expect_redirect_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
169
        # should be redirected on the created person
170
        self.assertEqual(path, 'cwuser/adim')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
171
        e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
172
        self.assertEqual(e.surname, 'Di Mascio')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
173
        email = e.use_email[0]
174
        self.assertEqual(email.address, 'dima@logilab.fr')
175

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    def test_create_mandatory_inlined(self):
        req = self.request()
        req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',

                    '__type:X': 'Salesterm',
                    '_cw_entity_fields:X': '',

                    '__type:Y': 'File',
                    '_cw_entity_fields:Y': 'data-subject,described_by_test-object',
                    'data-subject:Y': (u'coucou.txt', Binary('coucou')),
                    'described_by_test-object:Y': 'X',
                    }
        path, params = self.expect_redirect_handle_request(req, 'edit')
        self.assertTrue(path.startswith('salesterm/'), path)
        eid = path.split('/')[1]
        salesterm = req.entity_from_eid(eid)
        # The NOT NULL constraint of mandatory relation implies that the File
        # must be created before the Salesterm, otherwise Salesterm insertion
        # will fail.
        # NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the
        # insertion does not fail and we have to check dumbly that File is
        # created before.
        self.assertGreater(salesterm.eid, salesterm.described_by_test[0].eid)

Adrien Di Mascio's avatar
Adrien Di Mascio committed
200
    def test_edit_multiple_linked(self):
201
        req = self.request()
202
        peid = u(self.create_user(req, 'adim').eid)
Sylvain Thénault's avatar
Sylvain Thénault committed
203
        req.form = {'eid': [peid, 'Y'], '__maineid': peid,
204

Sylvain Thénault's avatar
Sylvain Thénault committed
205
                    '__type:'+peid: u'CWUser',
206
                    '_cw_entity_fields:'+peid: u'surname-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
207
                    'surname-subject:'+peid: u'Di Masci',
208

Sylvain Thénault's avatar
Sylvain Thénault committed
209
                    '__type:Y': u'EmailAddress',
210
                    '_cw_entity_fields:Y': u'address-subject,use_email-object',
Sylvain Thénault's avatar
Sylvain Thénault committed
211
212
                    'address-subject:Y': u'dima@logilab.fr',
                    'use_email-object:Y': peid,
213
                    }
214
        path, params = self.expect_redirect_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
215
        # should be redirected on the created person
216
        self.assertEqual(path, 'cwuser/adim')
Sylvain Thénault's avatar
Sylvain Thénault committed
217
        e = self.execute('Any P WHERE P surname "Di Masci"').get_entity(0, 0)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
218
        email = e.use_email[0]
219
        self.assertEqual(email.address, 'dima@logilab.fr')
220

Sylvain Thénault's avatar
Sylvain Thénault committed
221
        emaileid = u(email.eid)
222
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
223
224
225
        req.form = {'eid': [peid, emaileid],

                    '__type:'+peid: u'CWUser',
226
                    '_cw_entity_fields:'+peid: u'surname-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
227
228
229
                    'surname-subject:'+peid: u'Di Masci',

                    '__type:'+emaileid: u'EmailAddress',
230
                    '_cw_entity_fields:'+emaileid: u'address-subject,use_email-object',
Sylvain Thénault's avatar
Sylvain Thénault committed
231
232
233
                    'address-subject:'+emaileid: u'adim@logilab.fr',
                    'use_email-object:'+emaileid: peid,
                    }
234
        path, params = self.expect_redirect_handle_request(req, 'edit')
235
        email.cw_clear_all_caches()
236
        self.assertEqual(email.address, 'adim@logilab.fr')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
237

238

Adrien Di Mascio's avatar
Adrien Di Mascio committed
239
240
    def test_password_confirm(self):
        """test creation of two linked entities
241
        """
Adrien Di Mascio's avatar
Adrien Di Mascio committed
242
        user = self.user()
243
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
244
245
        req.form = {'eid': 'X',
                    '__cloned_eid:X': u(user.eid), '__type:X': 'CWUser',
246
                    '_cw_entity_fields:X': 'login-subject,upassword-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
247
248
                    'login-subject:X': u'toto',
                    'upassword-subject:X': u'toto',
249
                    }
250
251
252
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(req)
        self.assertEqual(cm.exception.errors, {'upassword-subject': u'password and confirmation don\'t match'})
253
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
254
        req.form = {'__cloned_eid:X': u(user.eid),
255
                    'eid': 'X', '__type:X': 'CWUser',
256
                    '_cw_entity_fields:X': 'login-subject,upassword-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
257
258
259
                    'login-subject:X': u'toto',
                    'upassword-subject:X': u'toto',
                    'upassword-subject-confirm:X': u'tutu',
260
                    }
261
262
263
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(req)
        self.assertEqual(cm.exception.errors, {'upassword-subject': u'password and confirmation don\'t match'})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
264
265
266


    def test_interval_bound_constraint_success(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
267
        feid = self.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
Adrien Di Mascio's avatar
Adrien Di Mascio committed
268
                            {'data': Binary('yo')})[0][0]
269
        self.commit()
270
        req = self.request(rollbackfirst=True)
271
272
        req.form = {'eid': ['X'],
                    '__type:X': 'Salesterm',
273
                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
274
275
                    'amount-subject:X': u'-10',
                    'described_by_test-subject:X': u(feid),
276
                }
277
278
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(req)
279
        cm.exception.translate(unicode)
280
        self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
281
        req = self.request(rollbackfirst=True)
282
283
        req.form = {'eid': ['X'],
                    '__type:X': 'Salesterm',
284
                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
285
286
                    'amount-subject:X': u'110',
                    'described_by_test-subject:X': u(feid),
287
                    }
288
289
        with self.assertRaises(ValidationError) as cm:
            self.ctrl_publish(req)
290
        cm.exception.translate(unicode)
291
        self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
292

293
        req = self.request(rollbackfirst=True)
294
295
        req.form = {'eid': ['X'],
                    '__type:X': 'Salesterm',
296
                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
297
298
                    'amount-subject:X': u'10',
                    'described_by_test-subject:X': u(feid),
299
                    }
300
        self.expect_redirect_handle_request(req, 'edit')
301
        # should be redirected on the created
Adrien Di Mascio's avatar
Adrien Di Mascio committed
302
303
        #eid = params['rql'].split()[-1]
        e = self.execute('Salesterm X').get_entity(0, 0)
304
        self.assertEqual(e.amount, 10)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
305

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
    def test_interval_bound_constraint_validateform(self):
        """Test the FormValidatorController controller on entity with
        constrained attributes"""
        feid = self.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
                            {'data': Binary('yo')})[0][0]
        seid = self.request().create_entity('Salesterm', amount=0, described_by_test=feid).eid
        self.commit()

        # ensure a value that violate a constraint is properly detected
        req = self.request(rollbackfirst=True)
        req.form = {'eid': [unicode(seid)],
                    '__type:%s'%seid: 'Salesterm',
                    '_cw_entity_fields:%s'%seid: 'amount-subject',
                    'amount-subject:%s'%seid: u'-10',
                }
        self.assertEqual('''<script type="text/javascript">
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
</script>'''%seid, self.ctrl_publish(req, 'validateform'))

        # ensure a value that comply a constraint is properly processed
        req = self.request(rollbackfirst=True)
        req.form = {'eid': [unicode(seid)],
                    '__type:%s'%seid: 'Salesterm',
                    '_cw_entity_fields:%s'%seid: 'amount-subject',
                    'amount-subject:%s'%seid: u'20',
                }
        self.assertEqual('''<script type="text/javascript">
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
</script>''', self.ctrl_publish(req, 'validateform'))
        self.assertEqual(20, self.execute('Any V WHERE X amount V, X eid %(eid)s', {'eid': seid})[0][0])

        req = self.request(rollbackfirst=True)
        req.form = {'eid': ['X'],
                    '__type:X': 'Salesterm',
                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
                    'amount-subject:X': u'0',
                    'described_by_test-subject:X': u(feid),
                }

        # ensure a value that is modified in an operation on a modify
        # hook works as it should (see
        # https://www.cubicweb.org/ticket/2509729 )
        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.assertEqual('''<script type="text/javascript">
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
</script>''', self.ctrl_publish(req, 'validateform'))

        self.assertEqual('''<script type="text/javascript">
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
</script>''', self.ctrl_publish(req, 'validateform'))

Adrien Di Mascio's avatar
Adrien Di Mascio committed
367
368
    def test_req_pending_insert(self):
        """make sure req's pending insertions are taken into account"""
Sylvain Thénault's avatar
Sylvain Thénault committed
369
        tmpgroup = self.request().create_entity('CWGroup', name=u"test")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
370
        user = self.user()
Sylvain Thénault's avatar
Sylvain Thénault committed
371
        req = self.request(**req_form(user))
372
        req.session.data['pending_insert'] = set([(user.eid, 'in_group', tmpgroup.eid)])
373
        path, params = self.expect_redirect_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
374
375
        usergroups = [gname for gname, in
                      self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
376
377
        self.assertItemsEqual(usergroups, ['managers', 'test'])
        self.assertEqual(get_pending_inserts(req), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
378
379
380
381

    def test_req_pending_delete(self):
        """make sure req's pending deletions are taken into account"""
        user = self.user()
382
        groupeid = self.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',
Adrien Di Mascio's avatar
Adrien Di Mascio committed
383
384
385
386
                                {'x': user.eid})[0][0]
        usergroups = [gname for gname, in
                      self.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
387
        self.assertItemsEqual(usergroups, ['managers', 'test'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
388
        # now try to delete the relation
Sylvain Thénault's avatar
Sylvain Thénault committed
389
        req = self.request(**req_form(user))
390
        req.session.data['pending_delete'] = set([(user.eid, 'in_group', groupeid)])
391
        path, params = self.expect_redirect_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
392
393
        usergroups = [gname for gname, in
                      self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
394
395
        self.assertItemsEqual(usergroups, ['managers'])
        self.assertEqual(get_pending_deletes(req), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
396
397
398

    def test_redirect_apply_button(self):
        redirectrql = rql_for_eid(4012) # whatever
399
400
        req = self.request()
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
401
            'eid': 'A', '__maineid' : 'A',
402
            '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
403
404
            'content-subject:A': u'"13:03:43"',
            'title-subject:A': u'huuu',
405
406
407
408
409
410
            '__redirectrql': redirectrql,
            '__redirectvid': 'primary',
            '__redirectparams': 'toto=tutu&tata=titi',
            '__form_id': 'edition',
            '__action_apply': '',
            }
411
        path, params = self.expect_redirect_handle_request(req, 'edit')
412
        self.assertTrue(path.startswith('blogentry/'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
413
        eid = path.split('/')[1]
414
        self.assertEqual(params['vid'], 'edition')
415
        self.assertNotEqual(int(eid), 4012)
416
417
418
        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
419
420
421

    def test_redirect_ok_button(self):
        redirectrql = rql_for_eid(4012) # whatever
422
423
        req = self.request()
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
424
            'eid': 'A', '__maineid' : 'A',
425
            '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
426
427
            'content-subject:A': u'"13:03:43"',
            'title-subject:A': u'huuu',
428
429
430
431
432
            '__redirectrql': redirectrql,
            '__redirectvid': 'primary',
            '__redirectparams': 'toto=tutu&tata=titi',
            '__form_id': 'edition',
            }
433
        path, params = self.expect_redirect_handle_request(req, 'edit')
434
435
436
437
438
        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
439
440

    def test_redirect_delete_button(self):
441
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
442
        eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
Sylvain Thénault's avatar
Sylvain Thénault committed
443
        req.form = {'eid': u(eid), '__type:%s'%eid: 'BlogEntry',
444
                    '__action_delete': ''}
445
        path, params = self.expect_redirect_handle_request(req, 'edit')
446
        self.assertEqual(path, 'blogentry')
Sylvain Thénault's avatar
Sylvain Thénault committed
447
        self.assertIn('_cwmsgid', params)
Sylvain Thénault's avatar
Sylvain Thénault committed
448
        eid = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
449
        self.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
450
                     {'x': self.session.user.eid, 'e': eid})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
451
        self.commit()
Sylvain Thénault's avatar
Sylvain Thénault committed
452
        req = req
Sylvain Thénault's avatar
Sylvain Thénault committed
453
        req.form = {'eid': u(eid), '__type:%s'%eid: 'EmailAddress',
454
                    '__action_delete': ''}
455
        path, params = self.expect_redirect_handle_request(req, 'edit')
456
        self.assertEqual(path, 'cwuser/admin')
Sylvain Thénault's avatar
Sylvain Thénault committed
457
        self.assertIn('_cwmsgid', params)
Sylvain Thénault's avatar
Sylvain Thénault committed
458
459
        eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
        eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
460
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
461
        req.form = {'eid': [u(eid1), u(eid2)],
462
463
464
                    '__type:%s'%eid1: 'BlogEntry',
                    '__type:%s'%eid2: 'EmailAddress',
                    '__action_delete': ''}
465
        path, params = self.expect_redirect_handle_request(req, 'edit')
466
        self.assertEqual(path, 'view')
Sylvain Thénault's avatar
Sylvain Thénault committed
467
        self.assertIn('_cwmsgid', params)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
468

469
470
471
472
473
474
475
476
477
478
479
480
    def test_simple_copy(self):
        req = self.request()
        blog = req.create_entity('Blog', title=u'my-blog')
        blogentry = req.create_entity('BlogEntry', title=u'entry1',
                                      content=u'content1', entry_of=blog)
        req = self.request()
        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',
                    }
481
        self.expect_redirect_handle_request(req, 'edit')
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
        blogentry2 = req.find_one_entity('BlogEntry', title=u'entry1-copy')
        self.assertEqual(blogentry2.entry_of[0].eid, blog.eid)

    def test_skip_copy_for(self):
        req = self.request()
        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 = self.request()
            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',
                        }
499
            self.expect_redirect_handle_request(req, 'edit')
500
501
502
503
504
505
            blogentry2 = req.find_one_entity('BlogEntry', title=u'entry1-copy')
            # entry_of should not be copied
            self.assertEqual(len(blogentry2.entry_of), 0)
        finally:
            blogentry.__class__.cw_skip_copy_for = []

Adrien Di Mascio's avatar
Adrien Di Mascio committed
506
    def test_nonregr_eetype_etype_editing(self):
507
        """non-regression test checking that a manager user can edit a CWEType entity
Adrien Di Mascio's avatar
Adrien Di Mascio committed
508
        """
509
        groupeids = sorted(eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")'))
Sylvain Thénault's avatar
Sylvain Thénault committed
510
511
512
513
        groups = [u(eid) for eid in groupeids]
        cwetypeeid = self.execute('CWEType X WHERE X name "CWEType"')[0][0]
        basegroups = [u(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': cwetypeeid})]
        cwetypeeid = u(cwetypeeid)
514
515
        req = self.request()
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
516
517
            'eid':      cwetypeeid,
            '__type:'+cwetypeeid:  'CWEType',
518
            '_cw_entity_fields:'+cwetypeeid: 'name-subject,final-subject,description-subject,read_permission-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
519
            'name-subject:'+cwetypeeid:     u'CWEType',
Sylvain Thénault's avatar
Sylvain Thénault committed
520
            'final-subject:'+cwetypeeid:    '',
Sylvain Thénault's avatar
Sylvain Thénault committed
521
522
            'description-subject:'+cwetypeeid:     u'users group',
            'read_permission-subject:'+cwetypeeid:  groups,
523
            }
Adrien Di Mascio's avatar
Adrien Di Mascio committed
524
        try:
525
            path, params = self.expect_redirect_handle_request(req, 'edit')
526
            e = self.execute('Any X WHERE X eid %(x)s', {'x': cwetypeeid}).get_entity(0, 0)
527
528
            self.assertEqual(e.name, 'CWEType')
            self.assertEqual(sorted(g.eid for g in e.read_permission), groupeids)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
529
530
        finally:
            # restore
531
            self.execute('SET X read_permission Y WHERE X name "CWEType", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
532
            self.commit()
533

Adrien Di Mascio's avatar
Adrien Di Mascio committed
534
535
536
537
    def test_nonregr_strange_text_input(self):
        """non-regression test checking text input containing "13:03:43"

        this seems to be postgres (tsearch?) specific
538
        """
539
540
        req = self.request()
        req.form = {
Sylvain Thénault's avatar
Sylvain Thénault committed
541
            'eid': 'A', '__maineid' : 'A',
542
            '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'title-subject,content-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
543
544
            'title-subject:A': u'"13:03:40"',
            'content-subject:A': u'"13:03:43"',}
545
        path, params = self.expect_redirect_handle_request(req, 'edit')
546
        self.assertTrue(path.startswith('blogentry/'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
547
        eid = path.split('/')[1]
548
        e = self.execute('Any C, T WHERE C eid %(x)s, C content T', {'x': eid}).get_entity(0, 0)
549
550
        self.assertEqual(e.title, '"13:03:40"')
        self.assertEqual(e.content, '"13:03:43"')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
551
552
553


    def test_nonregr_multiple_empty_email_addr(self):
554
        gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
555
556
        req = self.request()
        req.form = {'eid': ['X', 'Y'],
557

558
                    '__type:X': 'CWUser',
559
                    '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
560
561
562
                    'login-subject:X': u'adim',
                    'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
                    'in_group-subject:X': `gueid`,
563

564
                    '__type:Y': 'EmailAddress',
565
                    '_cw_entity_fields:Y': 'address-subject,alias-subject,use_email-object',
Sylvain Thénault's avatar
Sylvain Thénault committed
566
567
568
                    'address-subject:Y': u'',
                    'alias-subject:Y': u'',
                    'use_email-object:Y': 'X',
569
                    }
570
571
572
        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
573
574
575

    def test_nonregr_copy(self):
        user = self.user()
576
        req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
577
578
        req.form = {'__maineid' : 'X', 'eid': 'X',
                    '__cloned_eid:X': user.eid, '__type:X': 'CWUser',
579
                    '_cw_entity_fields:X': 'login-subject,upassword-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
580
581
                    'login-subject:X': u'toto',
                    'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
582
                    }
583
        path, params = self.expect_redirect_handle_request(req, 'edit')
584
        self.assertEqual(path, 'cwuser/toto')
585
        e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
586
587
        self.assertEqual(e.login, 'toto')
        self.assertEqual(e.in_group[0].name, 'managers')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
588
589
590


    def test_nonregr_rollback_on_validation_error(self):
591
592
        req = self.request()
        p = self.create_user(req, "doe")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
593
594
595
596
        # do not try to skip 'primary_email' for this test
        old_skips = p.__class__.skip_copy_for
        p.__class__.skip_copy_for = ()
        try:
Sylvain Thénault's avatar
Sylvain Thénault committed
597
            e = self.request().create_entity('EmailAddress', address=u'doe@doe.com')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
598
599
            self.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})
600
            req = self.request()
Sylvain Thénault's avatar
Sylvain Thénault committed
601
602
            req.form = {'eid': 'X',
                        '__cloned_eid:X': p.eid, '__type:X': 'CWUser',
603
                        '_cw_entity_fields:X': 'login-subject,surname-subject',
Sylvain Thénault's avatar
Sylvain Thénault committed
604
605
                        'login-subject': u'dodo',
                        'surname-subject:X': u'Boom',
606
                        '__errorurl' : "whatever but required",
Sylvain Thénault's avatar
Sylvain Thénault committed
607
                        }
Adrien Di Mascio's avatar
Adrien Di Mascio committed
608
609
610
611
612
            # 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:
613
                self.app_handle_request(req, 'edit')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
614
            except Redirect:
Sylvain Thénault's avatar
Sylvain Thénault committed
615
                req = self.request()
616
617
                req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
                req.form['vid'] = 'copy'
618
                self.app_handle_request(req, 'view')
619
            rset = self.execute('CWUser P WHERE P surname "Boom"')
620
            self.assertEqual(len(rset), 0)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
621
622
623
624
        finally:
            p.__class__.skip_copy_for = old_skips


625
class ReportBugControllerTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
626

627
    def test_usable_by_guest(self):
628
        self.login('anon')
Sylvain Thénault's avatar
Sylvain Thénault committed
629
630
631
        self.assertRaises(NoSelectableObject,
                          self.vreg['controllers'].select, 'reportbug', self.request())
        self.vreg['controllers'].select('reportbug', self.request(description='hop'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
632
633


634
635
class AjaxControllerTC(CubicWebTC):
    tested_controller = 'ajax'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
636
637
638

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

    def setup_database(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
642
643
644
        req = self.request()
        self.pytag = req.create_entity('Tag', name=u'python')
        self.cubicwebtag = req.create_entity('Tag', name=u'cubicweb')
645
        self.john = self.create_user(req, u'John')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
646
647
648
649


    ## tests ##################################################################
    def test_simple_exec(self):
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
650
651
652
653
        req = self.request(rql='CWUser P WHERE P login "John"',
                           pageid='123', fname='view')
        ctrl = self.ctrl(req)
        rset = self.john.as_rset()
654
        rset.req = req
655
        source = ctrl.publish()
656
        self.assertTrue(source.startswith('<div>'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
657

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
658
659
660
#     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'))
661
#         self.assertEqual(ctrl.publish(),
662
#                           json_dumps(self.execute(rql).rows))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
663
664
665

    def test_remote_add_existing_tag(self):
        self.remote_call('tag_entity', self.john.eid, ['python'])
666
        self.assertItemsEqual(
667
668
            [tname for tname, in self.execute('Any N WHERE T is Tag, T name N')],
            ['python', 'cubicweb'])
669
        self.assertEqual(
670
671
            self.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
            [['python']])
672

Adrien Di Mascio's avatar
Adrien Di Mascio committed
673
674
    def test_remote_add_new_tag(self):
        self.remote_call('tag_entity', self.john.eid, ['javascript'])
675
        self.assertItemsEqual(
676
677
            [tname for tname, in self.execute('Any N WHERE T is Tag, T name N')],
            ['python', 'cubicweb', 'javascript'])
678
        self.assertEqual(
679
680
            self.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
            [['javascript']])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
681
682

    def test_pending_insertion(self):
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
683
        res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '13']])
Sylvain Thénault's avatar
Sylvain Thénault committed
684
        deletes = get_pending_deletes(req)
685
        self.assertEqual(deletes, [])
Sylvain Thénault's avatar
Sylvain Thénault committed
686
        inserts = get_pending_inserts(req)
687
        self.assertEqual(inserts, ['12:tags:13'])
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
688
        res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
Sylvain Thénault's avatar
Sylvain Thénault committed
689
        deletes = get_pending_deletes(req)
690
        self.assertEqual(deletes, [])
Sylvain Thénault's avatar
Sylvain Thénault committed
691
        inserts = get_pending_inserts(req)
692
        self.assertEqual(inserts, ['12:tags:13', '12:tags:14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
693
        inserts = get_pending_inserts(req, 12)
694
        self.assertEqual(inserts, ['12:tags:13', '12:tags:14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
695
        inserts = get_pending_inserts(req, 13)
696
        self.assertEqual(inserts, ['12:tags:13'])
Sylvain Thénault's avatar
Sylvain Thénault committed
697
        inserts = get_pending_inserts(req, 14)
698
        self.assertEqual(inserts, ['12:tags:14'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
699
700
701
702
        req.remove_pending_operations()

    def test_pending_deletion(self):
        res, req = self.remote_call('add_pending_delete', ['12', 'tags', '13'])
Sylvain Thénault's avatar
Sylvain Thénault committed
703
        inserts = get_pending_inserts(req)
704
        self.assertEqual(inserts, [])
Sylvain Thénault's avatar
Sylvain Thénault committed
705
        deletes = get_pending_deletes(req)
706
        self.assertEqual(deletes, ['12:tags:13'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
707
        res, req = self.remote_call('add_pending_delete', ['12', 'tags', '14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
708
        inserts = get_pending_inserts(req)
709
        self.assertEqual(inserts, [])
Sylvain Thénault's avatar
Sylvain Thénault committed
710
        deletes = get_pending_deletes(req)
711
        self.assertEqual(deletes, ['12:tags:13', '12:tags:14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
712
        deletes = get_pending_deletes(req, 12)
713
        self.assertEqual(deletes, ['12:tags:13', '12:tags:14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
714
        deletes = get_pending_deletes(req, 13)
715
        self.assertEqual(deletes, ['12:tags:13'])
Sylvain Thénault's avatar
Sylvain Thénault committed
716
        deletes = get_pending_deletes(req, 14)
717
        self.assertEqual(deletes, ['12:tags:14'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
718
719
720
721
        req.remove_pending_operations()

    def test_remove_pending_operations(self):
        self.remote_call('add_pending_delete', ['12', 'tags', '13'])
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
722
        _, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
Sylvain Thénault's avatar
Sylvain Thénault committed
723
        inserts = get_pending_inserts(req)
724
        self.assertEqual(inserts, ['12:tags:14'])
Sylvain Thénault's avatar
Sylvain Thénault committed
725
        deletes = get_pending_deletes(req)
726
        self.assertEqual(deletes, ['12:tags:13'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
727
        req.remove_pending_operations()
728
729
        self.assertEqual(get_pending_deletes(req), [])
        self.assertEqual(get_pending_inserts(req), [])
730

Adrien Di Mascio's avatar
Adrien Di Mascio committed
731
732
733
734

    def test_add_inserts(self):
        res, req = self.remote_call('add_pending_inserts',
                                    [('12', 'tags', '13'), ('12', 'tags', '14')])
Sylvain Thénault's avatar
Sylvain Thénault committed
735
        inserts = get_pending_inserts(req)
736
        self.assertEqual(inserts, ['12:tags:13', '12:tags:14'])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
737
        req.remove_pending_operations()
738

Adrien Di Mascio's avatar
Adrien Di Mascio committed
739
740
741

    # silly tests
    def test_external_resource(self):
742
        self.assertEqual(self.remote_call('external_resource', 'RSS_LOGO')[0],
743
                          json_dumps(self.config.uiprops['RSS_LOGO']))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
744
    def test_i18n(self):
745
        self.assertEqual(self.remote_call('i18n', ['bimboom'])[0],
746
                          json_dumps(['bimboom']))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
747
748

    def test_format_date(self):
749
        self.assertEqual(self.remote_call('format_date', '2007-01-01 12:00:00')[0],
750
                          json_dumps('2007/01/01'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
751

752
753
754
755
    def test_ajaxfunc_noparameter(self):
        @ajaxfunc
        def foo(self, x, y):
            return 'hello'
756
757
758
759
760
761
        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)
762
        req = self.request()
763
        f = appobject(req)
764
765
766
        self.assertEqual(f(12, 13), 'hello')

    def test_ajaxfunc_checkpageid(self):
767
        @ajaxfunc(check_pageid=True)
768
        def foo(self, x, y):
769
770
771
772
773
774
775
            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)
776
777
        # no pageid
        req = self.request()
778
        f = appobject(req)
779
780
781
782
783
784
        self.assertRaises(RemoteCallFailed, f, 12, 13)

    def test_ajaxfunc_json(self):
        @ajaxfunc(output_type='json')
        def foo(self, x, y):
            return x + y
785
786
787
788
789
790
        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')
791
792
        # no pageid
        req = self.request()
793
        f = appobject(req)