unittest_views_basecontrollers.py 53.9 KB
Newer Older
1
# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
# contact https://www.logilab.fr/ -- mailto:contact@logilab.fr
3
4
5
6
7
8
9
10
#
# 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
# 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
17
# with CubicWeb.  If not, see <https://www.gnu.org/licenses/>.
18
"""cubicweb.web.views.basecontrollers unit tests"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
19

20
import time
Denis Laxalde's avatar
Denis Laxalde committed
21
from urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs
22
23
24

import lxml

25
from logilab.common.testlib import unittest_main
Adrien Di Mascio's avatar
Adrien Di Mascio committed
26

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

40

41
42
43
class ViewControllerTC(PyramidCWTest):
    settings = {"cubicweb.bwcompat": True}

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


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

68

69
class EditControllerTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
70
    def setUp(self):
71
        CubicWebTC.setUp(self)
72
        self.assertIn("users", self.schema.eschema("CWGroup").get_groups("read"))
73

Adrien Di Mascio's avatar
Adrien Di Mascio committed
74
    def tearDown(self):
75
        CubicWebTC.tearDown(self)
76
        self.assertIn("users", self.schema.eschema("CWGroup").get_groups("read"))
77

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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
85
    def test_validation_unique(self):
86
        """test creation of two linked entities"""
87
        with self.admin_access.web_request() as req:
88
89
90
91
92
93
94
95
            req.form = {
                "eid": "X",
                "__type:X": "CWUser",
                "_cw_entity_fields:X": "login-subject,upassword-subject",
                "login-subject:X": "admin",
                "upassword-subject:X": "toto",
                "upassword-subject-confirm:X": "toto",
            }
96
97
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
Denis Laxalde's avatar
Denis Laxalde committed
98
            cm.exception.translate(str)
99
            expected = {
100
101
                "": "some relations violate a unicity constraint",
                "login": "login is part of violated unicity constraint",
102
103
            }
            self.assertEqual(cm.exception.errors, expected)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
104

105
    def test_simultaneous_edition_only_one_commit(self):
106
        """Allow two simultaneous edit view of the same entity as long as only one commits"""
107
        with self.admin_access.web_request() as req:
108
            e = req.create_entity("BlogEntry", title="cubicweb.org", content="hop")
109
110
            expected_path = e.rest_path()
            req.cnx.commit()
111
112
113
114
            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
            ]
115
116

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

        with self.admin_access.web_request(**dict(html_form.form_values())) as req:
120
            path, args = self.expect_redirect_handle_request(req, path="edit")
121
122
123
            self.assertEqual(path, expected_path)

    def test_simultaneous_edition_refuse_second_commit(self):
124
        """Disallow committing changes to an entity edited in between"""
125
        with self.admin_access.web_request() as req:
126
            e = req.create_entity("BlogEntry", title="cubicweb.org", content="hop")
127
128
            eid = e.eid
            req.cnx.commit()
129
130
131
132
            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
            ]
133
134
135

        with self.admin_access.web_request() as req2:
            e = req2.entity_from_eid(eid)
136
            e.cw_set(content="hip")
137
138
139
140
141
            req2.cnx.commit()

        form_field_name = "content-subject:%d" % eid
        form_values = dict(html_form.form_values())
        assert form_field_name in form_values
142
        form_values[form_field_name] = "yep"
143
144
145
146
147
148
149
150
        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
151
    def test_user_editing_itself(self):
152
        """checking that a manager user can edit itself"""
153
154
        with self.admin_access.web_request() as req:
            user = req.user
155
156
157
158
159
160
            groupeids = [
                eid
                for eid, in req.execute(
                    "CWGroup G WHERE G name " 'in ("managers", "users")'
                )
            ]
Denis Laxalde's avatar
Denis Laxalde committed
161
162
            groups = [str(eid) for eid in groupeids]
            eid = str(user.eid)
163
            req.form = {
164
165
166
167
168
169
170
171
172
173
174
175
176
                "eid": eid,
                "__type:" + eid: "CWUser",
                "_cw_entity_fields:"
                + eid: "login-subject,firstname-subject,surname-subject,in_group-subject",
                "login-subject:" + eid: str(user.login),
                "surname-subject:" + eid: "Th\xe9nault",
                "firstname-subject:" + eid: "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, "Sylvain")
            self.assertEqual(e.surname, "Th\xe9nault")
177
178
            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
179
180

    def test_user_can_change_its_password(self):
181
        with self.admin_access.repo_cnx() as cnx:
182
            self.create_user(cnx, "user")
183
            cnx.commit()
184
        with self.new_access("user").web_request() as req:
Denis Laxalde's avatar
Denis Laxalde committed
185
            eid = str(req.user.eid)
186
            req.form = {
187
188
189
190
191
192
193
194
195
196
197
                "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
198

Sylvain Thénault's avatar
Sylvain Thénault committed
199
    def test_user_editing_itself_no_relation(self):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
200
201
202
        """checking we can edit an entity without specifying some required
        relations (meaning no changes)
        """
203
204
205
        with self.admin_access.web_request() as req:
            user = req.user
            groupeids = [g.eid for g in user.in_group]
Denis Laxalde's avatar
Denis Laxalde committed
206
            eid = str(user.eid)
207
            req.form = {
208
209
210
211
212
213
214
215
216
217
                "eid": eid,
                "__type:" + eid: "CWUser",
                "_cw_entity_fields:"
                + eid: "login-subject,firstname-subject,surname-subject",
                "login-subject:" + eid: str(user.login),
                "firstname-subject:" + eid: "Th\xe9nault",
                "surname-subject:" + eid: "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)
218
            self.assertEqual(e.login, user.login)
219
220
            self.assertEqual(e.firstname, "Th\xe9nault")
            self.assertEqual(e.surname, "Sylvain")
221
            self.assertEqual([g.eid for g in e.in_group], groupeids)
222
            self.assertEqual(e.cw_adapt_to("IWorkflowable").state, "activated")
223

Adrien Di Mascio's avatar
Adrien Di Mascio committed
224
    def test_create_multiple_linked(self):
225
226
        with self.admin_access.web_request() as req:
            gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
            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": "adim",
                "upassword-subject:X": "toto",
                "upassword-subject-confirm:X": "toto",
                "surname-subject:X": "Di Mascio",
                "in_group-subject:X": str(gueid),
                "__type:Y": "EmailAddress",
                "_cw_entity_fields:Y": "address-subject,use_email-object",
                "address-subject:Y": "dima@logilab.fr",
                "use_email-object:Y": "X",
            }
            path, _params = self.expect_redirect_handle_request(req, "edit")
243
            # should be redirected on the created person
244
            self.assertEqual(path, "cwuser/adim")
245
            e = req.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
246
            self.assertEqual(e.surname, "Di Mascio")
247
            email = e.use_email[0]
248
            self.assertEqual(email.address, "dima@logilab.fr")
249

250
    def test_create_mandatory_inlined(self):
251
        with self.admin_access.web_request() as req:
252
253
254
255
256
257
258
259
260
261
262
263
264
            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": ("coucou.txt", Binary(b"coucou")),
                "described_by_test-object:Y": "X",
            }
            path, _params = self.expect_redirect_handle_request(req, "edit")
            self.assertTrue(path.startswith("salesterm/"), path)
            eid = path.split("/")[1]
265
266
267
268
269
270
271
272
            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)
273
274

    def test_create_mandatory_inlined2(self):
275
        with self.admin_access.web_request() as req:
276
277
278
279
280
281
282
283
284
285
286
287
288
            req.form = {
                "eid": ["X", "Y"],
                "__maineid": "X",
                "__type:X": "Salesterm",
                "_cw_entity_fields:X": "described_by_test-subject",
                "described_by_test-subject:X": "Y",
                "__type:Y": "File",
                "_cw_entity_fields:Y": "data-subject",
                "data-subject:Y": ("coucou.txt", Binary(b"coucou")),
            }
            path, _params = self.expect_redirect_handle_request(req, "edit")
            self.assertTrue(path.startswith("salesterm/"), path)
            eid = path.split("/")[1]
289
290
291
292
293
294
295
296
            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)
297

298
299
300
    def test_edit_mandatory_inlined3_object(self):
        # non regression test for #3120495. Without the fix, leads to
        # "unhashable type: 'list'" error
301
        with self.admin_access.web_request() as req:
Denis Laxalde's avatar
Denis Laxalde committed
302
            cwrelation = str(req.execute('CWEType X WHERE X name "CWSource"')[0][0])
303
304
305
306
307
308
309
            req.form = {
                "eid": [cwrelation],
                "__maineid": cwrelation,
                "__type:" + cwrelation: "CWEType",
                "_cw_entity_fields:" + cwrelation: "to_entity-object",
                "to_entity-object:" + cwrelation: [9999, 9998],
            }
310
            with req.cnx.deny_all_hooks_but():
311
312
                path, _params = self.expect_redirect_handle_request(req, "edit")
            self.assertTrue(path.startswith("cwetype/CWSource"), path)
313

Adrien Di Mascio's avatar
Adrien Di Mascio committed
314
    def test_edit_multiple_linked(self):
315
        with self.admin_access.web_request() as req:
316
317
318
319
320
321
322
323
324
325
326
327
328
            peid = str(self.create_user(req, "adim").eid)
            req.form = {
                "eid": [peid, "Y"],
                "__maineid": peid,
                "__type:" + peid: "CWUser",
                "_cw_entity_fields:" + peid: "surname-subject",
                "surname-subject:" + peid: "Di Masci",
                "__type:Y": "EmailAddress",
                "_cw_entity_fields:Y": "address-subject,use_email-object",
                "address-subject:Y": "dima@logilab.fr",
                "use_email-object:Y": peid,
            }
            path, _params = self.expect_redirect_handle_request(req, "edit")
329
            # should be redirected on the created person
330
            self.assertEqual(path, "cwuser/adim")
331
332
            e = req.execute('Any P WHERE P surname "Di Masci"').get_entity(0, 0)
            email = e.use_email[0]
333
            self.assertEqual(email.address, "dima@logilab.fr")
334

335
            # with self.admin_access.web_request() as req:
Denis Laxalde's avatar
Denis Laxalde committed
336
            emaileid = str(email.eid)
337
338
339
340
341
342
343
344
345
346
347
            req.form = {
                "eid": [peid, emaileid],
                "__type:" + peid: "CWUser",
                "_cw_entity_fields:" + peid: "surname-subject",
                "surname-subject:" + peid: "Di Masci",
                "__type:" + emaileid: "EmailAddress",
                "_cw_entity_fields:" + emaileid: "address-subject,use_email-object",
                "address-subject:" + emaileid: "adim@logilab.fr",
                "use_email-object:" + emaileid: peid,
            }
            self.expect_redirect_handle_request(req, "edit")
348
            email.cw_clear_all_caches()
349
            self.assertEqual(email.address, "adim@logilab.fr")
350

Adrien Di Mascio's avatar
Adrien Di Mascio committed
351
    def test_password_confirm(self):
352
        """test creation of two linked entities"""
353
354
        with self.admin_access.web_request() as req:
            user = req.user
355
356
357
358
359
360
361
362
            req.form = {
                "eid": "X",
                "__cloned_eid:X": str(user.eid),
                "__type:X": "CWUser",
                "_cw_entity_fields:X": "login-subject,upassword-subject",
                "login-subject:X": "toto",
                "upassword-subject:X": "toto",
            }
363
364
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
365
366
367
368
369
370
371
372
373
374
375
376
377
            self.assertEqual(
                {"upassword-subject": "password and confirmation don't match"},
                cm.exception.errors,
            )
            req.form = {
                "__cloned_eid:X": str(user.eid),
                "eid": "X",
                "__type:X": "CWUser",
                "_cw_entity_fields:X": "login-subject,upassword-subject",
                "login-subject:X": "toto",
                "upassword-subject:X": "toto",
                "upassword-subject-confirm:X": "tutu",
            }
378
379
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
380
381
382
383
            self.assertEqual(
                {"upassword-subject": "password and confirmation don't match"},
                cm.exception.errors,
            )
Adrien Di Mascio's avatar
Adrien Di Mascio committed
384
385

    def test_interval_bound_constraint_success(self):
386
        with self.admin_access.repo_cnx() as cnx:
387
388
389
390
            feid = cnx.execute(
                'INSERT File X: X data_name "toto.txt", X data %(data)s',
                {"data": Binary(b"yo")},
            )[0][0]
391
392
393
            cnx.commit()

        with self.admin_access.web_request(rollbackfirst=True) as req:
394
395
396
397
398
399
400
            req.form = {
                "eid": ["X"],
                "__type:X": "Salesterm",
                "_cw_entity_fields:X": "amount-subject,described_by_test-subject",
                "amount-subject:X": "-10",
                "described_by_test-subject:X": str(feid),
            }
401
402
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
Denis Laxalde's avatar
Denis Laxalde committed
403
            cm.exception.translate(str)
404
405
406
            self.assertEqual(
                {"amount-subject": "value -10 must be >= 0"}, cm.exception.errors
            )
407
408

        with self.admin_access.web_request(rollbackfirst=True) as req:
409
410
411
412
413
414
415
            req.form = {
                "eid": ["X"],
                "__type:X": "Salesterm",
                "_cw_entity_fields:X": "amount-subject,described_by_test-subject",
                "amount-subject:X": "110",
                "described_by_test-subject:X": str(feid),
            }
416
417
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
Denis Laxalde's avatar
Denis Laxalde committed
418
            cm.exception.translate(str)
419
420
421
            self.assertEqual(
                cm.exception.errors, {"amount-subject": "value 110 must be <= 100"}
            )
422
423

        with self.admin_access.web_request(rollbackfirst=True) as req:
424
425
426
427
428
429
430
431
            req.form = {
                "eid": ["X"],
                "__type:X": "Salesterm",
                "_cw_entity_fields:X": "amount-subject,described_by_test-subject",
                "amount-subject:X": "10",
                "described_by_test-subject:X": str(feid),
            }
            self.expect_redirect_handle_request(req, "edit")
432
            # should be redirected on the created
433
434
            # eid = params['rql'].split()[-1]
            e = req.execute("Salesterm X").get_entity(0, 0)
435
            self.assertEqual(e.amount, 10)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
436

437
438
439
    def test_interval_bound_constraint_validateform(self):
        """Test the FormValidatorController controller on entity with
        constrained attributes"""
440
        with self.admin_access.repo_cnx() as cnx:
441
442
443
444
445
            feid = cnx.execute(
                'INSERT File X: X data_name "toto.txt", X data %(data)s',
                {"data": Binary(b"yo")},
            )[0][0]
            seid = cnx.create_entity("Salesterm", amount=0, described_by_test=feid).eid
446
            cnx.commit()
447
448

        # ensure a value that violate a constraint is properly detected
449
        with self.admin_access.web_request(rollbackfirst=True) as req:
450
451
452
453
454
455
456
457
            req.form = {
                "eid": [str(seid)],
                "__type:%s" % seid: "Salesterm",
                "_cw_entity_fields:%s" % seid: "amount-subject",
                "amount-subject:%s" % seid: "-10",
            }
            self.assertMultiLineEqual(
                """<script type="text/javascript">
