1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# pylint: disable=invalid-name |
3
|
|
|
|
4
|
|
|
from http import HTTPStatus |
5
|
|
|
|
6
|
|
|
from django import test |
7
|
|
|
from django.db.models import F |
8
|
|
|
from django.urls import reverse |
9
|
|
|
from django.utils.translation import gettext_lazy as _ |
10
|
|
|
|
11
|
|
|
from tcms.management.models import Product, Version |
12
|
|
|
from tcms.testcases.models import TestCasePlan, TestCaseStatus |
13
|
|
|
from tcms.testplans.models import TestPlan |
14
|
|
|
from tcms.tests import BasePlanCase, user_should_have_perm |
15
|
|
|
from tcms.tests.factories import ( |
16
|
|
|
ClassificationFactory, |
17
|
|
|
PlanTypeFactory, |
18
|
|
|
ProductFactory, |
19
|
|
|
TestCaseFactory, |
20
|
|
|
TestPlanFactory, |
21
|
|
|
UserFactory, |
22
|
|
|
VersionFactory, |
23
|
|
|
) |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
class BasePlanTest(test.TestCase): |
27
|
|
|
@classmethod |
28
|
|
|
def setUpTestData(cls): |
29
|
|
|
cls.user = UserFactory(username="admin", email="[email protected]") |
30
|
|
|
cls.user.set_password("admin") |
31
|
|
|
cls.user.is_superuser = True |
32
|
|
|
cls.user.is_staff = True |
33
|
|
|
cls.user.save() |
34
|
|
|
|
35
|
|
|
cls.classification = ClassificationFactory(name="Auto") |
36
|
|
|
cls.product = ProductFactory(name="Kiwi", classification=cls.classification) |
37
|
|
|
cls.product_version = VersionFactory(value="0.1", product=cls.product) |
38
|
|
|
cls.plan_type = PlanTypeFactory() |
39
|
|
|
|
40
|
|
|
cls.test_plan = TestPlanFactory( |
41
|
|
|
name="another test plan for testing", |
42
|
|
|
product_version=cls.product_version, |
43
|
|
|
author=cls.user, |
44
|
|
|
product=cls.product, |
45
|
|
|
type=cls.plan_type, |
46
|
|
|
) |
47
|
|
|
# add TestCases to plan with status CONFIRMED |
48
|
|
|
for _i in range(5): |
49
|
|
|
TestCaseFactory( |
50
|
|
|
plan=[cls.test_plan], |
51
|
|
|
case_status=TestCaseStatus.objects.get(name="CONFIRMED"), |
52
|
|
|
) |
53
|
|
|
|
54
|
|
|
# also add a few PROPOSED TestCases |
55
|
|
|
for _i in range(3): |
56
|
|
|
TestCaseFactory(plan=[cls.test_plan]) |
57
|
|
|
|
58
|
|
|
cls.plan_id = cls.test_plan.pk |
59
|
|
|
cls.child_plan = TestPlanFactory(parent=cls.test_plan) |
60
|
|
|
|
61
|
|
|
def setUp(self): |
62
|
|
|
super().setUp() |
63
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
64
|
|
|
username=self.user.username, |
65
|
|
|
password="admin", |
66
|
|
|
) |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
class PlanTests(BasePlanTest): |
70
|
|
|
def test_open_plans_search(self): |
71
|
|
|
location = reverse("plans-search") |
72
|
|
|
response = self.client.get(location) |
73
|
|
|
self.assertEqual(response.status_code, HTTPStatus.OK) |
74
|
|
|
|
75
|
|
|
def test_search_page_is_shown_with_get_parameter_used(self): |
76
|
|
|
response = self.client.get( |
77
|
|
|
reverse("plans-search"), {"product": self.product.pk} |
78
|
|
|
) |
79
|
|
|
self.assertContains( |
80
|
|
|
response, |
81
|
|
|
f'<option value="{self.product.pk}" selected>{self.product.name}</option>', |
82
|
|
|
html=True, |
83
|
|
|
) |
84
|
|
|
|
85
|
|
|
def test_plan_details(self): |
86
|
|
|
location = reverse("test_plan_url_short", args=[self.plan_id]) |
87
|
|
|
response = self.client.get(location) |
88
|
|
|
self.assertEqual(response.status_code, HTTPStatus.MOVED_PERMANENTLY) |
89
|
|
|
|
90
|
|
|
response = self.client.get(location, follow=True) |
91
|
|
|
self.assertEqual(response.status_code, HTTPStatus.OK) |
92
|
|
|
|
93
|
|
|
def test_plan_edit(self): |
94
|
|
|
location = reverse("plan-edit", args=[self.plan_id]) |
95
|
|
|
response = self.client.get(location) |
96
|
|
|
self.assertEqual(response.status_code, HTTPStatus.OK) |
97
|
|
|
|
98
|
|
|
def test_plan_history(self): |
99
|
|
|
# note: history URL is generated on the fly and not accessible via name |
100
|
|
|
location = f"/admin/testplans/testplan/{self.plan_id}/history/" |
101
|
|
|
response = self.client.get(location) |
102
|
|
|
self.assertEqual(response.status_code, HTTPStatus.OK) |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
class TestPlanModel(test.TestCase): |
106
|
|
|
"""Test some model operations directly without a view""" |
107
|
|
|
|
108
|
|
|
@classmethod |
109
|
|
|
def setUpTestData(cls): |
110
|
|
|
cls.plan_tester = UserFactory(username="tester") |
111
|
|
|
cls.plan_tester.set_password("password") |
112
|
|
|
cls.plan_tester.save() |
113
|
|
|
user_should_have_perm(cls.plan_tester, "testplans.view_testplan") |
114
|
|
|
|
115
|
|
|
cls.plan_1 = TestPlanFactory() |
116
|
|
|
cls.testcase_1 = TestCaseFactory() |
117
|
|
|
cls.testcase_2 = TestCaseFactory() |
118
|
|
|
|
119
|
|
|
cls.plan_1.add_case(cls.testcase_1) |
120
|
|
|
cls.plan_1.add_case(cls.testcase_2) |
121
|
|
|
|
122
|
|
|
def test_plan_delete_case(self): |
123
|
|
|
self.plan_1.delete_case(self.testcase_1) |
124
|
|
|
cases_left = TestCasePlan.objects.filter(plan=self.plan_1.pk) |
125
|
|
|
self.assertEqual(1, cases_left.count()) |
126
|
|
|
self.assertEqual(self.testcase_2.pk, cases_left[0].case.pk) |
127
|
|
|
|
128
|
|
|
def test_add_cases_sortkey_autoincrement(self): |
129
|
|
|
""" |
130
|
|
|
When you add new cases, each new case should get a sortkey of the |
131
|
|
|
highest sortkey in the database + 10. |
132
|
|
|
|
133
|
|
|
The first case should get sortkey 0. The offset between the sortkeys is |
134
|
|
|
to leave space to insert cases in between without having to update all |
135
|
|
|
cases. |
136
|
|
|
""" |
137
|
|
|
|
138
|
|
|
plan = TestPlanFactory() |
139
|
|
|
|
140
|
|
|
for sequence_no in range(3): |
141
|
|
|
case_plan = plan.add_case(TestCaseFactory()) |
142
|
|
|
self.assertEqual(sequence_no * 10, case_plan.sortkey) |
143
|
|
|
|
144
|
|
|
# Check if you can still specify a sortkey manually to insert a case in |
145
|
|
|
# between the other cases. |
146
|
|
|
case_plan = plan.add_case(TestCaseFactory(), sortkey=15) |
147
|
|
|
self.assertEqual(15, case_plan.sortkey) |
148
|
|
|
|
149
|
|
|
def test_get_full_url(self): |
150
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
151
|
|
|
username=self.plan_tester.username, password="password" |
152
|
|
|
) |
153
|
|
|
test_plan_url = self.plan_1.get_full_url() |
154
|
|
|
response = self.client.get(test_plan_url, follow=True) |
155
|
|
|
|
156
|
|
|
self.assertIsNotNone(test_plan_url) |
157
|
|
|
self.assertNotEqual(test_plan_url[-1], "/") |
158
|
|
|
self.assertContains(response, self.plan_1.name) |
159
|
|
|
|
160
|
|
|
|
161
|
|
|
class TestCloneView(BasePlanCase): |
162
|
|
|
"""Test case for cloning a plan""" |
163
|
|
|
|
164
|
|
|
@classmethod |
165
|
|
|
def setUpTestData(cls): |
166
|
|
|
super().setUpTestData() |
167
|
|
|
|
168
|
|
|
cls.another_plan = TestPlanFactory( |
169
|
|
|
name="Another plan for test", |
170
|
|
|
author=cls.tester, |
171
|
|
|
product=cls.product, |
172
|
|
|
product_version=cls.version, |
173
|
|
|
) |
174
|
|
|
cls.another_case_1 = TestCaseFactory( |
175
|
|
|
author=cls.tester, |
176
|
|
|
default_tester=None, |
177
|
|
|
reviewer=cls.tester, |
178
|
|
|
plan=[cls.another_plan], |
179
|
|
|
) |
180
|
|
|
cls.another_case_2 = TestCaseFactory( |
181
|
|
|
author=cls.tester, |
182
|
|
|
default_tester=None, |
183
|
|
|
reviewer=cls.tester, |
184
|
|
|
plan=[cls.another_plan], |
185
|
|
|
) |
186
|
|
|
|
187
|
|
|
cls.third_plan = TestPlanFactory( |
188
|
|
|
name="Third plan for test", |
189
|
|
|
author=cls.tester, |
190
|
|
|
product=cls.product, |
191
|
|
|
product_version=cls.version, |
192
|
|
|
) |
193
|
|
|
cls.third_case_1 = TestCaseFactory( |
194
|
|
|
author=cls.tester, |
195
|
|
|
default_tester=None, |
196
|
|
|
reviewer=cls.tester, |
197
|
|
|
plan=[cls.third_plan], |
198
|
|
|
) |
199
|
|
|
cls.third_case_2 = TestCaseFactory( |
200
|
|
|
author=cls.tester, |
201
|
|
|
default_tester=None, |
202
|
|
|
reviewer=cls.tester, |
203
|
|
|
plan=[cls.third_plan], |
204
|
|
|
) |
205
|
|
|
|
206
|
|
|
cls.totally_new_plan = TestPlanFactory( |
207
|
|
|
name="Test clone plan with copying cases", |
208
|
|
|
author=cls.tester, |
209
|
|
|
product=cls.product, |
210
|
|
|
product_version=cls.version, |
211
|
|
|
) |
212
|
|
|
cls.case_maintain_original_author = TestCaseFactory( |
213
|
|
|
author=cls.tester, |
214
|
|
|
default_tester=None, |
215
|
|
|
reviewer=cls.tester, |
216
|
|
|
plan=[cls.totally_new_plan], |
217
|
|
|
) |
218
|
|
|
cls.case_keep_default_tester = TestCaseFactory( |
219
|
|
|
author=cls.tester, |
220
|
|
|
default_tester=None, |
221
|
|
|
reviewer=cls.tester, |
222
|
|
|
plan=[cls.totally_new_plan], |
223
|
|
|
) |
224
|
|
|
|
225
|
|
|
cls.plan_tester = UserFactory() |
226
|
|
|
cls.plan_tester.set_password("password") |
227
|
|
|
cls.plan_tester.save() |
228
|
|
|
user_should_have_perm(cls.plan_tester, "testplans.add_testplan") |
229
|
|
|
user_should_have_perm(cls.plan_tester, "testplans.view_testplan") |
230
|
|
|
|
231
|
|
|
def test_open_clone_page_to_clone_one_plan(self): |
232
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
233
|
|
|
username=self.plan_tester.username, password="password" |
234
|
|
|
) |
235
|
|
|
|
236
|
|
|
response = self.client.get(reverse("plans-clone", args=[self.plan.pk])) |
237
|
|
|
|
238
|
|
|
_name = _("Name") |
239
|
|
|
self.assertContains( |
240
|
|
|
response, |
241
|
|
|
f'<label class="col-md-1 col-lg-1" for="id_name">{_name}</label>', |
242
|
|
|
html=True, |
243
|
|
|
) |
244
|
|
|
|
245
|
|
|
self.assertContains( |
246
|
|
|
response, |
247
|
|
|
f'<input type="text" id="id_name" name="name" value="{self.plan.make_cloned_name()}"' |
248
|
|
|
' class="form-control" required>', |
249
|
|
|
html=True, |
250
|
|
|
) |
251
|
|
|
|
252
|
|
|
def verify_cloned_plan(self, original_plan, cloned_plan, copy_cases=None): |
253
|
|
|
self.assertEqual(f"Copy of {original_plan.name}", cloned_plan.name) |
254
|
|
|
self.assertEqual(cloned_plan.text, original_plan.text) |
255
|
|
|
self.assertEqual(Product.objects.get(pk=self.product.pk), cloned_plan.product) |
256
|
|
|
self.assertEqual( |
257
|
|
|
Version.objects.get(pk=self.version.pk), cloned_plan.product_version |
258
|
|
|
) |
259
|
|
|
|
260
|
|
|
self._verify_options(original_plan, cloned_plan, copy_cases) |
261
|
|
|
|
262
|
|
|
def _verify_options(self, original_plan, cloned_plan, copy_cases): |
263
|
|
|
# number of TCs should always be the same |
264
|
|
|
self.assertEqual(cloned_plan.cases.count(), original_plan.cases.count()) |
265
|
|
|
|
266
|
|
|
# Verify option set_parent |
267
|
|
|
self.assertEqual(TestPlan.objects.get(pk=original_plan.pk), cloned_plan.parent) |
268
|
|
|
|
269
|
|
|
# Verify option copy_testcases |
270
|
|
|
for case in cloned_plan.cases.all(): |
271
|
|
|
is_case_linked = TestCasePlan.objects.filter( |
272
|
|
|
plan=original_plan, case=case |
273
|
|
|
).exists() |
274
|
|
|
|
275
|
|
|
if copy_cases: |
276
|
|
|
# Ensure cases of original plan are not linked to cloned plan |
277
|
|
|
self.assertFalse(is_case_linked) |
278
|
|
|
|
279
|
|
|
# verify author was updated |
280
|
|
|
self.assertEqual(self.plan_tester, case.author) |
281
|
|
|
else: |
282
|
|
|
self.assertTrue(is_case_linked) |
283
|
|
|
|
284
|
|
|
cases_from_original_plan = original_plan.cases.all().annotate( |
285
|
|
|
sortkey=F("testcaseplan__sortkey") |
286
|
|
|
) |
287
|
|
|
cases_from_cloned_plan = cloned_plan.cases.all().annotate( |
288
|
|
|
sortkey=F("testcaseplan__sortkey") |
289
|
|
|
) |
290
|
|
|
for original_case, copied_case in zip( |
291
|
|
|
cases_from_original_plan, cases_from_cloned_plan |
292
|
|
|
): |
293
|
|
|
# default tester is always kept |
294
|
|
|
self.assertEqual( |
295
|
|
|
original_case.default_tester, copied_case.default_tester |
296
|
|
|
) |
297
|
|
|
self.assertEqual(original_case.sortkey, copied_case.sortkey) |
298
|
|
|
|
299
|
|
|
if not copy_cases: |
300
|
|
|
# when linking TCs author doesn't change |
301
|
|
|
self.assertEqual(original_case.author, copied_case.author) |
302
|
|
|
|
303
|
|
|
def test_clone_a_plan_with_default_options(self): |
304
|
|
|
post_data = { |
305
|
|
|
"name": self.third_plan.make_cloned_name(), |
306
|
|
|
"product": self.product.pk, |
307
|
|
|
"version": self.version.pk, |
308
|
|
|
"set_parent": "on", |
309
|
|
|
"submit": "Clone", |
310
|
|
|
} |
311
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
312
|
|
|
username=self.plan_tester.username, password="password" |
313
|
|
|
) |
314
|
|
|
response = self.client.post( |
315
|
|
|
reverse("plans-clone", args=[self.third_plan.pk]), post_data |
316
|
|
|
) |
317
|
|
|
|
318
|
|
|
cloned_plan = TestPlan.objects.get(name=self.third_plan.make_cloned_name()) |
319
|
|
|
|
320
|
|
|
self.assertRedirects( |
321
|
|
|
response, |
322
|
|
|
reverse("test_plan_url_short", args=[cloned_plan.pk]), |
323
|
|
|
target_status_code=HTTPStatus.MOVED_PERMANENTLY, |
324
|
|
|
) |
325
|
|
|
|
326
|
|
|
self.verify_cloned_plan(self.third_plan, cloned_plan) |
327
|
|
|
|
328
|
|
|
def test_clone_a_plan_by_copying_cases(self): |
329
|
|
|
post_data = { |
330
|
|
|
"name": self.totally_new_plan.make_cloned_name(), |
331
|
|
|
"product": self.product.pk, |
332
|
|
|
"version": self.version.pk, |
333
|
|
|
"set_parent": "on", |
334
|
|
|
"submit": "Clone", |
335
|
|
|
"copy_testcases": "on", |
336
|
|
|
} |
337
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
338
|
|
|
username=self.plan_tester.username, password="password" |
339
|
|
|
) |
340
|
|
|
self.client.post( |
341
|
|
|
reverse("plans-clone", args=[self.totally_new_plan.pk]), post_data |
342
|
|
|
) |
343
|
|
|
cloned_plan = TestPlan.objects.get( |
344
|
|
|
name=self.totally_new_plan.make_cloned_name() |
345
|
|
|
) |
346
|
|
|
self.verify_cloned_plan(self.totally_new_plan, cloned_plan, copy_cases=True) |
347
|
|
|
|