Issues (87)

tcms/tests/__init__.py (2 issues)

1
# -*- coding: utf-8 -*-
2
# pylint: disable=invalid-name
3
4
from django import test
5
from django.conf import settings
6
from django.contrib.auth.models import Permission
7
from django.core.files.uploadedfile import SimpleUploadedFile
8
from django.urls import reverse
9
10
from tcms.testcases.models import TestCaseStatus
11
from tcms.testruns.models import TestExecutionStatus
12
from tcms.tests.factories import (
13
    BuildFactory,
14
    ProductFactory,
15
    TestCaseFactory,
16
    TestExecutionFactory,
17
    TestPlanFactory,
18
    TestRunFactory,
19
    UserFactory,
20
    VersionFactory,
21
)
22
23
24 View Code Duplication
def user_should_have_perm(user, perm):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
25
    if isinstance(perm, str):
26
        try:
27
            app_label, codename = perm.split(".")
28
        except ValueError:
29
            raise ValueError(f'"{perm}" should be: app_label.perm_codename') from None
30
        else:
31
            if not app_label or not codename:
32
                raise ValueError("Invalid app_label or codename")
33
            user.user_permissions.add(
34
                Permission.objects.get(
35
                    content_type__app_label=app_label, codename=codename
36
                )
37
            )
38
    elif isinstance(perm, Permission):
39
        user.user_permissions.add(perm)
40
    else:
41
        raise TypeError("perm should be an instance of either str or Permission")
42
43
44 View Code Duplication
def remove_perm_from_user(user, perm):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
45
    """Remove a permission from an user"""
46
47
    if isinstance(perm, str):
48
        try:
49
            app_label, codename = perm.split(".")
50
        except ValueError:
51
            raise ValueError(f'"{perm}" should be: app_label.perm_codename') from None
52
        else:
53
            if not app_label or not codename:
54
                raise ValueError("Invalid app_label or codename")
55
            get_permission = Permission.objects.get
56
            user.user_permissions.remove(
57
                get_permission(content_type__app_label=app_label, codename=codename)
58
            )
59
    elif isinstance(perm, Permission):
60
        user.user_permissions.remove(perm)
61
    else:
62
        raise TypeError("perm should be an instance of either str or Permission")
63
64
65
def create_request_user(username=None, password=None):
66
    if username:
67
        user = UserFactory(username=username)
68
    else:
69
        user = UserFactory()
70
    if password:
71
        user.set_password(password)
72
    else:
73
        user.set_password("password")
74
    user.save()
75
    return user
76
77
78
class LoggedInTestCase(test.TestCase):
79
    """
80
    Test case class for logged-in users which also provides couple of
81
    helper assertion methods.
82
    """
83
84
    @classmethod
85
    def setUpTestData(cls):
86
        cls.tester = UserFactory()
87
        cls.tester.set_password("password")
88
        cls.tester.save()
89
90
    def setUp(self):
91
        """
92
        Login because [view] permissions are required by default!
93
        """
94
        super().setUp()
95
        self.client.login(  # nosec:B106:hardcoded_password_funcarg
96
            username=self.tester.username,
97
            password="password",
98
        )
99
100
    def assertJsonResponse(self, response, expected, status_code=200):
101
        self.assertEqual(status_code, response.status_code)
102
        self.assertJSONEqual(
103
            str(response.content, encoding=settings.DEFAULT_CHARSET), expected
104
        )
105
106
    def attach_file_to(self, app_model_name, obj, file_obj=None):
107
        """
108
        Makes an attachment to use for testing.
109
        """
110
        app_name, model_name = app_model_name.split(".")
111
112
        add_url = reverse(
113
            "attachments:add",
114
            kwargs={
115
                "app_label": app_name,
116
                "model_name": model_name,
117
                "pk": obj.pk,
118
            },
119
        )
120
121
        if not file_obj:
122
            file_obj = SimpleUploadedFile(
123
                "a-test-filename.txt",
124
                b"Hello Test World",
125
                content_type="text/plain",
126
            )
127
        return self.client.post(add_url, {"attachment_file": file_obj}, follow=True)
128
129
130
class BasePlanCase(LoggedInTestCase):
131
    """Base test case by providing essential Plan and Case objects used in tests"""
132
133
    @classmethod
134
    def setUpTestData(cls):
135
        super().setUpTestData()
136
137
        cls.case_status_confirmed = TestCaseStatus.objects.get(name="CONFIRMED")
138
        cls.case_status_proposed = TestCaseStatus.objects.get(name="PROPOSED")
139
140
        cls.product = ProductFactory(name="Kiwi")
141
        cls.version = VersionFactory(value="0.1", product=cls.product)