458
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
459
460
461
462
</script>"""
                % seid,
                self.ctrl_publish(req, "validateform").decode("ascii"),
            )
463
464

        # ensure a value that comply a constraint is properly processed
465
        with self.admin_access.web_request(rollbackfirst=True) as req:
466
467
468
469
470
471
472
473
            req.form = {
                "eid": [str(seid)],
                "__type:%s" % seid: "Salesterm",
                "_cw_entity_fields:%s" % seid: "amount-subject",
                "amount-subject:%s" % seid: "20",
            }
            self.assertMultiLineEqual(
                """<script type="text/javascript">
474
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
475
476
477
478
479
480
481
482
483
</script>""",
                self.ctrl_publish(req, "validateform").decode("ascii"),
            )
            self.assertEqual(
                20,
                req.execute("Any V WHERE X amount V, X eid %(eid)s", {"eid": seid})[0][
                    0
                ],
            )
484

485
        with self.admin_access.web_request(rollbackfirst=True) as req:
486
487
488
489
490
491
492
            req.form = {
                "eid": ["X"],
                "__type:X": "Salesterm",
                "_cw_entity_fields:X": "amount-subject,described_by_test-subject",
                "amount-subject:X": "0",
                "described_by_test-subject:X": str(feid),
            }
493
494
495
496
497
498
499

            # 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)
500

501
            class ValidationErrorInOpAfterHook(Hook):
502
503
504
505
                __regid__ = "valerror-op-after-hook"
                __select__ = Hook.__select__ & is_instance("Salesterm")
                events = ("after_add_entity",)

506
507
508
509
                def __call__(self):
                    MyOperation(self._cw, entity=self.entity)

            with self.temporary_appobjects(ValidationErrorInOpAfterHook):
510
511
                self.assertMultiLineEqual(
                    """<script type="text/javascript">
