1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# pylint: disable=invalid-name, too-many-ancestors |
3
|
|
|
|
4
|
|
|
from http import HTTPStatus |
5
|
|
|
|
6
|
|
|
from django.contrib.auth.models import Permission |
7
|
|
|
from django.urls import reverse |
8
|
|
|
from django.utils.translation import gettext_lazy as _ |
9
|
|
|
|
10
|
|
|
from tcms.testruns.models import TestExecutionStatus, TestRun |
11
|
|
|
from tcms.tests import ( |
12
|
|
|
BaseCaseRun, |
13
|
|
|
BasePlanCase, |
14
|
|
|
PermissionsTestCase, |
15
|
|
|
remove_perm_from_user, |
16
|
|
|
user_should_have_perm, |
17
|
|
|
) |
18
|
|
|
from tcms.tests.factories import ( |
19
|
|
|
BuildFactory, |
20
|
|
|
TagFactory, |
21
|
|
|
TestExecutionFactory, |
22
|
|
|
TestRunFactory, |
23
|
|
|
UserFactory, |
24
|
|
|
) |
25
|
|
|
from tcms.utils.permissions import initiate_user_with_default_setups |
26
|
|
|
|
27
|
|
|
|
28
|
|
|
class TestGetRun(BaseCaseRun): |
29
|
|
|
"""Test get view method""" |
30
|
|
|
|
31
|
|
|
@classmethod |
32
|
|
|
def setUpTestData(cls): |
33
|
|
|
super().setUpTestData() |
34
|
|
|
initiate_user_with_default_setups(cls.tester) |
35
|
|
|
|
36
|
|
|
for _i in range(3): |
37
|
|
|
cls.test_run.add_tag(TagFactory()) |
38
|
|
|
|
39
|
|
|
cls.unauthorized = UserFactory() |
40
|
|
|
cls.unauthorized.set_password("password") |
41
|
|
|
cls.unauthorized.save() |
42
|
|
|
|
43
|
|
|
cls.unauthorized.user_permissions.add(*Permission.objects.all()) |
44
|
|
|
remove_perm_from_user(cls.unauthorized, "testruns.add_testruntag") |
45
|
|
|
remove_perm_from_user(cls.unauthorized, "testruns.delete_testruntag") |
46
|
|
|
|
47
|
|
|
def test_404_if_non_existing_pk(self): |
48
|
|
|
url = reverse("testruns-get", args=[99999999]) |
49
|
|
|
response = self.client.get(url) |
50
|
|
|
self.assertEqual(HTTPStatus.NOT_FOUND, response.status_code) |
51
|
|
|
|
52
|
|
|
def test_get_a_run(self): |
53
|
|
|
url = reverse("testruns-get", args=[self.test_run.pk]) |
54
|
|
|
response = self.client.get(url) |
55
|
|
|
|
56
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
57
|
|
|
_tags = _("Tags") |
58
|
|
|
self.assertContains( |
59
|
|
|
response, |
60
|
|
|
f'<h2 class="card-pf-title"><span class="fa fa-tags"></span>{_tags}</h2>', |
61
|
|
|
html=True, |
62
|
|
|
) |
63
|
|
|
|
64
|
|
|
def test_get_run_without_permissions_to_add_or_remove_tags(self): |
65
|
|
|
self.client.logout() |
66
|
|
|
|
67
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
68
|
|
|
username=self.unauthorized.username, password="password" |
69
|
|
|
) |
70
|
|
|
|
71
|
|
|
url = reverse("testruns-get", args=[self.test_run.pk]) |
72
|
|
|
response = self.client.get(url) |
73
|
|
|
|
74
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
75
|
|
|
self.assertNotContains(response, "Add Tag") |
76
|
|
|
self.assertNotContains(response, "js-remove-tag") |
77
|
|
|
|
78
|
|
|
|
79
|
|
|
class TestCreateNewRun(BasePlanCase): |
80
|
|
|
"""Test creating new run""" |
81
|
|
|
|
82
|
|
|
@classmethod |
83
|
|
|
def setUpTestData(cls): |
84
|
|
|
super().setUpTestData() |
85
|
|
|
|
86
|
|
|
cls.build = BuildFactory(version=cls.plan.product_version) |
87
|
|
|
|
88
|
|
|
user_should_have_perm(cls.tester, "testruns.add_testrun") |
89
|
|
|
user_should_have_perm(cls.tester, "testruns.view_testrun") |
90
|
|
|
cls.url = reverse("testruns-new") |
91
|
|
|
|
92
|
|
|
def test_get_shows_selected_cases(self): |
93
|
|
|
self.client.login( # nosec:B106:hardcoded_password_funcarg |
94
|
|
|
username=self.tester.username, password="password" |
95
|
|
|
) |
96
|
|
|
|
97
|
|
|
response = self.client.get( |
98
|
|
|
self.url, |
99
|
|
|
{"p": self.plan.pk, "c": [self.case_1.pk, self.case_2.pk, self.case_3.pk]}, |
100
|
|
|
) |
101
|
|
|
|
102
|
|
|
# Assert listed cases |
103
|
|
|
for _i, case in enumerate((self.case_1, self.case_2, self.case_3), 1): |
104
|
|
|
case_url = reverse("testcases-get", args=[case.pk]) |
105
|
|
|
self.assertContains( |
106
|
|
|
response, |
107
|
|
|
f'<a href="{case_url}">TC-{case.pk}: {case.summary}</a>', |
108
|
|
|
html=True, |
109
|
|
|
) |
110
|
|
|
|
111
|
|
|
def test_post_creates_new_run(self): |
112
|
|
|
new_run_summary = "TestRun summary" |
113
|
|
|
|
114
|
|
|
post_data = { |
115
|
|
|
"summary": new_run_summary, |
116
|
|
|
"plan": self.plan.pk, |
117
|
|
|
"product_id": self.plan.product_id, |
118
|
|
|
"product": self.plan.product_id, |
119
|
|
|
"build": self.build.pk, |
120
|
|
|
"manager": self.tester.email, |
121
|
|
|
"default_tester": self.tester.email, |
122
|
|
|
"notes": "", |
123
|
|
|
"case": [self.case_1.pk, self.case_2.pk], |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
url = reverse("testruns-new") |
127
|
|
|
response = self.client.post(url, post_data) |
128
|
|
|
|
129
|
|
|
new_run = TestRun.objects.last() |
130
|
|
|
|
131
|
|
|
self.assertRedirects(response, reverse("testruns-get", args=[new_run.pk])) |
132
|
|
|
|
133
|
|
|
for case, execution in zip( |
134
|
|
|
(self.case_1, self.case_2), new_run.executions.order_by("case") |
135
|
|
|
): |
136
|
|
|
self.assertEqual(case, execution.case) |
137
|
|
|
self.assertIsNone(execution.tested_by) |
138
|
|
|
self.assertEqual(self.tester, execution.assignee) |
139
|
|
|
self.assertEqual( |
140
|
|
|
case.history.latest().history_id, execution.case_text_version |
141
|
|
|
) |
142
|
|
|
self.assertEqual(new_run.build, execution.build) |
143
|
|
|
self.assertIsNone(execution.stop_date) |
144
|
|
|
|
145
|
|
|
|
146
|
|
|
class TestCloneRunView(PermissionsTestCase): |
147
|
|
|
permission_label = "testruns.add_testrun" |
148
|
|
|
http_method_names = ["get"] |
149
|
|
|
|
150
|
|
|
@classmethod |
151
|
|
|
def setUpTestData(cls): |
152
|
|
|
cls.test_run = TestRunFactory() |
153
|
|
|
cls.execution_1 = TestExecutionFactory(run=cls.test_run) |
154
|
|
|
cls.execution_2 = TestExecutionFactory(run=cls.test_run) |
155
|
|
|
|
156
|
|
|
cls.url = reverse("testruns-clone", args=[cls.test_run.pk]) |
157
|
|
|
|
158
|
|
|
super().setUpTestData() |
159
|
|
|
|
160
|
|
|
def verify_get_with_permission(self): |
161
|
|
|
response = self.client.get(self.url) |
162
|
|
|
|
163
|
|
|
self.assertContains(response, _("Clone TestRun")) |
164
|
|
|
|
165
|
|
|
_clone_of = _("Clone of ") |
166
|
|
|
self.assertContains( |
167
|
|
|
response, |
168
|
|
|
'<input id="id_summary" class="form-control" name="summary" ' |
169
|
|
|
f'type="text" value="{_clone_of}{self.test_run.summary}" required>', |
170
|
|
|
html=True, |
171
|
|
|
) |
172
|
|
|
|
173
|
|
|
for execution in (self.execution_1, self.execution_2): |
174
|
|
|
case_url = reverse("testcases-get", args=[execution.case.pk]) |
175
|
|
|
|
176
|
|
|
self.assertContains( |
177
|
|
|
response, |
178
|
|
|
f'<a href="{case_url}">TC-{execution.case.pk}: {execution.case.summary}</a>', |
179
|
|
|
html=True, |
180
|
|
|
) |
181
|
|
|
|
182
|
|
|
|
183
|
|
|
class TestSearchRuns(BaseCaseRun): |
184
|
|
|
@classmethod |
185
|
|
|
def setUpTestData(cls): |
186
|
|
|
super(TestSearchRuns, cls).setUpTestData() |
187
|
|
|
|
188
|
|
|
cls.search_runs_url = reverse("testruns-search") |
189
|
|
|
user_should_have_perm(cls.tester, "testruns.view_testrun") |
190
|
|
|
|
191
|
|
|
def test_search_page_is_shown(self): |
192
|
|
|
response = self.client.get(self.search_runs_url) |
193
|
|
|
self.assertContains(response, '<input id="id_summary" type="text"') |
194
|
|
|
|
195
|
|
|
def test_search_page_is_shown_with_get_parameter_used(self): |
196
|
|
|
response = self.client.get(self.search_runs_url, {"product": self.product.pk}) |
197
|
|
|
self.assertContains( |
198
|
|
|
response, |
199
|
|
|
f'<option value="{self.product.pk}" selected>{self.product.name}</option>', |
200
|
|
|
html=True, |
201
|
|
|
) |
202
|
|
|
|
203
|
|
|
|
204
|
|
|
class TestRunCasesMenu(BaseCaseRun): |
205
|
|
|
@classmethod |
206
|
|
|
def setUpTestData(cls): |
207
|
|
|
super().setUpTestData() |
208
|
|
|
user_should_have_perm(cls.tester, "testruns.view_testrun") |
209
|
|
|
|
210
|
|
|
cls.url = reverse("testruns-get", args=[cls.test_run.pk]) |
211
|
|
|
|
212
|
|
|
_update_text_version = _("Update text version") |
213
|
|
|
cls.update_text_version_html = f""" |
214
|
|
|
<a href="#" class="update-case-text-bulk"> |
215
|
|
|
<span class="fa fa-refresh"></span>{_update_text_version} |
216
|
|
|
</a> |
217
|
|
|
""" |
218
|
|
|
|
219
|
|
|
_assignee = _("Assignee") |
220
|
|
|
cls.change_assignee_html = f""" |
221
|
|
|
<a class="change-assignee-bulk" href="#"> |
222
|
|
|
<span class="fa pficon-user"></span> |
223
|
|
|
{_assignee} |
224
|
|
|
</a> |
225
|
|
|
""" |
226
|
|
|
|
227
|
|
|
_delete = _("Delete") |
228
|
|
|
cls.remove_executions_html = f""" |
229
|
|
|
<a class="bg-danger remove-execution-bulk" href="#"> |
230
|
|
|
<span class="fa fa-trash-o"></span> |
231
|
|
|
{_delete} |
232
|
|
|
</a> |
233
|
|
|
""" |
234
|
|
|
|
235
|
|
|
def test_add_cases_to_run_with_permission(self): |
236
|
|
|
user_should_have_perm(self.tester, "testruns.add_testexecution") |
237
|
|
|
response = self.client.get(self.url) |
238
|
|
|
self.assertContains(response, _("Search and add test cases")) |
239
|
|
|
self.assertContains(response, _("Advanced search")) |
240
|
|
|
|
241
|
|
|
def test_add_cases_to_run_without_permission(self): |
242
|
|
|
remove_perm_from_user(self.tester, "testruns.add_testexecution") |
243
|
|
|
response = self.client.get(self.url) |
244
|
|
|
self.assertNotContains(response, _("Search and add test cases")) |
245
|
|
|
self.assertNotContains(response, _("Advanced search")) |
246
|
|
|
|
247
|
|
|
def test_change_assignee_with_permission(self): |
248
|
|
|
user_should_have_perm(self.tester, "testruns.change_testexecution") |
249
|
|
|
response = self.client.get(self.url) |
250
|
|
|
self.assertContains(response, self.change_assignee_html, html=True) |
251
|
|
|
|
252
|
|
|
def test_change_assignee_without_permission(self): |
253
|
|
|
remove_perm_from_user(self.tester, "testruns.change_testexecution") |
254
|
|
|
response = self.client.get(self.url) |
255
|
|
|
self.assertNotContains(response, self.change_assignee_html, html=True) |
256
|
|
|
|
257
|
|
|
def test_update_text_version_with_permission(self): |
258
|
|
|
user_should_have_perm(self.tester, "testruns.change_testexecution") |
259
|
|
|
response = self.client.get(self.url) |
260
|
|
|
self.assertContains(response, self.update_text_version_html, html=True) |
261
|
|
|
|
262
|
|
|
def test_update_text_version_without_permission(self): |
263
|
|
|
remove_perm_from_user(self.tester, "testruns.change_testexecution") |
264
|
|
|
response = self.client.get(self.url) |
265
|
|
|
self.assertNotContains(response, self.update_text_version_html, html=True) |
266
|
|
|
|
267
|
|
|
def test_remove_executions_with_permission(self): |
268
|
|
|
user_should_have_perm(self.tester, "testruns.delete_testexecution") |
269
|
|
|
response = self.client.get(self.url) |
270
|
|
|
self.assertContains(response, self.remove_executions_html, html=True) |
271
|
|
|
|
272
|
|
|
def test_remove_executions_without_permission(self): |
273
|
|
|
remove_perm_from_user(self.tester, "testruns.delete_testexecution") |
274
|
|
|
response = self.client.get(self.url) |
275
|
|
|
self.assertNotContains(response, self.remove_executions_html, html=True) |
276
|
|
|
|
277
|
|
|
|
278
|
|
|
class TestRunStatusMenu(BaseCaseRun): |
279
|
|
|
@classmethod |
280
|
|
|
def setUpTestData(cls): |
281
|
|
|
super().setUpTestData() |
282
|
|
|
cls.url = reverse("testruns-get", args=[cls.test_run.pk]) |
283
|
|
|
user_should_have_perm(cls.tester, "testruns.view_testrun") |
284
|
|
|
cls.status_menu_html = [] |
285
|
|
|
|
286
|
|
|
def test_get_status_options_with_permission(self): |
287
|
|
|
user_should_have_perm(self.tester, "testruns.change_testexecution") |
288
|
|
|
response = self.client.get(self.url) |
289
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
290
|
|
|
|
291
|
|
|
for execution_status in TestExecutionStatus.objects.all(): |
292
|
|
|
self.assertContains( |
293
|
|
|
response, |
294
|
|
|
f'<span class="{execution_status.icon}"></span>{execution_status.name}', |
295
|
|
|
html=True, |
296
|
|
|
) |
297
|
|
|
|
298
|
|
|
def test_get_status_options_without_permission(self): |
299
|
|
|
remove_perm_from_user(self.tester, "testruns.change_testexecution") |
300
|
|
|
response = self.client.get(self.url) |
301
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
302
|
|
|
|
303
|
|
|
for execution_status in TestExecutionStatus.objects.all(): |
304
|
|
|
self.assertNotContains( |
305
|
|
|
response, |
306
|
|
|
f'<span class="{execution_status.icon}"></span>{execution_status.name}', |
307
|
|
|
html=True, |
308
|
|
|
) |
309
|
|
|
|