142
143
        cls.plan = TestPlanFactory(
144
            author=cls.tester, product=cls.product, product_version=cls.version
145
        )
146
147
        cls.case = TestCaseFactory(
148
            author=cls.tester,
149
            default_tester=None,
150
            reviewer=cls.tester,
151
            case_status=cls.case_status_confirmed,
152
            plan=[cls.plan],
153
        )
154
        cls.case.save()  # will generate history object
155
156
        cls.case_1 = TestCaseFactory(
157
            author=cls.tester,
158
            default_tester=None,
159
            reviewer=cls.tester,
160
            case_status=cls.case_status_confirmed,
161
            plan=[cls.plan],
162
        )
163
        cls.case_1.save()  # will generate history object
164
165
        cls.case_2 = TestCaseFactory(
166
            author=cls.tester,
167
            default_tester=None,
168
            reviewer=cls.tester,
169
            case_status=cls.case_status_confirmed,
170
            plan=[cls.plan],
171
        )
172
        cls.case_2.save()  # will generate history object
173
174
        cls.case_3 = TestCaseFactory(
175
            author=cls.tester,
176
            default_tester=None,
177
            reviewer=cls.tester,
178
            case_status=cls.case_status_confirmed,
179
            plan=[cls.plan],
180
        )
181
        cls.case_3.save()  # will generate history object
182
183
        cls.case_4 = TestCaseFactory(
184
            author=cls.tester,
185
            default_tester=None,
186
            reviewer=cls.tester,
187
            case_status=cls.case_status_confirmed,
188
            plan=[cls.plan],
189
        )
190
        cls.case_4.save()  # will generate history object
191
192
        cls.case_5 = TestCaseFactory(
193
            author=cls.tester,
194
            default_tester=None,
195
            reviewer=cls.tester,
196
            case_status=cls.case_status_confirmed,
197
            plan=[cls.plan],
198
        )
199
        cls.case_5.save()  # will generate history object
200
201
        cls.case_6 = TestCaseFactory(
202
            author=cls.tester,
203
            default_tester=None,
204
            reviewer=cls.tester,
205
            case_status=cls.case_status_confirmed,
206
            plan=[cls.plan],
207
        )
208
        cls.case_6.save()  # will generate history object
209
210
211
class BaseCaseRun(BasePlanCase):
212
    """Base test case containing test run and case runs"""
213
214
    @classmethod
215
    def setUpTestData(cls):
216
        super().setUpTestData()
217
218
        cls.status_idle = TestExecutionStatus.objects.filter(weight=0).first()
219
220
        cls.build = BuildFactory(version=cls.version)
221
222
        cls.test_run = TestRunFactory(
223
            plan=cls.plan,
224
            build=cls.build,
225
            manager=cls.tester,
226
            default_tester=cls.tester,
227
        )
228
229
        executions = []
230
        for i, case in enumerate((cls.case_1, cls.case_2, cls.case_3), 1):
231
            executions.append(
232
                TestExecutionFactory(
233
                    assignee=cls.tester,
234
                    run=cls.test_run,
235
                    build=cls.build,
236
                    status=cls.status_idle,
237
                    case=case,
238
                    sortkey=i * 10,
239
                )
240
            )
241
242
        # used in other tests as well
243
        cls.execution_1 = executions[0]
244
        cls.execution_2 = executions[1]
245
        cls.execution_3 = executions[2]
246
247
        cls.test_run_1 = TestRunFactory(
248
            plan=cls.plan,
249
            build=cls.build,
250
            manager=cls.tester,
251
            default_tester=cls.tester,
252
        )
253
254
        # create a few more TestExecution objects
255
        for i, case in enumerate((cls.case_4, cls.case_5, cls.case_6), 1):
256
            executions.append(
257
                TestExecutionFactory(
258
                    assignee=cls.tester,
259
                    tested_by=cls.tester,
260
                    run=cls.test_run_1,
261
                    build=cls.build,
262
                    status=cls.status_idle,
263
                    case=case,
264
                    sortkey=i * 10,
265
                )
266
            )
267
        # used in other tests as well
268
        cls.execution_4 = executions[3]
269
        cls.execution_5 = executions[4]
270
        cls.execution_6 = executions[5]
271
272
273
class PermissionsTestMixin:
274
    base_classes = ["PermissionsTestCase", "APIPermissionsTestCase"]
275
    http_method_names = []  # api, get or post
276
    permission_label = None
277
278
    # skip running if class called directly by test runner
279
    @classmethod
280
    def setUpClass(cls):
281
        if cls.__name__ in cls.base_classes:
282
            return
283
        super().setUpClass()
284
285
    @classmethod
286
    def tearDownClass(cls):
287
        if cls.__name__ in cls.base_classes:
288
            return
289
        super().tearDownClass()