512
 window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
513
514
515
</script>""",
                    self.ctrl_publish(req, "validateform").decode("ascii"),
                )
516

517
518
            self.assertMultiLineEqual(
                """<script type="text/javascript">
519
 window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
520
521
522
</script>""",
                self.ctrl_publish(req, "validateform").decode("ascii"),
            )
523

Adrien Di Mascio's avatar
Adrien Di Mascio committed
524
525
    def test_req_pending_insert(self):
        """make sure req's pending insertions are taken into account"""
526
        with self.admin_access.web_request() as req:
527
            tmpgroup = req.create_entity("CWGroup", name="test")
528
529
530
            user = req.user
            req.cnx.commit()
        with self.admin_access.web_request(**req_form(user)) as req:
531
532
533
534
535
536
537
538
539
540
541
            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"])
542
            self.assertEqual(get_pending_inserts(req), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
543
544
545

    def test_req_pending_delete(self):
        """make sure req's pending deletions are taken into account"""
546
547
        with self.admin_access.web_request() as req:
            user = req.user
548
549
550
551
552
553
554
555
556
557
            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}
                )
            ]
558
            # just make sure everything was set correctly
559
            self.assertCountEqual(usergroups, ["managers", "test"])
560
561
562
            req.cnx.commit()
            # now try to delete the relation
        with self.admin_access.web_request(**req_form(user)) as req:
563
564
565
566
567
568
569
570
571
            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"])
572
            self.assertEqual(get_pending_deletes(req), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
573
574

    def test_redirect_apply_button(self):
575
        with self.admin_access.web_request() as req:
576
            redirectrql = rql_for_eid(4012)  # whatever
577
            req.form = {
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
                "eid": "A",
                "__maineid": "A",
                "__type:A": "BlogEntry",
                "_cw_entity_fields:A": "content-subject,title-subject",
                "content-subject:A": '"13:03:43"',
                "title-subject:A": "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")
594
            self.assertNotEqual(int(eid), 4012)
595
596
597
            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
598
599

    def test_redirect_ok_button(self):
600
        with self.admin_access.web_request() as req:
601
            redirectrql = rql_for_eid(4012)  # whatever
602
            req.form = {
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
                "eid": "A",
                "__maineid": "A",
                "__type:A": "BlogEntry",
                "_cw_entity_fields:A": "content-subject,title-subject",
                "content-subject:A": '"13:03:43"',
                "title-subject:A": "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
620
621

    def test_redirect_delete_button(self):
622
        with self.admin_access.web_request() as req:
623
624
625
626
627
628
629
630
631
632
633
634
635
636
            eid = req.create_entity("BlogEntry", title="hop", content="hop").eid
            req.form = {
                "eid": str(eid),
                "__type:%s" % eid: "BlogEntry",
                "__action_delete": "",
            }
            path, params = self.expect_redirect_handle_request(req, "edit")
            self.assertEqual(path, "blogentry")
            self.assertIn("_cwmsgid", params)
            eid = req.create_entity("EmailAddress", address="hop@logilab.fr").eid
            req.execute(
                "SET X use_email E WHERE E eid %(e)s, X eid %(x)s",
                {"x": req.user.eid, "e": eid},
            )
637
            req.cnx.commit()
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
            req.form = {
                "eid": str(eid),
                "__type:%s" % eid: "EmailAddress",
                "__action_delete": "",
            }
            path, params = self.expect_redirect_handle_request(req, "edit")
            self.assertEqual(path, "cwuser/admin")
            self.assertIn("_cwmsgid", params)
            eid1 = req.create_entity("BlogEntry", title="hop", content="hop").eid
            eid2 = req.create_entity("EmailAddress", address="hop@logilab.fr").eid
            req.form = {
                "eid": [str(eid1), str(eid2)],
                "__type:%s" % eid1: "BlogEntry",
                "__type:%s" % eid2: "EmailAddress",
                "__action_delete": "",
            }
            path, params = self.expect_redirect_handle_request(req, "edit")
            self.assertEqual(path, "view")
            self.assertIn("_cwmsgid", params)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
657

658
    def test_simple_copy(self):
659
        with self.admin_access.web_request() as req:
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
            blog = req.create_entity("Blog", title="my-blog")
            blogentry = req.create_entity(
                "BlogEntry", title="entry1", content="content1", entry_of=blog
            )
            req.form = {
                "__maineid": "X",
                "eid": "X",
                "__cloned_eid:X": blogentry.eid,
                "__type:X": "BlogEntry",
                "_cw_entity_fields:X": "title-subject,content-subject",
                "title-subject:X": "entry1-copy",
                "content-subject:X": "content1",
            }
            self.expect_redirect_handle_request(req, "edit")
            blogentry2 = req.find("BlogEntry", title="entry1-copy").one()
675
676
677
678
            self.assertEqual(blogentry2.entry_of[0].eid, blog.eid)

    def test_skip_copy_for(self):
        with self.admin_access.web_request() as req:
679
680
681
682
683
            blog = req.create_entity("Blog", title="my-blog")
            blogentry = req.create_entity(
                "BlogEntry", title="entry1", content="content1", entry_of=blog
            )
            blogentry.__class__.cw_skip_copy_for = [("entry_of", "subject")]
684
            try:
685
686
687
688
689
690
691
692
693
694
695
                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": "entry1-copy",
                    "content-subject:X": "content1",
                }
                self.expect_redirect_handle_request(req, "edit")
                blogentry2 = req.find("BlogEntry", title="entry1-copy").one()
696
697
698
699
                # entry_of should not be copied
                self.assertEqual(len(blogentry2.entry_of), 0)
            finally:
                blogentry.__class__.cw_skip_copy_for = []
700

701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
    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 = {
719
720
721
722
723
724
725
                    "eid": "X",
                    "__type:X": "CWUser",
                    "_cw_entity_fields:X": "login-subject,upassword-subject,in_group-subject",
                    "login-subject:X": "adim",
                    "upassword-subject:X": "toto",
                    "upassword-subject-confirm:X": "toto",
                    "in_group-subject:X": repr(gueid),
726
727
                }
                nb_process_posted_calls = [0]
728
                self.expect_redirect_handle_request(req, "edit")
729
                self.assertEqual(nb_process_posted_calls[0], 1)
730
                user = req.find("CWUser", login="adim").one()
731
732
                self.assertEqual(set(g.eid for g in user.in_group), set([gueid]))
                req.form = {
733
734
735
736
737
738
739
740
741
742
743
                    "eid": ["X", "Y"],
                    "__type:X": "CWUser",
                    "_cw_entity_fields:X": "login-subject,upassword-subject,in_group-subject",
                    "login-subject:X": "dlax",
                    "upassword-subject:X": "toto",
                    "upassword-subject-confirm:X": "toto",
                    "in_group-subject:X": repr(gueid),
                    "__type:Y": "EmailAddress",
                    "_cw_entity_fields:Y": "address-subject,use_email-object",
                    "address-subject:Y": "dlax@cw.org",
                    "use_email-object:Y": "X",
744
745
                }
                nb_process_posted_calls = [0]
746
747
748
749
750
751
752
753
                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="dlax").one()
                self.assertEqual(
                    set(e.address for e in user.use_email), set(["dlax@cw.org"])
                )
