1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# pylint: disable=invalid-name, too-many-ancestors |
3
|
|
|
|
4
|
|
|
import unittest |
5
|
|
|
from http import HTTPStatus |
6
|
|
|
from urllib.parse import urlencode |
7
|
|
|
|
8
|
|
|
from django.urls import reverse |
9
|
|
|
from django.forms import ValidationError |
10
|
|
|
from django.test import RequestFactory |
11
|
|
|
|
12
|
|
|
from tcms.testcases.fields import MultipleEmailField |
13
|
|
|
from tcms.management.models import Priority, Tag |
14
|
|
|
from tcms.testcases.models import TestCase |
15
|
|
|
from tcms.testcases.views import get_selected_testcases |
16
|
|
|
from tcms.testruns.models import TestCaseRunStatus |
17
|
|
|
from tcms.tests.factories import BugFactory |
18
|
|
|
from tcms.tests.factories import TestCaseFactory |
19
|
|
|
from tcms.tests import BasePlanCase, BaseCaseRun |
20
|
|
|
from tcms.tests import user_should_have_perm |
21
|
|
|
from tcms.utils.permissions import initiate_user_with_default_setups |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
class TestGetCaseRunDetailsAsDefaultUser(BaseCaseRun): |
25
|
|
|
"""Assert what a default user (non-admin) will see""" |
26
|
|
|
|
27
|
|
|
@classmethod |
28
|
|
|
def setUpTestData(cls): |
29
|
|
|
super(TestGetCaseRunDetailsAsDefaultUser, cls).setUpTestData() |
30
|
|
|
|
31
|
|
|
def test_user_in_default_group_sees_comments(self): |
32
|
|
|
# test for https://github.com/kiwitcms/Kiwi/issues/74 |
33
|
|
|
initiate_user_with_default_setups(self.tester) |
34
|
|
|
|
35
|
|
|
url = reverse('caserun-detail-pane', args=[self.case_run_1.case_id]) |
36
|
|
|
response = self.client.get( |
37
|
|
|
url, |
38
|
|
|
{ |
39
|
|
|
'case_run_id': self.case_run_1.pk, |
40
|
|
|
'case_text_version': self.case_run_1.case.latest_text_version() |
41
|
|
|
} |
42
|
|
|
) |
43
|
|
|
|
44
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
45
|
|
|
|
46
|
|
|
self.assertContains( |
47
|
|
|
response, |
48
|
|
|
'<textarea name="comment" cols="40" id="id_comment" maxlength="10000" ' |
49
|
|
|
'rows="10">\n</textarea>', |
50
|
|
|
html=True) |
51
|
|
|
|
52
|
|
|
for status in TestCaseRunStatus.objects.all(): |
53
|
|
|
self.assertContains( |
54
|
|
|
response, |
55
|
|
|
"<input type=\"submit\" class=\"btn btn_%s btn_status js-status-button\" " |
56
|
|
|
"title=\"%s\"" % (status.name.lower(), status.name), |
57
|
|
|
html=False |
58
|
|
|
) |
59
|
|
|
|
60
|
|
|
def test_user_sees_bugs(self): |
61
|
|
|
bug_1 = BugFactory() |
62
|
|
|
bug_2 = BugFactory() |
63
|
|
|
|
64
|
|
|
self.case_run_1.add_bug(bug_1.bug_id, bug_1.bug_system.pk) |
65
|
|
|
self.case_run_1.add_bug(bug_2.bug_id, bug_2.bug_system.pk) |
66
|
|
|
|
67
|
|
|
url = reverse('caserun-detail-pane', args=[self.case_run_1.case.pk]) |
68
|
|
|
response = self.client.get( |
69
|
|
|
url, |
70
|
|
|
{ |
71
|
|
|
'case_run_id': self.case_run_1.pk, |
72
|
|
|
'case_text_version': self.case_run_1.case.latest_text_version() |
73
|
|
|
} |
74
|
|
|
) |
75
|
|
|
|
76
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
77
|
|
|
self.assertContains(response, bug_1.get_full_url()) |
78
|
|
|
self.assertContains(response, bug_2.get_full_url()) |
79
|
|
|
|
80
|
|
|
|
81
|
|
|
class TestMultipleEmailField(unittest.TestCase): |
82
|
|
|
|
83
|
|
|
@classmethod |
84
|
|
|
def setUpClass(cls): |
85
|
|
|
super().setUpClass() |
86
|
|
|
cls.field = MultipleEmailField() |
87
|
|
|
|
88
|
|
|
def test_to_python(self): |
89
|
|
|
value = u'zhangsan@localhost' |
90
|
|
|
pyobj = self.field.to_python(value) |
91
|
|
|
self.assertEqual(pyobj, [value]) |
92
|
|
|
|
93
|
|
|
value = u'zhangsan@localhost,,[email protected],' |
94
|
|
|
pyobj = self.field.to_python(value) |
95
|
|
|
self.assertEqual(pyobj, [u'zhangsan@localhost', u'[email protected]']) |
96
|
|
|
|
97
|
|
|
for value in ('', None, []): |
98
|
|
|
pyobj = self.field.to_python(value) |
99
|
|
|
self.assertEqual(pyobj, []) |
100
|
|
|
|
101
|
|
|
def test_clean(self): |
102
|
|
|
value = u'zhangsan@localhost' |
103
|
|
|
data = self.field.clean(value) |
104
|
|
|
self.assertEqual(data, [value]) |
105
|
|
|
|
106
|
|
|
value = u'zhangsan@localhost,[email protected]' |
107
|
|
|
data = self.field.clean(value) |
108
|
|
|
self.assertEqual(data, [u'zhangsan@localhost', u'[email protected]']) |
109
|
|
|
|
110
|
|
|
value = u',zhangsan@localhost, ,[email protected], \n' |
111
|
|
|
data = self.field.clean(value) |
112
|
|
|
self.assertEqual(data, [u'zhangsan@localhost', '[email protected]']) |
113
|
|
|
|
114
|
|
|
value = ',zhangsan,zhangsan@localhost, \n,[email protected], ' |
115
|
|
|
self.assertRaises(ValidationError, self.field.clean, value) |
116
|
|
|
|
117
|
|
|
value = '' |
118
|
|
|
self.field.required = True |
119
|
|
|
self.assertRaises(ValidationError, self.field.clean, value) |
120
|
|
|
|
121
|
|
|
value = '' |
122
|
|
|
self.field.required = False |
123
|
|
|
data = self.field.clean(value) |
124
|
|
|
self.assertEqual(data, []) |
125
|
|
|
|
126
|
|
|
|
127
|
|
|
class TestEditCase(BasePlanCase): |
128
|
|
|
"""Test edit view method""" |
129
|
|
|
|
130
|
|
|
@classmethod |
131
|
|
|
def setUpTestData(cls): |
132
|
|
|
super(TestEditCase, cls).setUpTestData() |
133
|
|
|
|
134
|
|
|
cls.proposed_case = TestCaseFactory( |
135
|
|
|
author=cls.tester, |
136
|
|
|
default_tester=None, |
137
|
|
|
reviewer=cls.tester, |
138
|
|
|
case_status=cls.case_status_proposed, |
139
|
|
|
plan=[cls.plan]) |
140
|
|
|
|
141
|
|
|
# test data for https://github.com/kiwitcms/Kiwi/issues/334 |
142
|
|
|
# pylint: disable=objects-update-used |
143
|
|
|
Priority.objects.filter(value='P4').update(is_active=False) |
144
|
|
|
|
145
|
|
|
user_should_have_perm(cls.tester, 'testcases.change_testcase') |
146
|
|
|
cls.case_edit_url = reverse('testcases-edit', args=[cls.case_1.pk]) |
147
|
|
|
|
148
|
|
|
# Copy, then modify or add new data for specific tests below |
149
|
|
|
cls.edit_data = { |
150
|
|
|
'from_plan': cls.plan.pk, |
151
|
|
|
'summary': cls.case_1.summary, |
152
|
|
|
'product': cls.case_1.category.product.pk, |
153
|
|
|
'category': cls.case_1.category.pk, |
154
|
|
|
'default_tester': '', |
155
|
|
|
'case_status': cls.case_status_confirmed.pk, |
156
|
|
|
'arguments': '', |
157
|
|
|
'extra_link': '', |
158
|
|
|
'notes': '', |
159
|
|
|
'is_automated': '0', |
160
|
|
|
'requirement': '', |
161
|
|
|
'script': '', |
162
|
|
|
'alias': '', |
163
|
|
|
'priority': cls.case_1.priority.pk, |
164
|
|
|
'tag': 'RHEL', |
165
|
|
|
'setup': '', |
166
|
|
|
'action': '', |
167
|
|
|
'breakdown': '', |
168
|
|
|
'effect': '', |
169
|
|
|
'cc_list': '', |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
def test_404_if_case_id_not_exist(self): |
173
|
|
|
url = reverse('testcases-edit', args=[99999]) |
174
|
|
|
response = self.client.get(url) |
175
|
|
|
self.assert404(response) |
176
|
|
|
|
177
|
|
|
def test_404_if_from_plan_not_exist(self): |
178
|
|
|
response = self.client.get(self.case_edit_url, {'from_plan': 9999}) |
179
|
|
|
self.assert404(response) |
180
|
|
|
|
181
|
|
|
def test_show_edit_page(self): |
182
|
|
|
response = self.client.get(self.case_edit_url) |
183
|
|
|
self.assertEqual(200, response.status_code) |
184
|
|
|
self.assertNotContains(response, ">P4</option") |
185
|
|
|
|
186
|
|
|
def test_edit_a_case(self): |
187
|
|
|
edit_data = self.edit_data.copy() |
188
|
|
|
new_summary = 'Edited: {0}'.format(self.case_1.summary) |
189
|
|
|
edit_data['summary'] = new_summary |
190
|
|
|
|
191
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
192
|
|
|
|
193
|
|
|
redirect_url = '{0}?from_plan={1}'.format( |
194
|
|
|
reverse('testcases-get', args=[self.case_1.pk]), |
195
|
|
|
self.plan.pk, |
196
|
|
|
) |
197
|
|
|
self.assertRedirects(response, redirect_url) |
198
|
|
|
|
199
|
|
|
edited_case = TestCase.objects.get(pk=self.case_1.pk) |
200
|
|
|
self.assertEqual(new_summary, edited_case.summary) |
201
|
|
|
|
202
|
|
|
def test_continue_edit_this_case_after_save(self): |
203
|
|
|
edit_data = self.edit_data.copy() |
204
|
|
|
edit_data['_continue'] = 'continue edit' |
205
|
|
|
|
206
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
207
|
|
|
|
208
|
|
|
redirect_url = '{0}?from_plan={1}'.format( |
209
|
|
|
reverse('testcases-edit', args=[self.case_1.pk]), |
210
|
|
|
self.plan.pk, |
211
|
|
|
) |
212
|
|
|
self.assertRedirects(response, redirect_url) |
213
|
|
|
|
214
|
|
|
def test_continue_edit_next_confirmed_case_after_save(self): |
215
|
|
|
edit_data = self.edit_data.copy() |
216
|
|
|
edit_data['_continuenext'] = 'continue edit next case' |
217
|
|
|
|
218
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
219
|
|
|
|
220
|
|
|
redirect_url = '{0}?from_plan={1}'.format( |
221
|
|
|
reverse('testcases-edit', args=[self.case_2.pk]), |
222
|
|
|
self.plan.pk, |
223
|
|
|
) |
224
|
|
|
self.assertRedirects(response, redirect_url) |
225
|
|
|
|
226
|
|
|
def test_continue_edit_next_non_confirmed_case_after_save(self): |
227
|
|
|
edit_data = self.edit_data.copy() |
228
|
|
|
edit_data['case_status'] = self.case_status_proposed.pk |
229
|
|
|
edit_data['_continuenext'] = 'continue edit next case' |
230
|
|
|
|
231
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
232
|
|
|
|
233
|
|
|
redirect_url = '{0}?from_plan={1}'.format( |
234
|
|
|
reverse('testcases-edit', args=[self.proposed_case.pk]), |
235
|
|
|
self.plan.pk, |
236
|
|
|
) |
237
|
|
|
self.assertRedirects(response, redirect_url) |
238
|
|
|
|
239
|
|
|
def test_return_to_plan_confirmed_cases_tab(self): |
240
|
|
|
edit_data = self.edit_data.copy() |
241
|
|
|
edit_data['_returntoplan'] = 'return to plan' |
242
|
|
|
|
243
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
244
|
|
|
|
245
|
|
|
redirect_url = '{0}#testcases'.format( |
246
|
|
|
reverse('test_plan_url_short', args=[self.plan.pk]) |
247
|
|
|
) |
248
|
|
|
self.assertRedirects(response, redirect_url, target_status_code=301) |
249
|
|
|
|
250
|
|
|
def test_return_to_plan_review_cases_tab(self): |
251
|
|
|
edit_data = self.edit_data.copy() |
252
|
|
|
edit_data['case_status'] = self.case_status_proposed.pk |
253
|
|
|
edit_data['_returntoplan'] = 'return to plan' |
254
|
|
|
|
255
|
|
|
response = self.client.post(self.case_edit_url, edit_data) |
256
|
|
|
|
257
|
|
|
redirect_url = '{0}#reviewcases'.format( |
258
|
|
|
reverse('test_plan_url_short', args=[self.plan.pk]) |
259
|
|
|
) |
260
|
|
|
self.assertRedirects(response, redirect_url, target_status_code=301) |
261
|
|
|
|
262
|
|
|
|
263
|
|
|
class TestPrintablePage(BasePlanCase): |
264
|
|
|
"""Test printable page view method""" |
265
|
|
|
|
266
|
|
|
@classmethod |
267
|
|
|
def setUpTestData(cls): |
268
|
|
|
super(TestPrintablePage, cls).setUpTestData() |
269
|
|
|
cls.printable_url = reverse('testcases-printable') |
270
|
|
|
|
271
|
|
|
cls.case_1.add_text(action='action', |
272
|
|
|
effect='effect', |
273
|
|
|
setup='setup', |
274
|
|
|
breakdown='breakdown') |
275
|
|
|
cls.case_2.add_text(action='action', |
276
|
|
|
effect='effect', |
277
|
|
|
setup='setup', |
278
|
|
|
breakdown='breakdown') |
279
|
|
|
|
280
|
|
|
def test_printable_page(self): |
281
|
|
|
# printing only 1 of the cases |
282
|
|
|
response = self.client.post(self.printable_url, |
283
|
|
|
{'case': [self.case_1.pk]}) |
284
|
|
|
|
285
|
|
|
# not printing the Test Plan header section |
286
|
|
|
self.assertNotContains(response, 'Test Plan Document') |
287
|
|
|
|
288
|
|
|
# response contains the first TestCase |
289
|
|
|
self.assertContains( |
290
|
|
|
response, |
291
|
|
|
'<h3>TC-{0}: {1}</h3>'.format(self.case_1.pk, self.case_1.summary), |
292
|
|
|
html=True |
293
|
|
|
) |
294
|
|
|
|
295
|
|
|
# but not the second TestCase b/c it was not selected |
296
|
|
|
self.assertNotContains( |
297
|
|
|
response, |
298
|
|
|
'<h3>TC-{0}: {1}'.format(self.case_2.pk, self.case_2.summary), |
299
|
|
|
html=True |
300
|
|
|
) |
301
|
|
|
|
302
|
|
|
|
303
|
|
|
class TestCloneCase(BasePlanCase): |
304
|
|
|
"""Test clone view method""" |
305
|
|
|
|
306
|
|
|
@classmethod |
307
|
|
|
def setUpTestData(cls): |
308
|
|
|
super(TestCloneCase, cls).setUpTestData() |
309
|
|
|
|
310
|
|
|
user_should_have_perm(cls.tester, 'testcases.add_testcase') |
311
|
|
|
cls.clone_url = reverse('testcases-clone') |
312
|
|
|
|
313
|
|
|
def test_refuse_if_missing_argument(self): |
314
|
|
|
# Refuse to clone cases if missing selectAll and case arguments |
315
|
|
|
response = self.client.get(self.clone_url, {}, follow=True) |
316
|
|
|
|
317
|
|
|
self.assertContains(response, 'At least one TestCase is required') |
318
|
|
|
|
319
|
|
|
def test_show_clone_page_with_from_plan(self): |
320
|
|
|
response = self.client.get(self.clone_url, |
321
|
|
|
{'from_plan': self.plan.pk, |
322
|
|
|
'case': [self.case_1.pk, self.case_2.pk]}) |
323
|
|
|
|
324
|
|
|
self.assertContains( |
325
|
|
|
response, |
326
|
|
|
"""<div> |
327
|
|
|
<input type="radio" id="id_use_sameplan" name="selectplan" value="{0}"> |
328
|
|
|
<label for="id_use_sameplan" class="strong">Use the same Plan -- {0} : {1}</label> |
329
|
|
|
</div>""".format(self.plan.pk, self.plan.name), |
330
|
|
|
html=True) |
331
|
|
|
|
332
|
|
|
for loop_counter, case in enumerate([self.case_1, self.case_2]): |
333
|
|
|
self.assertContains( |
334
|
|
|
response, |
335
|
|
|
'<label for="id_case_{0}">' |
336
|
|
|
'<input checked="checked" id="id_case_{0}" name="case" ' |
337
|
|
|
'type="checkbox" value="{1}"> {2}</label>'.format( |
338
|
|
|
loop_counter, case.pk, case.summary), |
339
|
|
|
html=True) |
340
|
|
|
|
341
|
|
|
def test_show_clone_page_without_from_plan(self): |
342
|
|
|
response = self.client.get(self.clone_url, {'case': self.case_1.pk}) |
343
|
|
|
|
344
|
|
|
self.assertNotContains( |
345
|
|
|
response, |
346
|
|
|
'Use the same Plan -- {0} : {1}'.format(self.plan.pk, |
347
|
|
|
self.plan.name), |
348
|
|
|
) |
349
|
|
|
|
350
|
|
|
self.assertContains( |
351
|
|
|
response, |
352
|
|
|
'<label for="id_case_0">' |
353
|
|
|
'<input checked="checked" id="id_case_0" name="case" ' |
354
|
|
|
'type="checkbox" value="{0}"> {1}</label>'.format( |
355
|
|
|
self.case_1.pk, self.case_1.summary), |
356
|
|
|
html=True) |
357
|
|
|
|
358
|
|
|
|
359
|
|
|
class TestSearchCases(BasePlanCase): |
360
|
|
|
"""Test search view method""" |
361
|
|
|
|
362
|
|
|
@classmethod |
363
|
|
|
def setUpTestData(cls): |
364
|
|
|
super().setUpTestData() |
365
|
|
|
|
366
|
|
|
cls.search_url = reverse('testcases-search') |
367
|
|
|
|
368
|
|
|
def test_page_renders(self): |
369
|
|
|
response = self.client.get(self.search_url, {}) |
370
|
|
|
self.assertContains(response, '<option value="">----------</option>', html=True) |
371
|
|
|
|
372
|
|
|
|
373
|
|
|
class TestGetCasesFromPlan(BasePlanCase): |
374
|
|
|
@classmethod |
375
|
|
|
def setUpTestData(cls): |
376
|
|
|
super().setUpTestData() |
377
|
|
|
initiate_user_with_default_setups(cls.tester) |
378
|
|
|
|
379
|
|
|
def test_casetags_are_shown_in_template(self): |
380
|
|
|
# pylint: disable=tag-objects-get_or_create |
381
|
|
|
tag, _ = Tag.objects.get_or_create(name='Linux') |
382
|
|
|
self.case.add_tag(tag) |
383
|
|
|
|
384
|
|
|
url = reverse('testcases-all') |
385
|
|
|
response_data = urlencode({ |
386
|
|
|
'from_plan': self.plan.pk, |
387
|
|
|
'template_type': 'case', |
388
|
|
|
'a': 'initial'}) |
389
|
|
|
# note: this is how the UI sends the request |
390
|
|
|
response = self.client.post(url, data=response_data, |
391
|
|
|
content_type='application/x-www-form-urlencoded; charset=UTF-8', |
392
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest') |
393
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
394
|
|
|
self.assertContains(response, 'Tags:') |
395
|
|
|
self.assertContains(response, '<a href="#testcases">Linux</a>') |
396
|
|
|
|
397
|
|
|
def test_disabled_priority_now_shown(self): |
398
|
|
|
# test data for https://github.com/kiwitcms/Kiwi/issues/334 |
399
|
|
|
# pylint: disable=objects-update-used |
400
|
|
|
Priority.objects.filter(value='P4').update(is_active=False) |
401
|
|
|
|
402
|
|
|
url = reverse('testcases-all') |
403
|
|
|
response_data = urlencode({ |
404
|
|
|
'from_plan': self.plan.pk, |
405
|
|
|
'template_type': 'case', |
406
|
|
|
'a': 'initial'}) |
407
|
|
|
# note: this is how the UI sends the request |
408
|
|
|
response = self.client.post(url, data=response_data, |
409
|
|
|
content_type='application/x-www-form-urlencoded; charset=UTF-8', |
410
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest') |
411
|
|
|
self.assertEqual(HTTPStatus.OK, response.status_code) |
412
|
|
|
self.assertContains(response, 'Set P3') |
413
|
|
|
self.assertNotContains(response, 'Set P4') |
414
|
|
|
|
415
|
|
|
|
416
|
|
|
class TestGetSelectedTestcases(BasePlanCase): |
417
|
|
|
def test_get_selected_testcases_works_with_both_string_and_int_pks(self): |
418
|
|
|
""" |
419
|
|
|
Assures that tcms.testcases.views.get_selected_testcases |
420
|
|
|
returns the same results, regardless of whether the |
421
|
|
|
passed request contains the case pks as strings or |
422
|
|
|
integers, as long as they are the same in both occasions. |
423
|
|
|
""" |
424
|
|
|
|
425
|
|
|
case_int_pks = [self.case.pk, self.case_1.pk, self.case_2.pk, self.case_3.pk] |
426
|
|
|
case_str_pks = [] |
427
|
|
|
|
428
|
|
|
for _pk in case_int_pks: |
429
|
|
|
case_str_pks.append(str(_pk)) |
430
|
|
|
|
431
|
|
|
int_pk_query = get_selected_testcases( |
432
|
|
|
RequestFactory().post( |
433
|
|
|
reverse('testcases-clone'), |
434
|
|
|
{'case': case_int_pks} |
435
|
|
|
) |
436
|
|
|
) |
437
|
|
|
|
438
|
|
|
str_pk_query = get_selected_testcases( |
439
|
|
|
RequestFactory().post( |
440
|
|
|
reverse('testcases-clone'), |
441
|
|
|
{'case': case_str_pks} |
442
|
|
|
) |
443
|
|
|
) |
444
|
|
|
|
445
|
|
|
for case in TestCase.objects.filter(pk__in=case_int_pks): |
446
|
|
|
self.assertTrue(case in int_pk_query) |
447
|
|
|
self.assertTrue(case in str_pk_query) |
448
|
|
|
|