290
291
    def __call__(self, result=None):
292
        if self.__class__.__name__ in self.base_classes:
293
            return None
294
295
        return super().__call__(result)
296
297
    # end skip running
298
299
    @classmethod
300
    def setUpTestData(cls):
301
        super().setUpTestData()
302
        cls.check_mandatory_attributes()
303
304
    @classmethod
305
    def check_mandatory_attributes(cls):
306
        """
307
        Make sure important class attributes are defined.
308
        """
309
        if not cls.permission_label:
310
            raise RuntimeError(
311
                "Configure `permission_label` attribute for this test class"
312
            )
313
314
        if not cls.http_method_names:
315
            raise RuntimeError(
316
                "Configure `http_method_names` attribute for this test class"
317
            )
318
319
    def verify_api_with_permission(self):
320
        self.fail("Not implemented")
321
322
    def verify_get_with_permission(self):
323
        self.fail("Not implemented")
324
325
    def verify_post_with_permission(self):
326
        self.fail("Not implemented")
327
328
    def verify_api_without_permission(self):
329
        self.fail("Not implemented")
330
331
    def verify_get_without_permission(self):
332
        self.fail("Not implemented")
333
334
    def verify_post_without_permission(self):
335
        self.fail("Not implemented")
336
337
    def test_with_permission(self):
338
        """
339
        Actual test method for positive scenario. Will validate
340
        all of the accepted methods by calling the
341
        verify_X_with_permission() method(s).
342
        """
343
        self.no_permissions_but(self.permission_label)
344
        self.client.login(  # nosec:B106:hardcoded_password_funcarg
345
            username=self.tester.username, password="password"
346
        )
347
348
        for method in self.http_method_names:
349
            function = getattr(self, f"verify_{method}_with_permission")
350
            function()
351
352
    def no_permissions_but(self, tested_permission):
353
        """
354
        Make sure self.tester has no other permissions but
355
        the one required!
356
        """
357
        self.tester.user_permissions.remove()
358
        user_should_have_perm(self.tester, tested_permission)
359
360
    def test_without_permission(self):
361
        """
362
        Actual test method for negative scenario. Will validate
363
        all of the accepted methods by calling the
364
        verify_X_without_permission() method(s).
365
        """
366
        self.all_permissions_except(self.permission_label)
367
        self.client.login(  # nosec:B106:hardcoded_password_funcarg
368
            username=self.tester.username, password="password"
369
        )
370
371
        for method in self.http_method_names:
372
            function = getattr(self, f"verify_{method}_without_permission")
373
            function()
374
375
    def all_permissions_except(self, tested_permission):
376
        """
377
        Make sure self.tester has all other permissions except
378
        the one required!
379
        """
380
        for perm in Permission.objects.all():
381
            user_should_have_perm(self.tester, perm)
382
383
        remove_perm_from_user(self.tester, tested_permission)
384
385
386
class PermissionsTestCase(PermissionsTestMixin, LoggedInTestCase):
387
    """Base class for all tests around view permissions"""
388
389
    url = None
390
    post_data = {}
391
392
    @classmethod
393
    def check_mandatory_attributes(cls):
394
        """
395
        Make sure important class attributes are defined.
396
        """
397
        super().check_mandatory_attributes()
398
399
        if not cls.url:
400
            raise RuntimeError("Configure `url` attribute for this test class")
401
402
        if "post" in cls.http_method_names and not cls.post_data:
403
            raise RuntimeError("Configure `post_data` attribute for this test class")
404
405
        if "post" not in cls.http_method_names and cls.post_data:
406
            raise RuntimeError(
407
                "Unnecessary `post_data` attribute configured for non-POST test!"
408
            )
409
410
    def verify_get_without_permission(self):
411
        """
412
        Implement all validation steps for GET self.url
413
        when self.tester does not have the appropriate permission.
414
415
        Default implementation asserts that user is redirected back
416
        to the login page!
417
        """
418
        response = self.client.get(self.url)
419
        self.assertRedirects(response, reverse("tcms-login") + "?next=" + self.url)
420
421
    def verify_post_without_permission(self):
422
        """
423
        Implement all validation steps for POST self.url
424
        when self.tester does not have the appropriate permission.
425
426
        Default implementation asserts that user is redirected back
427
        to the login page!
428
        """
429
        response = self.client.post(self.url, self.post_data)
430
        self.assertRedirects(response, reverse("tcms-login") + "?next=" + self.url)
431
432
    def all_permissions_except(self, tested_permission):
433
        """
434
        Make sure self.tester has all other permissions except
435
        the one required!
436
        """
437
        for perm in Permission.objects.all():
438
            user_should_have_perm(self.tester, perm)
439
440
        remove_perm_from_user(self.tester, tested_permission)
441