754
755
756
757

        finally:
            ff.RelationField.process_posted = orig_process_posted

Adrien Di Mascio's avatar
Adrien Di Mascio committed
758
    def test_nonregr_eetype_etype_editing(self):
759
        """non-regression test checking that a manager user can edit a CWEType entity"""
760
        with self.admin_access.web_request() as req:
761
762
763
764
765
766
            groupeids = sorted(
                eid
                for eid, in req.execute(
                    "CWGroup G " 'WHERE G name in ("managers", "users")'
                )
            )
Denis Laxalde's avatar
Denis Laxalde committed
767
            groups = [str(eid) for eid in groupeids]
768
            cwetypeeid = req.execute('CWEType X WHERE X name "CWEType"')[0][0]
769
770
771
772
773
774
775
            basegroups = [
                str(eid)
                for eid, in req.execute(
                    "CWGroup G " "WHERE X read_permission G, X eid %(x)s",
                    {"x": cwetypeeid},
                )
            ]
Denis Laxalde's avatar
Denis Laxalde committed
776
            cwetypeeid = str(cwetypeeid)
777
            req.form = {
778
779
780
781
782
783
784
785
                "eid": cwetypeeid,
                "__type:" + cwetypeeid: "CWEType",
                "_cw_entity_fields:"
                + cwetypeeid: "name-subject,final-subject,description-subject,read_permission-subject",
                "name-subject:" + cwetypeeid: "CWEType",
                "final-subject:" + cwetypeeid: "",
                "description-subject:" + cwetypeeid: "users group",
                "read_permission-subject:" + cwetypeeid: groups,
786
            }
