unittest_views_basecontrollers.py 53.8 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
117
        with self.admin_access.web_request():
            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
            req.session.data["pending_insert"] = {(user.eid, "in_group", tmpgroup.eid)}
532
533
534
535
536
537
538
539
            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"])
540
            self.assertEqual(get_pending_inserts(req), [])
Adrien Di Mascio's avatar
Adrien Di Mascio committed
541
542
543

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

    def test_redirect_apply_button(self):
573
        with self.admin_access.web_request() as req:
574
            redirectrql = rql_for_eid(4012)  # whatever
575
            req.form = {
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
                "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")
592
            self.assertNotEqual(int(eid), 4012)
593
594
595
            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
596
597

    def test_redirect_ok_button(self):
598
        with self.admin_access.web_request() as req:
599
            redirectrql = rql_for_eid(4012)  # whatever
600
            req.form = {
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
                "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
618
619

    def test_redirect_delete_button(self):
620
        with self.admin_access.web_request() as req:
621
622
623
624
625
626
627
628
629
630
631
632
633
634
            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},
            )
635
            req.cnx.commit()
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
            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
655

656
    def test_simple_copy(self):
657
        with self.admin_access.web_request() as req:
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
            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()
673
674
675
676
            self.assertEqual(blogentry2.entry_of[0].eid, blog.eid)

    def test_skip_copy_for(self):
        with self.admin_access.web_request() as req:
677
678
679
680
681
            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")]
682
            try:
683
684
685
686
687
688
689
690
691
692
693
                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()
694
695
696
697
                # entry_of should not be copied
                self.assertEqual(len(blogentry2.entry_of), 0)
            finally:
                blogentry.__class__.cw_skip_copy_for = []
698

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

        finally:
            ff.RelationField.process_posted = orig_process_posted

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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
798
799
800
801
    def test_nonregr_strange_text_input(self):
        """non-regression test checking text input containing "13:03:43"

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

    def test_nonregr_multiple_empty_email_addr(self):
822
823
        with self.admin_access.web_request() as req:
            gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
824
825
826
827
828
829
830
831
832
833
834
835
836
837
            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",
            }
838
839
            with self.assertRaises(ValidationError) as cm:
                self.ctrl_publish(req)
840
            self.assertEqual(cm.exception.errors, {"address-subject": "required field"})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
841
842

    def test_nonregr_copy(self):
843
844
        with self.admin_access.web_request() as req:
            user = req.user
845
846
847
848
849
850
851
852
853
854
855
856
            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")
857
            e = req.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
858
859
            self.assertEqual(e.login, "toto")
            self.assertEqual(e.in_group[0].name, "managers")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
860
861

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

897
    def test_regr_inlined_forms(self):
898
        with self.admin_access.web_request() as req:
899
            self.schema["described_by_test"].inlined = False
900
            try:
901
902
903
904
905
906
907
908
909
910
911
912
913
                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")),
                }
914
915
                values_by_eid = {
                    eid: req.extract_entity_params(eid, minparams=2)
916
                    for eid in req.edited_eids()
917
                }
918
                editctrl = self.vreg["controllers"].select("edit", req)
919
920
921
                # don't call publish to enforce select order
                editctrl.errors = []
                editctrl._to_create = {}
922
923
924
925
                editctrl.edit_entity(
                    values_by_eid["X"]
                )  # #3064653 raise ValidationError
                editctrl.edit_entity(values_by_eid["Y"])
926
            finally:
927
                self.schema["described_by_test"].inlined = False
928

Adrien Di Mascio's avatar
Adrien Di Mascio committed
929

930
class ReportBugControllerTC(CubicWebTC):
931
    def test_usable_by_guest(self):
932
933
934
935
936
937
        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
938
939


940
class AjaxControllerTC(CubicWebTC):
941
    tested_controller = "ajax"
Adrien Di Mascio's avatar
Adrien Di Mascio committed
942
943

    def ctrl(self, req=None):
944
945
        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
946
947

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

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

965
966
967
968
969
    #     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
970
971

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

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

996
997
998
    def test_maydel_perms(self):
        """Check that AjaxEditRelationCtxComponent calls rdef.check with a
        sufficient context"""
999
        with self.remote_calling("tag_entity", self.john.eid, ["python"]) as (_, req):
1000
            req.cnx.commit()
1001
        with self.temporary_permissions(
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
            (
                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:
1013
1014
1015
1016
1017
                ctrl = self.ctrl(req)
                rset = self.john.as_rset()
                rset.req = req
                source = ctrl.publish()
                # maydel jscall
1018
                self.assertIn(b"ajaxBoxRemoveLinkedEntity", source)
1019

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

    def test_pending_deletion(self):
1045
1046
1047
1048
        with self.remote_calling("add_pending_delete", ["12", "tags", "13"]) as (
            _,
            req,
        ):
1049
1050
1051
            inserts = get_pending_inserts(req)
            self.assertEqual(inserts, [])
            deletes = get_pending_deletes(req)
1052
1053
1054
1055
            self.assertEqual(deletes, ["12:tags:13"])
        with self.remote_calling(
            "add_pending_delete", ["12", "tags", "14"], session=req.session
        ) as (_, req):
1056
1057
1058
            inserts = get_pending_inserts(req)
            self.assertEqual(inserts, [])
            deletes = get_pending_deletes(req)
1059
            self.assertCountEqual(deletes, ["12:tags:13", "12:tags:14"])
1060
            deletes = get_pending_deletes(req, 12)