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