787
            try:
788
789
790
791
792
                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")
793
794
795
                self.assertEqual(sorted(g.eid for g in e.read_permission), groupeids)
            finally:
                # restore
796
797
798
799
                req.execute(
                    'SET X read_permission Y WHERE X name "CWEType", '
                    "Y eid IN (%s), NOT X read_permission Y" % (",".join(basegroups))
                )
800
                req.cnx.commit()
801

Adrien Di Mascio's avatar
Adrien Di Mascio committed
802
803
804
805
    def test_nonregr_strange_text_input(self):
        """non-regression test checking text input containing "13:03:43"

        this seems to be postgres (tsearch?) specific
806
        """
807
808
        with self.admin_access.web_request() as req:
            req.form = {
809
810
811
812
813
814
815
816
817
818
819
820
821
                "eid": "A",
                "__maineid": "A",
                "__type:A": "BlogEntry",
                "_cw_entity_fields:A": "title-subject,content-subject",
                "title-subject:A": '"13:03:40"',
                "content-subject:A": '"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)
822
823
            self.assertEqual(e.title, '"13:03:40"')
            self.assertEqual(e.content, '"13:03:43"')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
824
825

    def test_nonregr_multiple_empty_email_addr(self):
826
827
        with self.admin_access.web_request() as req:
            gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
828
829
830
831
832
833
834
835
836
837
838
839
840
841
            req.form = {
                "eid": ["X", "Y"],
                "__type:X": "CWUser",
                "_cw_entity_fields:X": "login-subject,upassword-subject,in_group-subject",
                "login-subject:X": "adim",
                "upassword-subject:X": "toto",
                "upassword-subject-confirm:X": "toto",
                "in_group-subject:X": repr(gueid),
                "__type:Y": "EmailAddress",
                "_cw_entity_fields:Y": "address-subject,alias-subject,use_email-object",
                "address-subject:Y": "",
                "alias-subject:Y": "",
                "use_email-object:Y": "X",
            }
842
843
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
844
            self.assertEqual(cm.exception.errors, {"address-subject": "required field"})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
845
846

    def test_nonregr_copy(self):
847
848
        with self.admin_access.web_request() as req:
            user = req.user
849
850
851
852
853
854
855
856
857
858
859
860
            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": "toto",
                "upassword-subject:X": "toto",
                "upassword-subject-confirm:X": "toto",
            }
            path, _params = self.expect_redirect_handle_request(req, "edit")
            self.assertEqual(path, "cwuser/toto")
861
            e = req.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
862
863
            self.assertEqual(e.login, "toto")
            self.assertEqual(e.in_group[0].name, "managers")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
864
865

    def test_nonregr_rollback_on_validation_error(self):
866
867
        with self.admin_access.web_request(url="edit") as req:
            p = self.create_user(req, "doe")
868
            # do not try to skip 'primary_email' for this test
869
870
            old_skips = p.__class__.cw_skip_copy_for
            p.__class__.cw_skip_copy_for = ()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
871
            try:
872
873
874
875
876
877
878
879
880
881
882
883
884
885
                e = req.create_entity("EmailAddress", address="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": "dodo",
                    "surname-subject:X": "Boom",
                    "__errorurl": "whatever but required",
                }
886
887
888
889
890
                # 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:
891
                    self.app_handle_request(req)
892
                except Redirect:
893
894
895
                    req.form["rql"] = "Any X WHERE X eid %s" % p.eid
                    req.form["vid"] = "copy"
                    self.app_handle_request(req, "view")
896
897
898
                rset = req.execute('CWUser P WHERE P surname "Boom"')
                self.assertEqual(len(rset), 0)
            finally:
899
                p.__class__.cw_skip_copy_for = old_skips
Adrien Di Mascio's avatar
Adrien Di Mascio committed
900

901
    def test_regr_inlined_forms(self):
902
        with self.admin_access.web_request() as req:
903
            self.schema["described_by_test"].inlined = False
904
            try:
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
                req.data["eidmap"] = {}
                req.data["pending_others"] = set()
                req.data["pending_inlined"] = {}
                req.form = {
                    "eid": ["X", "Y"],
                    "__maineid": "X",
                    "__type:X": "Salesterm",
                    "_cw_entity_fields:X": "described_by_test-subject",
                    "described_by_test-subject:X": "Y",
                    "__type:Y": "File",
                    "_cw_entity_fields:Y": "data-subject",
                    "data-subject:Y": ("coucou.txt", Binary(b"coucou")),
                }
                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)
923
924
925
                # don't call publish to enforce select order
                editctrl.errors = []
                editctrl._to_create = {}
926
927
928
929
                editctrl.edit_entity(
                    values_by_eid["X"]
                )  # #3064653 raise ValidationError
                editctrl.edit_entity(values_by_eid["Y"])
930
            finally:
931
                self.schema["described_by_test"].inlined = False
932

Adrien Di Mascio's avatar
Adrien Di Mascio committed
933

934
class ReportBugControllerTC(CubicWebTC):
935
    def test_usable_by_guest(self):
936
937
938
939
940
941
        with self.new_access("anon").web_request() as req:
            self.assertRaises(
                NoSelectableObject, self.vreg["controllers"].select, "reportbug", req
            )
        with self.new_access("anon").web_request(description="hop") as req:
            self.vreg["controllers"].select("reportbug", req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
942
943


944
class AjaxControllerTC(CubicWebTC):
945
    tested_controller = "ajax"
Adrien Di Mascio's avatar
Adrien Di Mascio committed
946
947

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

    def setup_database(self):
952
        with self.admin_access.repo_cnx() as cnx:
953
954
955
            self.pytag = cnx.create_entity("Tag", name="python")
            self.cubicwebtag = cnx.create_entity("Tag", name="cubicweb")
            self.john = self.create_user(cnx, "John")
956
            cnx.commit()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
957
958
959

    ## tests ##################################################################
    def test_simple_exec(self):
960
961
962
        with self.admin_access.web_request(
            rql='CWUser P WHERE P login "John"', pageid="123", fname="view"
        ) as req:
963
964
965
966
            ctrl = self.ctrl(req)
            rset = self.john.as_rset()
            rset.req = req
            source = ctrl.publish()
967
            self.assertTrue(source.startswith(b"<div>"))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
968

969
970
971
972
973
    #     def test_json_exec(self):
    #         rql = 'Any T,N WHERE T is Tag, T name N'
    #         ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
    #         self.assertEqual(ctrl.publish(),
    #                           json_dumps(self.execute(rql).rows))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
974
975

    def test_remote_add_existing_tag(self):
976
        with self.remote_calling("tag_entity", self.john.eid, ["python"]) as (_, req):
977
            self.assertCountEqual(
978
979
980
                [tname for tname, in req.execute("Any N WHERE T is Tag, T name N")],
                ["python", "cubicweb"],
            )
981
            self.assertEqual(
982
983
984
                req.execute("Any N WHERE T tags P, P is CWUser, T name N").rows,
                [["python"]],
            )
985

Adrien Di Mascio's avatar
Adrien Di Mascio committed
986
    def test_remote_add_new_tag(self):
987
988
989
990
        with self.remote_calling("tag_entity", self.john.eid, ["javascript"]) as (
            _,
            req,
        ):
991
            self.assertCountEqual(
992
993
994
                [tname for tname, in req.execute("Any N WHERE T is Tag, T name N")],
                ["python", "cubicweb", "javascript"],
            )
995
            self.assertEqual(
996
997
998
                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
999

1000
1001
1002
    def test_maydel_perms(self):
        """Check that AjaxEditRelationCtxComponent calls rdef.check with a
        sufficient context"""
1003
        with self.remote_calling("tag_entity", self.john.eid, ["python"]) as (_, req):
1004
            req.cnx.commit()
1005
        with self.temporary_permissions(
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
            (
                self.schema["tags"].rdefs["Tag", "CWUser"],
                {"delete": (RRQLExpression("S owned_by U"),)},
            )
        ):
            with self.admin_access.web_request(
                rql='CWUser P WHERE P login "John"',
                pageid="123",
                fname="view",
                session=req.session,
            ) as req:
1017
1018
1019
1020
1021
                ctrl = self.ctrl(req)
                rset = self.john.as_rset()
                rset.req = req
                source = ctrl.publish()
                # maydel jscall
1022
                self.assertIn(b"ajaxBoxRemoveLinkedEntity", source)
1023

Adrien Di Mascio's avatar
Adrien Di Mascio committed
1024
    def test_pending_insertion(self):
1025
1026
1027
1028
        with self.remote_calling("add_pending_inserts", [["12", "tags", "13"]]) as (
            _,
            req,
        ):
1029
1030
1031
            deletes = get_pending_deletes(req)
            self.assertEqual(deletes, [])
            inserts = get_pending_inserts(req)
1032
1033
1034
1035
            self.assertEqual(inserts, ["12:tags:13"])
        with self.remote_calling(
            "add_pending_inserts", [["12", "tags", "14"]], session=req.session
        ) as (_, req):
1036
1037
1038
            deletes = get_pending_deletes(req)
            self.assertEqual(deletes, [])
            inserts = get_pending_inserts(req)
1039
            self.assertCountEqual(inserts, ["12:tags:13", "12:tags:14"])
1040
            inserts = get_pending_inserts(req, 12)
1041
            self.assertCountEqual(inserts, ["12:tags:13", "12:tags:14"])
1042
            inserts = get_pending_inserts(req, 13)
1043
            self.assertEqual(inserts, ["12:tags:13"])
1044
            inserts = get_pending_inserts(req, 14)
1045
            self.assertEqual(inserts, ["12:tags:14"])
1046
            req.remove_pending_operations()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
1047
1048

    def test_pending_deletion(self):
1049
1050
1051
1052
        with self.remote_calling("add_pending_delete", ["12", "tags", "13"]) as (
            _,
            req,
        ):
1053
1054
1055
            inserts = get_pending_inserts(req)
            self.assertEqual(inserts, [])
            deletes = get_pending_deletes(req)
1056
1057
1058
1059
            self.assertEqual(deletes, ["12:tags:13"])
        with self.remote_calling(
            "add_pending_delete", ["12", "tags", "14"], session=req.session
        ) as (_, req):
1060
1061
1062
            inserts = get_pending_inserts(req)
            self.assertEqual(inserts, [])
            deletes = get_pending_deletes(req)
1063
            self.assertCountEqual(deletes, ["12:tags:13", "12:tags:14"])
1064
            deletes = get_pending_deletes(req, 12)
1065
            self.assertCountEqual(deletes, ["12:tags:13", "12:tags:14"])
1066
            deletes = get_pending_deletes(req, 13)
1067
            self.assertEqual(deletes, ["12:tags:13"])
1068
            deletes = get_pending_deletes(req, 14)
1069
            self.assertEqual(deletes, ["12:tags:14"])