def create(self, validated_data): # TODO 이슈 생성 시에 태그 같이 생성 tags_data = validated_data.pop('tags') # TODO 로그인된 사용자 세션 입력으로 바꾸기 validated_data['user_id'] = 1 issue = Issue(**validated_data) for tag_data in tags_data: tag = IssueTag.objects.create(**tag_data) issue.tags.add(tag) issue.save() return issue
def test_comment(self, slackmock): issue = Issue() issue.title = self.title_name issue.project = self.project issue.save() self.selenium.get('{}{}'.format( self.live_server_url, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': issue.number }))) f = self.selenium.find_element_by_id("id_text") f.send_keys(self.comment) self.selenium.find_element_by_name("action").click() slackmock().api_call.assert_called_with( "chat.postMessage", channel="channel", attachments=[{ 'fallback': str(self.user) + ' commented on \"' + self.short + '-1 ' + self.title_name + '\".', 'pretext': 'New comment:', 'text': self.comment, 'title': self.short + "-1 " + self.title_name, 'title_link': "http://localhost:8000/project/" + self.short + "/issue/1/", 'author_name': str(self.user), 'author_link': "http://localhost:8000" + self.user.get_absolute_url(), 'author_icon': "http://localhost:8000" + self.user.avatar.url, 'color': 'good', }])
def test_managerfunctions(self): issue1 = Issue(title="issue1", project=self.project, kanbancol=self.column) issue1.save() sprint_current = Sprint(project=self.project, startdate=(timezone.now() - timedelta(days=1))) sprint_current.save() sprint_old = Sprint(project=self.project, startdate=(timezone.now() - timedelta(days=14)), enddate=(timezone.now() - timedelta(days=1))) sprint_old.save() issues_wo_sprint = Issue.objects.without_sprint() self.assertTrue(issue1 in issues_wo_sprint) issue1.sprint = sprint_current issue1.save() issues_cur_sprint = Issue.objects.current_sprint() self.assertTrue(issue1 in issues_cur_sprint) issue1.sprint = sprint_old issue1.save() issues_cur_sprint = Issue.objects.current_sprint() issues_wo_sprint = Issue.objects.without_sprint() self.assertFalse(issue1 in issues_cur_sprint) self.assertFalse(issue1 in issues_wo_sprint)
def test_sprints_and_issues_in_archive_view(self): newsprint = Sprint(project=self.project) newsprint.save() startedsprint = Sprint(project=self.project, startdate=datetime.datetime.now()) startedsprint.save() stoppedsprint = Sprint(project=self.project, startdate=datetime.datetime.now(), enddate=datetime.datetime.now()) stoppedsprint.save() wrongsprint = Sprint(project=self.project, enddate=datetime.datetime.now()) wrongsprint.save() issueinnew = Issue(title="boo", project=self.project, sprint=newsprint, archived=True) issueinnew.save() issueinstarted = Issue(title="coo", project=self.project, sprint=startedsprint, archived=True) issueinstarted.save() issueinstopped = Issue(title="doo", project=self.project, sprint=stoppedsprint, archived=True) issueinstopped.save() issueinwrong = Issue(title="foo", project=self.project, sprint=wrongsprint, archived=True) issueinwrong.save() issueinno = Issue(title="goo", project=self.project, archived=True) issueinno.save() response = self.client.get(reverse('archive:archive', kwargs={'project': self.project.name_short})) self.assertNotIn(issueinnew, response.context['archived_issues_without_sprint']) self.assertNotIn(issueinwrong, response.context['archived_issues_without_sprint']) self.assertNotIn(issueinstopped, response.context['archived_issues_without_sprint']) self.assertNotIn(issueinstarted, response.context['archived_issues_without_sprint']) self.assertIn(issueinno, response.context['archived_issues_without_sprint']) self.assertEqual(response.context['sprints_sorted'].all()[0], wrongsprint) self.assertEqual(response.context['sprints_sorted'].all()[1], stoppedsprint) self.assertEqual(response.context['sprints_sorted'].all()[2], startedsprint) self.assertEqual(response.context['sprints_sorted'].all()[3], newsprint)
def test_board_drag_n_drop(self): def ajax_complete(driver): try: return 0 == driver.execute_script("return jQuery.active") except WebDriverException: pass # create project and issues driver = self.selenium project = Project(name='TestProjekt', name_short='TP', creator=self.user) project.save() project.developer.add(self.user) issue1 = Issue(project=project, title="TestIssue1") issue2 = Issue(project=project, title="TestIssue2") issue1.save() issue2.save() driver.get("{}{}".format(self.live_server_url, reverse('sprint:sprintboard', kwargs={'project': project.name_short} ))) self.assertEqual(issue1.kanbancol.position, 0) self.assertEqual(issue2.kanbancol.position, 0) driver.find_element_by_link_text("Board").click() # move issue1 and issue2 source_element = driver.find_element_by_id('board_issue_1') dest_element = driver.find_element_by_id('sortable1') ActionChains(driver).drag_and_drop(source_element, dest_element).perform() source_element = driver.find_element_by_id('board_issue_2') dest_element = driver.find_element_by_id('sortable2') ActionChains(driver).drag_and_drop(source_element, dest_element).perform() WebDriverWait(driver, 10).until(ajax_complete, "Timeout waiting for page to load" ) # assert kanbancol has changed issue1.refresh_from_db() issue2.refresh_from_db()
class IndexTest(TestCase): """ smoke test the basic pages """ def setUp(self): super(IndexTest , self).setUp() u = User.objects.create_user("test","*****@*****.**","test") self.i = Issue( title="test" , body="test", user=u ) self.i.save() def tearDown(self): super( IndexTest , self ).tearDown() def test_smoke_test(self): """ smoke test loading the basic pages should just work""" urls = [ ] urls.append('/') urls.append(reverse('api_doc')) for url in urls: response = self.client.get(url) self.assertEqual(response.status_code , 200) def test_dashboard_test(self): """ smoke test views """ self.assertEquals( self.client.login(username='******', password='******'), True) urls = [ ] urls.append('/') urls.append(reverse('my_issues', args=["new"])) for url in urls: response = self.client.get(url) self.assertEqual(response.status_code , 200) def test_anonymous_votes(self): url = reverse("vote", args=[self.i.pk] ) response = self.client.post(url, direction=1) self.assertEqual(response.status_code, 302)
def test_number_kanbancolumns_for_case_not_default(self): driver = self.selenium issue = Issue(title="title", kanbancol=KanbanColumn.objects.get(project=self.project, name="Todo"), due_date=str(datetime.date.today()), priority=3, storypoints=2, description="blubber", project=self.project) issue.save() issue.assignee.add(self.user) driver.get("{}{}".format( self.live_server_url, reverse('issue:create', kwargs={'project': self.project2.name_short}))) driver.find_element_by_id("id_title").send_keys("title") # assert that 2nd project has one kanban col more self.assertEqual( len(Select(driver.find_element_by_id("id_kanbancol")).options), 5) # assert that dependsOn now has one entry driver.get('{}{}'.format( self.live_server_url, reverse('backlog:backlog', kwargs={'project': self.project.name_short}))) driver.find_element_by_link_text("New issue").click() driver.find_element_by_xpath("(//input[@type='search'])[2]").send_keys( '\n') time.sleep(1) self.assertEqual( len( driver.find_elements_by_css_selector( '#select2-id_dependsOn-results li')), 1) for i in driver.find_elements_by_css_selector( '#select2-id_dependsOn-results li'): self.assertIn("title", i.text)
def propose(user, title, body, direction, source_url, source_type=u"website", is_draft=False, issue_no=None): """ Propose an issue into the game """ if not issue_no: userprofile = user.get_profile() if not role_to_actions[userprofile.role].has_key('propose'): return new_issue = Issue( title = title, url = source_url, source_type = source_type, body = body, user = user, is_draft = is_draft, ) new_issue.save() user.message_set.create(message="You created issue \"%s\" successfully " % new_issue.title ) score.propose(user) levels.upgrade[userprofile.role](userprofile) else: # we are editing one TODO remove this possibility? issue = get_object_or_404(Issue, pk=issue_no) # Now change the issue and issue body to the appropriate values. issue.title = title issue.is_draft = is_draft issue.body = body issue.source_type = source_type issue.source_url = source_url issue.save() new_issue = issue Vote.objects.record_vote(user , new_issue, direction,) return new_issue
def test_create_and_download_attachment(self): issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") issue.save() # create file for uploading filecontent = 'Hello World' temp = tempfile.NamedTemporaryFile(delete=False) temp.write(filecontent.encode()) temp.close() f = File(open(temp.name, 'r')) attachment = Attachment(file=f, creator=self.user, issue=issue) attachment.save() f.close() os.unlink(temp.name) issue = Issue.objects.get(pk=issue.pk) self.assertEqual(issue.attachments.count(), 1) attachment = issue.attachments.first() self.assertEqual(attachment.creator, self.user) self.assertEqual(attachment.seqnum, 1) self.assertEqual(attachment.issue.nextAttachmentId, 2) response = self.client.get( reverse('issue:download_attachment', kwargs={ 'project': proj_short, 'sqn_i': 1, 'sqn_a': attachment.seqnum })) self.assertEqual(response.status_code, 200) self.assertEqual("application/octet-stream", response.get('Content-Type')) self.assertEqual(response.resolver_match.func.__name__, AttachmentDownloadView.as_view().__name__)
def test_flags(self): kanbancol = KanbanColumn(name='Column', position=1, project=self.project) kanbancol.save() issue = Issue(title="Test-Issue", kanbancol=kanbancol, project=self.project, type="Bug", sprint=self.sprint) issue.save() self.assertEqual(issue.was_in_sprint, False) response = self.client.post( reverse('sprint:stopsprint', kwargs={ 'project': self.project.name_short, 'sqn_s': self.sprint.seqnum })) self.assertEqual(Issue.objects.get(pk=issue.pk).was_in_sprint, True) issue.save() # unset the flag self.assertEqual(issue.was_in_sprint, False)
def test_timelog_loginfo_and_issue_loginfo_log_time_in_multiple_issues_and_projects_for_own_user(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}, follow=True) response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue2.number}), {'time': '15m', 'created_at': self.time}, follow=True) response = self.client.get(reverse('timelog:loginfo')) # first log self.assertContains(response, "1 Hour and 30 Minutes") # second log - second issue self.assertContains(response, "15 Minutes") project2 = Project(creator=self.user, name_short='PRJ2') project2.save() project2.developer.add(self.user) # both are shown in timelog:loginfo response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, "1 Hour and 30 Minutes") self.assertContains(response, "15 Minutes") # additional log for a different project issue3 = Issue(title='third_issue', project=project2) issue3.save() issue3.assignee.add(self.user) response = self.client.post(reverse('issue:log', kwargs={"project": project2.name_short, "sqn_i": issue3.number}), {'time': '20m', 'created_at': self.time}, follow=True) # all three are shown in timelog:loginfo response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, "1 Hour and 30 Minutes") self.assertContains(response, "15 Minutes") self.assertContains(response, "20 Minutes")
def test_attachment_delete(self): # create sample issue issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") issue.save() # create sample attachment filecontent = 'Hello World' temp = tempfile.NamedTemporaryFile(delete=False) temp.write(filecontent.encode()) temp.close() f = File(open(temp.name, 'r')) attachment = Attachment(file=f, creator=self.user, issue=issue) attachment.save() f.close() filePath = attachment.file.path self.assertTrue(os.path.isfile(filePath)) # delete the attachment response = self.client.get(reverse('issue:delete_attachment', kwargs={ 'project': self.project.name_short, 'sqn_i': issue.number, 'sqn_a': attachment.seqnum }), follow=True) self.assertRedirects( response, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': issue.number })) self.assertFalse(issue.attachments.all().exists()) self.assertFalse(os.path.isfile(filePath))
def test_archive_and_unarchive_single_issue_function(self): issue = Issue(title="bar", project=self.project) issue.save() self.assertFalse(issue.archived) n = Issue.objects.archived().count() values = {'sqn_i': issue.number} response = self.client.post( reverse('issue:archiveissue', kwargs={'project': self.project.name_short}), values) self.assertRedirects( response, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': issue.number })) issue.refresh_from_db() self.assertEqual(Issue.objects.archived().count(), (n + 1)) self.assertTrue(issue.archived) values = {'sqn_i': issue.number} response = self.client.post( reverse('issue:unarchiveissue', kwargs={'project': self.project.name_short}), values) self.assertRedirects( response, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': issue.number })) issue.refresh_from_db() self.assertEqual(Issue.objects.archived().count(), n) self.assertFalse(issue.archived)
def test_dependencies(self): driver = self.selenium # create two issues issue = Issue(title="title", kanbancol=KanbanColumn.objects.get(project=self.project, name="Todo"), due_date=str(datetime.date.today()), priority=3, storypoints=2, description="blubber", project=self.project ) issue.save() issue.assignee.add(self.user) issue = Issue(title="title", kanbancol=KanbanColumn.objects.get(project=self.project, name="Todo"), due_date=str(datetime.date.today()), priority=3, storypoints=2, description="blubber", project=self.project ) issue.save() issue.assignee.add(self.user) driver.get('{}{}'.format(self.live_server_url, reverse('backlog:backlog', kwargs={'project': self.project.name_short} ))) driver.find_element_by_link_text("PRJ-1").click() driver.find_element_by_id("issue_detail_edit_link").click() driver.find_element_by_xpath("(//input[@type='search'])[2]").send_keys('\n') time.sleep(1) self.assertEqual(len(driver.find_elements_by_css_selector('#select2-id_dependsOn-results li')), 1) for i in driver.find_elements_by_css_selector('#select2-id_dependsOn-results li'): i.click() driver.find_element_by_id("id_submit_edit").click() self.assertIn('Depends on', driver.page_source) self.assertIn('PRJ-2', driver.page_source) url = driver.current_url driver.find_element_by_link_text("title").click() self.assertIn('Dependent issues', driver.page_source) self.assertIn('PRJ-1', driver.page_source) driver.find_element_by_link_text("title").click() self.assertEqual(url, driver.current_url)
def test_next_parameter_in_post_requests(self): issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") issue.save() response = self.client.post(reverse( 'issue:assigntome', kwargs={'project': self.project.name_short}), { 'sqn_i': issue.number, 'next': '/timelog' }, follow=True) self.assertRedirects(response, reverse('timelog:loginfo')) response = self.client.post(reverse( 'issue:rmfromme', kwargs={'project': self.project.name_short}), { 'sqn_i': issue.number, 'next': '/timelog' }, follow=True) self.assertRedirects(response, reverse('timelog:loginfo')) response = self.client.post( reverse('issue:archiveissue', kwargs={'project': self.project.name_short}), { 'sqn_i': issue.number, 'next': '/timelog' }, follow=True) self.assertRedirects(response, reverse('timelog:loginfo')) response = self.client.post(reverse( 'issue:unarchiveissue', kwargs={'project': self.project.name_short}), { 'sqn_i': issue.number, 'next': '/timelog' }, follow=True) self.assertRedirects(response, reverse('timelog:loginfo'))
def test_board_filter_my_issues(self): driver = self.selenium project = Project(name='TestProjekt', name_short='TP', creator=self.user) project.save() project.developer.add(self.user) issue1 = Issue(project=project, title="TestIssue1", priority=4, type='Story') issue2 = Issue(project=project, title="TestIssue2", priority=0, type='Bug') issue3 = Issue(project=project, title="TestIssue3", priority=3, type='Task') issue4 = Issue(project=project, title="TestIssue3", priority=2, type='Task') issue5 = Issue(project=project, title="TestIssue3", priority=0, type='Story') issue1.save() issue2.save() issue3.save() issue4.save() issue5.save() driver.get("{}{}".format(self.live_server_url, reverse('sprint:sprintboard', kwargs={'project': project.name_short} ))) # 5 issues in col 0 col = driver.find_element_by_id("sortable0") issues = col.find_elements_by_class_name("issuecard") self.assertEqual(len(issues), 5) issue1.assignee.add(self.user) driver.get("{}{}".format(self.live_server_url, reverse('sprint:sprintboard', kwargs={'project': project.name_short} ))+"?myissues=true") # 1 issue in col 0 col = driver.find_element_by_id("sortable0") issues = col.find_elements_by_class_name("issuecard") self.assertEqual(len(issues), 1)
def test_view_and_template(self): # create response = self.client.get( reverse('issue:create', kwargs={'project': self.project.name_short})) self.assertTemplateUsed(response, create_template) self.assertEqual(response.resolver_match.func.__name__, IssueCreateView.as_view().__name__) # create issue for edit and detail tests issue = Issue(title="foo") issue.project = self.project issue.save() number = issue.number # edit response = self.client.get( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': number })) self.assertTemplateUsed(response, edit_template) self.assertEqual(response.resolver_match.func.__name__, IssueEditView.as_view().__name__) # detail response = self.client.get( reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': number })) self.assertTemplateUsed(response, detail_template) self.assertEqual(response.resolver_match.func.__name__, IssueDetailView.as_view().__name__)
def test_create_and_edit_issues_with_get_requests_disabled(self): values = { 'title': "Test-Issue", 'kanbancol': self.column.pk, 'type': "Bug", 'assignee': (self.user.pk), 'priority': 2, } # create response = self.client.get( reverse('issue:create', kwargs={'project': self.project.name_short}), values) # didn't store something self.assertTemplateUsed(response, create_template) try: self.assertIsNone( Issue.objects.get(title="Test-Issue", assignee=self.user.pk)) except Issue.DoesNotExist: pass # create issue for edit test issue = Issue(title="foo") issue.project = self.project issue.save() number = issue.number # edit response = self.client.get( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': number }), values) # didn't store something self.assertIsNotNone( Issue.objects.get(title="foo", project=self.project))
def test_assign_to_me_remove_from_me(self): issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") issue.save() response = self.client.post( reverse('issue:assigntome', kwargs={'project': self.project.name_short}), {'sqn_i': issue.number}) self.assertIn(self.user, issue.assignee.all()) response = self.client.post( reverse('issue:rmfromme', kwargs={'project': self.project.name_short}), {'sqn_i': issue.number}) self.assertNotIn(self.user, issue.assignee.all()) response = self.client.post(reverse( 'issue:rmfromme', kwargs={'project': self.project.name_short}), {'sqn_i': issue.number}, follow=True) self.assertContains( response, 'Issue was not in selected sprint, not performing any action')
def test_storypoints_in_backlog(self): kanbancol = KanbanColumn(name='Column', position=1, project=self.project) kanbancol.save() issue = Issue(title="Test-Issue", kanbancol=kanbancol, project=self.project, type="Bug", sprint=self.sprint) issue.save() response = self.client.get( reverse('backlog:backlog', kwargs={'project': self.project.name_short})) self.assertNotContains(response, 'Storypoints:') issue.storypoints = 5 issue.save() response = self.client.get( reverse('backlog:backlog', kwargs={'project': self.project.name_short})) self.assertNotContains(response, 'Storypoints:') issue.assignee.add(self.user) response = self.client.get( reverse('backlog:backlog', kwargs={'project': self.project.name_short})) self.assertContains(response, 'Storypoints:') self.assertContains(response, str(self.user.username) + ': 5') user2 = get_user_model().objects.create_user('a2', 'b2', 'c2') user2.save() self.project.developer.add(user2) issue.assignee.add(user2) response = self.client.get( reverse('backlog:backlog', kwargs={'project': self.project.name_short})) self.assertContains(response, str(user2.username) + ': 2.5') self.assertContains(response, str(self.user.username) + ': 2.5') issue2 = Issue(title="Test-Issue", kanbancol=kanbancol, project=self.project, type="Bug", sprint=self.sprint, storypoints=5) issue2.save() issue2.assignee.add(self.user) response = self.client.get( reverse('backlog:backlog', kwargs={'project': self.project.name_short})) self.assertContains(response, str(user2.username) + ': 2.5') self.assertContains(response, str(self.user.username) + ': 7.5')
def test_form(self): # create vals = { 'name': "Testcolumn", 'type': 'ToDo', 'project': self.project.pk } response = self.client.post( reverse('kanbancol:create', kwargs={'project': self.short}), vals) self.client.get(reverse("project:edit", kwargs={"project": self.short})) self.assertRedirects( response, reverse('project:edit', kwargs={'project': self.short})) response = self.client.get(response['location']) self.assertEqual(response.status_code, 200) # We always insert at the end self.assertEqual(response.context['columns'][3].name, "Testcolumn") self.assertEqual(str(response.context['columns'][3]), "Testcolumn") # modify vals = { 'name': "Testmodification", 'type': 'ToDo', } response = self.client.post( reverse('kanbancol:update', kwargs={ 'position': 3, 'project': self.short }), vals) self.assertRedirects( response, reverse('project:edit', kwargs={'project': self.short})) response = self.client.get(response['location']) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['columns'][3].name, "Testmodification") # assign issue to column and try to delete issue = Issue(title="Test-Issue", kanbancol=KanbanColumn.objects.get( position=3, project__name_short=self.short), project=self.project, type="Bug") issue.save() self.assertEqual(KanbanColumn.objects.count(), 4) response = self.client.post( reverse('kanbancol:delete', kwargs={ 'position': 3, 'project': self.short }), {'delete': 'true'}) self.assertRedirects( response, reverse('project:edit', kwargs={'project': self.short})) self.assertEqual(KanbanColumn.objects.count(), 4) issue.kanbancol = KanbanColumn.objects.get( position=2, project__name_short=self.short) issue.save() # delete response = self.client.post( reverse('kanbancol:delete', kwargs={ 'position': 3, 'project': self.short }), {'delete': 'true'}) self.assertRedirects( response, reverse('project:edit', kwargs={'project': self.short})) response = self.client.get(response['location']) self.assertEqual(KanbanColumn.objects.count(), 3) self.assertNotContains(response, vals["name"])
class TimelogTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify these elements they need to be created in setUp(), instead of here cls.user = get_user_model().objects.create_user('test', '*****@*****.**', 'test1234') cls.user2 = get_user_model().objects.create_user('test2', '*****@*****.**', 'test1234') cls.user.save() cls.user2.save() def setUp(self): self.client.force_login(self.user) # NOTE: these elements get modified by some testcases, so they should NOT be created in setUpTestData() self.project = Project(creator=self.user, name_short='PRJ', activity_only_for_managers=False) self.project.save() self.project.developer.add(self.user) self.project2 = Project(creator=self.user, activity_only_for_managers=True, name_short='PRJJ') self.project2.save() self.project2.manager.add(self.user) self.project2.developer.add(self.user, self.user2) self.kanbancol = KanbanColumn(project=self.project, position=4, name='test') self.kanbancol.save() self.issue = Issue(title='a very very very very very very very long issue title', project=self.project, due_date='2016-12-16', kanbancol=self.kanbancol, storypoints='3' ) self.issue.save() self.issue.assignee.add(self.user) self.issue2 = Issue(title='second_issue', project=self.project) self.issue2.save() self.issue2.assignee.add(self.user) self.time = now().strftime("%Y-%m-%d %H:%M:%S") # success logedit_address_kwargs self.sqn_l1_address_kwarg = {"project": self.issue.project.name_short, "sqn_i": self.issue.number, "sqn_l": 1 } def test_view_and_template(self): # TODO TESTCASE see invite_users # use view_and_template() # TODO which views? # - timelog:loginfo # - timelog:archiv # - issue:log # - issue:logs with (?P<sqn_l>[0-9]+)/edit/ (logedit) # - issue:logs with (?P<sqn_l>[0-9]+)/delete/? (logdelete) # - issue:logedit # - issue:logdelete # - ProjectUserTimelogView - project:usertimelog # - ProjectDetailTimelogView - project:timelog # - ... pass def test_redirect_to_login_and_login_required(self): self.client.logout() # TODO TESTCASE see invite_users # redirect_to_login_and_login_required() # TODO which views? # - timelog:loginfo # - timelog:archiv # - issue:log # - issue:logs with (?P<sqn_l>[0-9]+)/edit/ (logedit) # - issue:logs with (?P<sqn_l>[0-9]+)/delete/? (logdelete) # - issue:logedit # - issue:logdelete # - ProjectUserTimelogView - project:usertimelog # - ProjectDetailTimelogView - project:timelog # - ... pass def test_user_passes_test_mixin(self): # TODO TESTCASE # - timelog:loginfo # - timelog:archiv # - issue:log # - issue:logs with (?P<sqn_l>[0-9]+)/edit/ (logedit) # - issue:logs with (?P<sqn_l>[0-9]+)/delete/? (logdelete) # - issue:logedit # - issue:logdelete # - ProjectUserTimelogView - project:usertimelog # - ProjectDetailTimelogView - project:timelog # - ... pass def test_log_time_and_redirect(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}, follow=True) self.assertRedirects(response, reverse('issue:detail', kwargs={'project': self.project.name_short, 'sqn_i': 1})) # PER USER START def test_timelog_loginfo_per_user_only_visible_for_own_user(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}, follow=True) response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, "1 Hour and 30 Minutes") # tag filter issue title short self.assertContains(response, "...") self.client.logout() self.client.force_login(self.user2) response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number, }), {'time': '15m', 'created_at': self.time}) self.client.logout() self.client.force_login(self.user) response = self.client.get(reverse('timelog:loginfo')) # first log self.assertContains(response, "1 Hour and 30 Minutes") # second log - different user self.assertNotContains(response, "15 Minutes") def test_timelog_loginfo_and_issue_loginfo_log_time_in_multiple_issues_and_projects_for_own_user(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}, follow=True) response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue2.number}), {'time': '15m', 'created_at': self.time}, follow=True) response = self.client.get(reverse('timelog:loginfo')) # first log self.assertContains(response, "1 Hour and 30 Minutes") # second log - second issue self.assertContains(response, "15 Minutes") project2 = Project(creator=self.user, name_short='PRJ2') project2.save() project2.developer.add(self.user) # both are shown in timelog:loginfo response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, "1 Hour and 30 Minutes") self.assertContains(response, "15 Minutes") # additional log for a different project issue3 = Issue(title='third_issue', project=project2) issue3.save() issue3.assignee.add(self.user) response = self.client.post(reverse('issue:log', kwargs={"project": project2.name_short, "sqn_i": issue3.number}), {'time': '20m', 'created_at': self.time}, follow=True) # all three are shown in timelog:loginfo response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, "1 Hour and 30 Minutes") self.assertContains(response, "15 Minutes") self.assertContains(response, "20 Minutes") # PER ISSUE START def test_log_time_per_issue_even_from_other_user(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}) response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '15m', 'created_at': self.time}) self.client.logout() self.project.developer.add(self.user2) self.issue.assignee.add(self.user2) self.client.force_login(self.user2) # second user log response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '20m', 'created_at': self.time}) response = self.client.get(reverse('issue:detail', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number})) # first log - first user self.assertContains(response, "1 Hour and 30 Minutes") # second log - first user self.assertContains(response, "15 Minutes") # third log - second user self.assertContains(response, "20 Minutes") def test_log_time_in_future_fails(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': (now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")}) self.assertEqual(response.status_code, 200) # still on the same page (issue:log) self.assertTemplateUsed(response, 'timelog/timelog_create.html') self.assertContains(response, 'The date entered must be today or lesser.') def test_log_time_delete_log_per_issue(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}) response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '2h', 'created_at': self.time}) response = self.client.post(reverse('issue:logdelete', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number, "sqn_l": 2}), {'delete': 'true'}, follow=True) self.assertTemplateUsed(response, 'timelog/timelog_list_peruser.html') self.assertContains(response, "1 Hour and 30 Minutes") self.assertNotContains(response, "2 Hours") def test_log_time_keep_log_per_issue(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}) response = self.client.post(reverse('issue:logdelete', kwargs=self.sqn_l1_address_kwarg), {'keep': 'true'}, follow=True) self.assertTemplateUsed(response, 'timelog/timelog_list_peruser.html') self.assertContains(response, "1 Hour and 30 Minutes") def test_log_time_edit_log_per_issue(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1d1h30m', 'created_at': self.time}) response = self.client.get(reverse('issue:detail', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number})) self.assertContains(response, "1 Day, 1 Hour and 30 Minutes") response = self.client.get(reverse('issue:logedit', kwargs=self.sqn_l1_address_kwarg)) self.assertContains(response, "1d 1h 30m") response = self.client.post(reverse('issue:logedit', kwargs=self.sqn_l1_address_kwarg), {'time': '3h', 'created_at': self.time, 'save_timelog_change': 'true'}) response = self.client.get(reverse('issue:detail', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number})) self.assertContains(response, "3 Hours") def test_edit_and_delete_as_other_user(self): response = self.client.post(reverse('issue:log', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number}), {'time': '1h30m', 'created_at': self.time}) self.assertEqual(str(Timelog.objects.filter(issue=self.issue)[0].time), "1:30:00") # test to edit or delete as other user - should not be possible self.client.logout() self.client.force_login(self.user2) user_doesnt_pass_test_and_gets_404(self, 'issue:logedit', address_kwargs=self.sqn_l1_address_kwarg) self.assertEqual(str(Timelog.objects.filter(issue=self.issue)[0].time), "1:30:00") user_doesnt_pass_test_and_gets_404(self, 'issue:logdelete', address_kwargs=self.sqn_l1_address_kwarg) self.assertEqual(str(Timelog.objects.filter(issue=self.issue)[0].time), "1:30:00") # PROJECT START def test_project_detail_timelog_from_different_issuesss_of_project_even_from_other_users(self): cases = ['2d 2h 5m', '1d 1h 5m', '2d 5m', '1d 3h'] mappings = ['2 Days, 2 Hours and 5 Minutes', '1 Day, 1 Hour and 5 Minutes', '2 Days and 5 Minutes', '1 Day and 3 Hours'] sums = ['3 Days, 3 Hours and 10 Minutes', '3 Days, 3 Hours and 5 Minutes'] # first user, first issue response = self.client.post(reverse('timelog:loginfo'), {'time': cases[0], 'created_at': self.time, 'issue': self.issue.id}, follow=True) # first user, second issue response = self.client.post(reverse('timelog:loginfo'), {'time': cases[1], 'created_at': self.time, 'issue': self.issue2.id}, follow=True) self.client.logout() self.project.developer.add(self.user2) self.issue.assignee.add(self.user2) self.issue2.assignee.add(self.user2) self.client.force_login(self.user2) # second user # first user, first issue response = self.client.post(reverse('timelog:loginfo'), {'time': cases[2], 'created_at': self.time, 'issue': self.issue.id}, follow=True) # first user, second issue response = self.client.post(reverse('timelog:loginfo'), {'time': cases[3], 'created_at': self.time, 'issue': self.issue2.id}, follow=True) response = self.client.get(reverse('project:timelog', kwargs={"project": self.issue.project.name_short})) for user in self.project.developer.all(): self.assertContains(response, user.username) for i in range(4): self.assertContains(response, mappings[i]) # sums of both users self.assertContains(response, 'total: '+sums[0]) self.assertContains(response, 'total: '+sums[1]) def test_project_detail_timelog_username_appears(self): for dev in self.project.developer.all(): response = self.client.get(reverse('project:usertimelog', kwargs={"project": self.issue.project.name_short, "username": dev.username})) self.assertContains(response, dev.username) def test_timelog_edit_render_value_issue_logedit(self): cases = ['1d 1h 5m', '1d 5m', '1d 3h', '1h 5m', '1d', '3h', '5m' ] for i in range(len(cases)): response = self.client.post(reverse('timelog:loginfo'), {'time': cases[i], 'created_at': self.time, 'issue': self.issue.id}, follow=True) response = self.client.get(reverse('issue:logedit', kwargs={"project": self.issue.project.name_short, "sqn_i": self.issue.number, "sqn_l": i+1})) self.assertContains(response, cases[i]) def test_timelog_loginfo(self): cases = ['2d 2h 5m', '1d 1h 5m', '1d 5m', '1d 3h', '1h 5m', '1d', '3h', '5m', '1m' ] for i in range(len(cases)): response = self.client.post(reverse('timelog:loginfo'), {'time': cases[i], 'created_at': self.time, 'issue': self.issue.id}, follow=True) log = Timelog.objects.get(id=i+1) response = self.client.get(reverse('timelog:loginfo')) self.assertContains(response, duration(log.time)) def test_timelog_model_str_method(self): response = self.client.post(reverse('timelog:loginfo'), {'time': '2h30m', 'created_at': self.time, 'issue': self.issue.id}, follow=True) log = Timelog.objects.get(id=1) self.assertEqual(str(log), "logged time: {} for issue: {}".format(log.time, log.issue)) def test_project_timelogs_only_for_manager_setting(self): response = self.client.get(reverse('project:timelog', kwargs={"project": self.project2.name_short})) for user in self.project2.developer.all(): self.assertContains(response, user.username) response = self.client.get(reverse('project:usertimelog', kwargs={"project": self.project2.name_short, "username": self.user2.username}), follow=True) self.assertContains(response, self.user2.username) self.client.force_login(self.user2) response = self.client.get(reverse('project:timelog', kwargs={"project": self.project2.name_short})) self.assertRedirects(response, reverse('project:usertimelog', kwargs={"project": self.project2.name_short, "username": self.user2.username} ) ) response = self.client.get(reverse('project:usertimelog', kwargs={"project": self.project2.name_short, "username": self.user.username}), follow=True) self.assertRedirects(response, reverse('project:usertimelog', kwargs={"project": self.project2.name_short, "username": self.user2.username} ) ) self.project2.activity_only_for_managers = False self.project2.save() response = self.client.get(reverse('project:timelog', kwargs={"project": self.project2.name_short})) for user in self.project2.developer.all(): self.assertContains(response, user.username) response = self.client.get(reverse('project:usertimelog', kwargs={"project": self.project2.name_short, "username": self.user.username}), follow=True) self.assertContains(response, self.user.username) def test_timelog_archiv(self): response = self.client.get(reverse('timelog:archiv')) response = self.client.get(reverse('timelog:archiv')+'?page=2')
class StripImgMetadataTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify these elements they need to be created in setUp(), instead of here cls.user = get_user_model().objects.create_user( user_name, user_email, 'c') cls.project = Project(creator=cls.user, name_short='PRJ') cls.project.save() cls.project.manager.add(cls.user) cls.project.developer.add(cls.user) cls.images = list(cls.create_images()) # TODO https://blog.brian.jp/python/png/2016/07/07/file-fun-with-pyhon.html # TODO malicious payload jpg, png, bmp, gif @classmethod def tearDownClass(cls): for image in cls.images: os.unlink(image) os.unlink(forbidden_img) super().tearDownClass() def setUp(self): self.client.force_login(self.user) # NOTE: these elements get modified by some testcases, so they should NOT be created in setUpTestData() self.issue = Issue(title="Test-Issue", project=self.project) self.issue.save() def tearDown(self): # delete uploaded avatars rmtree(avatars_path, ignore_errors=True) # TODO TESTCASE delete all uploaded files (the files that are stored on server side # so attachments needs to be removed too # TODO therefore we need to know the structure of the attachments dir and which one has been added # helper function to create all the different image types and returns the file names of all generated images def create_images(): # actual image color: 255,0,0 img = Image.new("RGB", (100, 20), color='red') text = ImageDraw.Draw(img) text.text((10, 10), "Hello World", fill=(0, 0, 0)) image_wmeta = 'image_wmeta' # thumbnail color: 0,0,255 o = io.BytesIO() secret_thumbnail = Image.new("RGB", (120, 20), color='blue') text = ImageDraw.Draw(secret_thumbnail) text.text((10, 10), "secret thumbnail", fill=(0, 0, 0)) # transform it to bytes secret_thumbnail.save(o, "jpeg") secret_exif_thumbnail = o.getvalue() secret_thumbnail.close() # forbidden image_extension img.save(forbidden_img, "tiff") # bmp doesn't contain critical meta information img.save(image_wmeta + "_bmp" + '.bmp') # for some reasons some of these values don't match the relative specification: # rational numbers are separated at the comma, f.e. 13.37 is represented by [(13), (37)] # http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf#page=47 ... # http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf#page=87 jpg_exif = { "0th": { piexif.ImageIFD.ImageDescription: u"description", piexif.ImageIFD.StripOffsets: 3, piexif.ImageIFD.Artist: u"artist", piexif.ImageIFD.Copyright: u"copyright holder", piexif.ImageIFD.DateTime: u"2012:01:08 10:09:01", }, "Exif": { piexif.ExifIFD.DateTimeOriginal: u"2016:08:07 13:37:10", piexif.ExifIFD.DateTimeDigitized: u"2015:03:07 14:20:30", piexif.ExifIFD.OffsetTime: u"2017:05:09 08:04:04", piexif.ExifIFD.OffsetTimeOriginal: u"2017:04:12 18:15:00", piexif.ExifIFD.OffsetTimeDigitized: u"2016:02:10 11:10:03", piexif.ExifIFD.SubSecTime: u"2017:09:04 10:03:10", piexif.ExifIFD.SubSecTimeOriginal: u"2019:10:03 10:03:10", piexif.ExifIFD.SubSecTimeDigitized: u"2013:10:03 10:03:10", piexif.ExifIFD.CameraOwnerName: u"Cameraname", piexif.ExifIFD.BodySerialNumber: u"body serialnumber", piexif.ExifIFD.LensSerialNumber: u"lens serialnumber", piexif.ExifIFD.UserComment: b"secret comment", }, "GPS": { piexif.GPSIFD.GPSLatitudeRef: u"N", piexif.GPSIFD.GPSLatitude: [(10, 1), (20, 1), (0, 0)], piexif.GPSIFD.GPSLongitudeRef: u"W", piexif.GPSIFD.GPSLongitude: [(10, 1), (20, 1), (0, 0)], piexif.GPSIFD.GPSAltitudeRef: 0, piexif.GPSIFD.GPSAltitude: (200, 1), piexif.GPSIFD.GPSTimeStamp: [(10), (3)], piexif.GPSIFD.GPSSatellites: u"satellites", piexif.GPSIFD.GPSStatus: u"A", piexif.GPSIFD.GPSMeasureMode: u"3", piexif.GPSIFD.GPSDOP: [(1), (4)], piexif.GPSIFD.GPSSpeedRef: u"K", piexif.GPSIFD.GPSSpeed: [(42), (10)], piexif.GPSIFD.GPSTrackRef: u"T", piexif.GPSIFD.GPSTrack: [(21), (123)], piexif.GPSIFD.GPSImgDirectionRef: u"T", piexif.GPSIFD.GPSImgDirection: [(10), (12)], piexif.GPSIFD.GPSMapDatum: u"today", piexif.GPSIFD.GPSDestLatitudeRef: u"N", piexif.GPSIFD.GPSDestLatitude: [(8, 1), (30, 1), (0, 0)], piexif.GPSIFD.GPSDestLongitudeRef: u"E", piexif.GPSIFD.GPSDestLongitude: [(8), (30)], piexif.GPSIFD.GPSDestBearingRef: u"T", piexif.GPSIFD.GPSDestBearing: [(1), (10)], piexif.GPSIFD.GPSDestDistanceRef: u"K", piexif.GPSIFD.GPSDestDistance: [(10), (3)], piexif.GPSIFD.GPSProcessingMethod: b"WLAN", piexif.GPSIFD.GPSAreaInformation: b"area", piexif.GPSIFD.GPSDateStamp: u"2015:10:03 10:03:10", piexif.GPSIFD.GPSDifferential: 1, piexif.GPSIFD.GPSHPositioningError: [(2), (0)], }, "1st": { piexif.ImageIFD.ImageDescription: u"description", piexif.ImageIFD.StripOffsets: 3, piexif.ImageIFD.Artist: u"artist", piexif.ImageIFD.Copyright: u"copyright holder", piexif.ImageIFD.DateTime: u"2013:10:03 10:03:10", }, "thumbnail": secret_exif_thumbnail } png_dict = { "ImageDescription": u"description", "StripOffsets": "3", "Artist": u"artist", "Copyright": u"copyright holder", "DateTime": u"2012:01:08 10:09:01", "DateTimeOriginal": u"2016:08:07 13:37:10", "DateTimeDigitized": u"2015:03:07 14:20:30", "OffsetTime": u"2017:05:09 08:04:04", "OffsetTimeOriginal": u"2017:04:12 18:15:00", "OffsetTimeDigitized": u"2016:02:10 11:10:03", "SubSecTime": u"2017:09:04 10:03:10", "SubSecTimeOriginal": u"2019:10:03 10:03:10", "SubSecTimeDigitized": u"2013:10:03 10:03:10", "CameraOwnerName": u"Cameraname", "BodySerialNumber": u"body serialnumber", "LensSerialNumber": u"lens serialnumber", "UserComment": b"secret comment", "GPSLatitudeRef": u"N", "GPSLatitude": "3 deg 20' 0.00", "GPSLongitudeRef": u"W", "GPSLongitude": "3 deg 20.1' 0.00", "GPSAltitudeRef": "0", "GPSAltitude": "200 m Above Sea Level", "GPSTimeStamp": "03:19:59.999999", "GPSSatellites": u"satellites", "GPSStatus": u"A", "GPSMeasureMode": u"3", "GPSSpeedRef": u"K", "GPSSpeed": "4.2", "GPSTrackRef": u"T", "GPSTrack": "0.1707317073", "GPSImgDirectionRef": u"T", "GPSImgDirection": "0.6333333333", "GPSMapDatum": u"today", "GPSDestLatitudeRef": u"N", "GPSDestLatitude": "3 deg 30' 0.00", "GPSDestLongitudeRef": u"E", "GPSDestLongitude": "0 deg 16' 0.00", "GPSDestBearingRef": u"T", "GPSDestBearing": "0.1", "GPSDestDistanceRef": u"K", "GPSDestDistance": "3.333333333", "GPSProcessingMethod": b"WLAN", "GPSAreaInformation": b"area", "GPSDateStamp": u"2015:10:03 10:03:10", "GPSDifferential": "1", "ImageDescription": u"description", "StripOffsets": "3", "Artist": u"artist", "Copyright": u"copyright holder", "DateTime": u"2013:10:03 10:03:10", } # jpg with exif img.save(image_wmeta + '_jpg' + '.jpg', exif=piexif.dump(jpg_exif)) # copy jpg to jpe, jpeg copyfile(image_wmeta + '_jpg' + '.jpg', image_wmeta + '_jpe' + '.jpe') copyfile(image_wmeta + '_jpg' + '.jpg', image_wmeta + '_jpeg' + '.jpeg') # png exif-part png_info = PngImagePlugin.PngInfo() # copy png metadata for k, v in png_dict.items(): png_info.add_text(k, v, 0) img.save(image_wmeta + '_png' + '.png', "PNG", pnginfo=png_info) img.save(image_wmeta + '_gif' + '.gif') img.close() # xmp for gif and png xmp = XMPMeta() xmp.append_array_item(consts.XMP_NS_DC, 'secret', 'secret information', { 'prop_array_is_ordered': True, 'prop_value_is_array': True }) # gif xmp # TODO BUG Exempi library version >= 2.5 does not work with GIF images created by Pillow. # TODO BUG The format gets not recognized by Exempi. # TODO BUG Maybe a newer Pillow or Exempi version will fix this... # gif_image = XMPFiles(file_path=image_wmeta + '_gif' + ".gif", open_forupdate=True) # gif_image.put_xmp(xmp) # gif_image.close_file() # png part 2 png_image = XMPFiles(file_path=image_wmeta + '_png' + ".png", open_forupdate=True) png_image.put_xmp(xmp) png_image.close_file() return ((image_wmeta + '_' + suffix + "." + suffix) for suffix in ALLOWED_IMG_EXTENSIONS) def verify_metadatas_are_removed(self, file_path): # helper function that verifies that the provided image doesn't contain any sensitive metadata # empty exif self.assertEqual(piexif.load(file_path)["0th"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["Exif"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["GPS"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["1st"], {}, msg="sensitive exif data left") # Imagine the following scenario: An image contains sensitive information, it gets modified to hide these. # If there is an exif-thumbnail it might represent the previous image and hence could leak those information. self.assertEqual(piexif.load(file_path)["thumbnail"], None, msg="The exif thumbnail has not been removed.") # verify that xmp is also empty. Normally the xmp content is stored in within the rdf tag xmp_file = XMPFiles(file_path=file_path) xmp_content = str(xmp_file.get_xmp()) # this won't match if there are any additional xmp elements left, because they would occur between the opening # of the rdf:Description tag and the closing of the rdf:RDF tag. sensitive_information = re.findall(" <rdf:Description.*\n </rdf:RDF>", xmp_content) self.assertEqual( len(sensitive_information), 1, msg="There are sensitive xmp-tags left:\n\n{}".format(xmp_content)) xmp_file.close_file() def verify_images_are_sanitized(self, file_path): # helper function that verifies that the provided image is sanitized # TODO TESTCASE image sanitize check return True def test_change_avatar(self): # upload all prepared images for img_name in self.images: img = open(img_name, "rb") img_dict = { 'avatar': img, 'email': user_email, 'timezone': timezone, 'language': 'en', } response = self.client.post(reverse('user_profile:edit_profile', kwargs={"username": user_name}), img_dict, follow=True) img.close() # verify the file has been uploaded successfully file_name = os.path.basename(img_name).partition(".")[0] + ".jpg" self.assertContains(response, file_name) file_path = avatars_path + "/" + file_name # verify there are no sensitive metadata left self.verify_metadatas_are_removed(file_path) # verify there is no malicious code left self.verify_images_are_sanitized(file_path) def test_comment_with_picture(self): # TODO TESTCASE comment with picture from issue-detail - picture stripping # upload one image # TODO call verify_metadatas_are_removed() and verify_images_are_sanitized() pass def test_file_upload_picture(self): # TODO TESTCASE attachment from issue-detail - picture stripping # upload one image # TODO call verify_metadatas_are_removed() and verify_images_are_sanitized() pass def test_change_user_profile_wo_default_avatar(self): # TODO TESTCASE create test that changes the user profile while the current avatar is not the default one # this might produce some additional errors pass def test_reject_forbidden_img_extensions(self): # upload a forbidden img extensions img = open(forbidden_img, "rb") img_dict = { 'avatar': img, 'email': user_email, 'timezone': timezone, 'language': 'en', } response = self.client.post(reverse('user_profile:edit_profile', kwargs={"username": user_name}), img_dict, follow=True) img.close() # verify the file has NOT been uploaded successfully file_name = os.path.basename(forbidden_img).partition(".")[0] + ".jpg" self.assertNotContains(response, file_name) self.assertContains(response, "is not allowed. Allowed extensions are:") # upload a forbidden img type with an allowed extensions file_name = 'trick.jpg' copyfile(forbidden_img, file_name) img = open(file_name, "rb") img_dict['avatar'] = img response = self.client.post(reverse('user_profile:edit_profile', kwargs={"username": user_name}), img_dict, follow=True) img.close() os.unlink(file_name) self.assertNotContains(response, file_name) self.assertContains( response, "Either unable to detect the image type or the image type is not supported. " + "Supported image extensions are:") def test_file_size_limitation(self): # verify that the allowed image size (avatar) is actually limited huge_img = TEST_FILE_PATH + '/8mb.png' img = open(huge_img, "rb") img_dict = { 'avatar': img, 'email': user_email, 'timezone': timezone, 'language': 'en', } # TODO BUG ResourceWarning: unclosed file <_io.BufferedReader name='/tmp/tmp......upload.png'> # seems to be a leak in Pillow response = self.client.post(reverse('user_profile:edit_profile', kwargs={"username": user_name}), img_dict, follow=True) img.close() self.assertContains( response, "The uploaded image exceeds the allowed file size of: ") def test_malicious_pictures(self): # TODO TESTCASE upload malicious image and verify it is harmless after upload pass
class GitFrontendTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify these elements they need to be created in setUp(), instead of here cls.user = get_user_model().objects.create_user('a', 'b', 'c') cls.user2 = get_user_model().objects.create_user('d', 'e', 'f') def setUp(self): self.client.force_login(self.user) # NOTE: these elements get modified by some testcases, so they should NOT be created in setUpTestData() self.project = Project(creator=self.user, name_short='PRJ') self.project.save() self.issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.project.kanbancol.first(), type="Bug") self.issue.save() def test_view_and_template(self): # TODO TESTCASE invite_users # use view_and_template() # - FileDiffView - issue:commit_diff # TODO need to create a repository first # view_and_template(self, FileDiffView, 'commit/file_diff.html', 'issue:commit_diff', # address_kwargs={'project': self.project.name_short, 'sqn_i': self.issue.number}) pass def test_redirect_to_login_and_login_required(self): self.client.logout() # TODO TESTCASE invite_users # use redirect_to_login_and_login_required() # - FileDiffView - issue:commit_diff def test_clone_and_import(self): repo_path = '/tmp/gitpythonrepo' self.project.manager.add(self.user) self.project.developer.add(self.user2) self.project.save() filecontent1 = 'Hello World File 1' tmp1 = tempfile.NamedTemporaryFile(delete=False) tmp1.write(filecontent1.encode()) tmp1.close() filecontent2 = 'Hello World File 2' tmp2 = tempfile.NamedTemporaryFile(delete=False) tmp2.write(filecontent2.encode()) tmp2.close() with open(tmp1.name, 'r') as f1: with open(tmp2.name, 'r') as f2: response = self.client.post(reverse( 'project:gitintegration:create', kwargs={'project': self.project.name_short}), { 'url': 'http://im-a-dum.my/repo/path', 'rsa_priv_path': f1, 'rsa_pub_path': f2, }, follow=True) self.assertNotContains( response, "Your account doesn't have access to this page") self.project.refresh_from_db() self.assertEqual(self.project.repos.count(), 1) repo = self.project.repos.first() self.assertEqual(repo.url, 'http://im-a-dum.my/repo/path') # set correct repo path. This is not possible via request due to validators repo.url = repo_path repo.save() self.assertEqual(repo.url, repo_path) self.assertIn(filecontent1, repo.rsa_priv_path.read().decode()) self.assertIn(filecontent2, repo.rsa_pub_path.read().decode()) # read() opened the file repo.rsa_priv_path.close() repo.rsa_pub_path.close() # try importing without valid remote repository shutil.rmtree(repo_path, ignore_errors=True) shutil.rmtree(repo.get_local_repo_path(), ignore_errors=True) import_commits() repo.refresh_from_db() self.assertEqual(repo.conn_ok, False) # create a local repo initial_file = repo_path + '/initial' remote_repo = Repo.init(repo_path) f_open = open(initial_file, 'wb') f_open.write('Content in first file\n'.encode()) f_open.close() remote_repo.index.add([initial_file]) remote_repo_master = remote_repo.index.commit("initial commit") initial_sha = remote_repo_master.hexsha # try import again, connection should now work, but no commit to import Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) self.assertEqual(repo.last_commit_processed, remote_repo_master.hexsha) self.assertEqual(Commit.objects.count(), 0) # create commit with issue identifier in message f = repo_path + '/file1' open(f, 'wb').close() remote_repo.index.add([f]) remote_repo_master = remote_repo.index.commit(self.project.name_short + "-1 commit body") # import again, self.issue should now have a commit associated, activity shoud have increased activity = len(json.loads(repo.project.activity)) Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertGreater(len(json.loads(repo.project.activity)), activity) self.assertEqual(repo.conn_ok, True) self.assertEqual(repo.last_commit_processed, remote_repo_master.hexsha) self.assertEqual(Commit.objects.count(), 1) self.assertEqual(self.issue.commits.count(), 1) # examine commit c = self.issue.commits.first() self.assertNotEqual(c.author, '') self.assertEqual(c.name, remote_repo_master.hexsha) self.assertEqual(c.repository, repo) self.assertEqual(c.message, "commit body") self.assertEqual(len(c.get_tags()), 0) firstchange = c.get_changes()['file1'] self.assertEqual(firstchange['lines'], 0) self.assertEqual(firstchange['insertions'], 0) self.assertEqual(firstchange['deletions'], 0) # empty changes, get_changes() must not crash savedchanges = c.get_changes() c.changes = "" c.save() c.get_changes() c.set_changes(savedchanges) self.assertEqual(c.get_changes(), remote_repo_master.stats.files) # permissions checks maps to project's functions self.assertEqual(c.user_has_read_permissions(self.user), True) self.assertEqual(c.user_has_read_permissions(self.user2), True) self.assertEqual(c.user_has_write_permissions(self.user), True) self.assertEqual(c.user_has_write_permissions(self.user2), False) # reset last_commit_processed and try again: no duplicates should appear in Commits repo.last_commit_processed = '' repo.save() Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) self.assertEqual(repo.last_commit_processed, remote_repo_master.hexsha) self.assertEqual(Commit.objects.count(), 1) self.assertEqual(self.issue.commits.count(), 1) # commit with invalid issue id f = repo_path + '/file2' open(f, 'wb').close() remote_repo.index.add([f]) remote_repo_master = remote_repo.index.commit( self.project.name_short + "-42 commit body with invalid issue id") # activity should not change activity = len(json.loads(repo.project.activity)) Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertEqual(len(json.loads(repo.project.activity)), activity) self.assertEqual(repo.conn_ok, True) self.assertEqual(repo.last_commit_processed, remote_repo_master.hexsha) self.assertEqual(Commit.objects.count(), 1) self.assertEqual(self.issue.commits.count(), 1) # create commit with issue identifier in message f = repo_path + '/file3' f_open = open(f, 'wb') f_open.write('Fancy file content\nEven with newline\n'.encode()) f_open.close() remote_repo.index.add([f]) remote_repo_master = remote_repo.index.commit( self.project.name_short + "-1 commit body for file 3\n" + "This time with longer commit message") tag = remote_repo.create_tag('Test-Tag', ref=remote_repo_master) # import again, self.issue should now have 2 commits associated activity = len(json.loads(repo.project.activity)) Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertGreater(len(json.loads(repo.project.activity)), activity) self.assertEqual(repo.conn_ok, True) self.assertEqual(repo.last_commit_processed, remote_repo_master.hexsha) self.assertEqual(Commit.objects.count(), 2) self.assertEqual(self.issue.commits.count(), 2) # examine newest commit c = self.issue.commits.get(name=remote_repo_master.hexsha) self.assertNotEqual(c.author, '') self.assertEqual(c.name, remote_repo_master.hexsha) self.assertEqual(c.repository, repo) self.assertEqual( c.message, "commit body for file 3\nThis time with longer commit message") self.assertEqual(c.get_title(), "commit body for file 3") self.assertEqual(c.get_name_short(), remote_repo_master.hexsha[:7]) firstchange = c.get_changes()['file3'] self.assertEqual(firstchange['lines'], 2) self.assertEqual(firstchange['insertions'], 2) self.assertEqual(firstchange['deletions'], 0) self.assertEqual(c.__str__(), "Commit " + remote_repo_master.hexsha) self.assertEqual(len(c.get_tags()), 1) self.assertEqual(c.get_tags()[0], 'Test-Tag') # test file diff view diff = '@@ -0,0 +1,2 @@\n+Fancy file content\n+Even with newline\n'.splitlines( ) response = self.client.post( reverse('issue:commit_diff', kwargs={ 'project': self.project.name_short, 'sqn_i': c.issue.number, }), { 'filename': list(c.get_changes().keys())[0], 'repository': repo.pk, 'commit_sha': c.get_name_short(), }, ) self.assertNotContains( response, "Your account doesn't have access to this page") self.assertEqual(response.context_data['diff'], diff) self.assertEqual(response.context_data['filename'], list(c.get_changes().keys())[0]) self.assertEqual(response.context_data['commit_sha'], c.get_name_short()) # add another tag to an already imported commit tag = remote_repo.create_tag('Another_tag', ref=remote_repo_master) activity = len(json.loads(repo.project.activity)) Frontend.import_new_commits(repo) repo.refresh_from_db() self.assertGreater(len(json.loads(repo.project.activity)), activity) c = self.issue.commits.get(name=remote_repo_master.hexsha) self.assertEqual(len(c.get_tags()), 2) self.assertIn('Test-Tag', c.get_tags()) self.assertIn('Another_tag', c.get_tags()) # check with insufficient privileges self.project.manager.clear() user_doesnt_pass_test_and_gets_404( self, 'issue:commit_diff', address_kwargs={ 'project': self.project.name_short, 'sqn_i': c.issue.number, }, get_kwargs={ 'filename': list(c.get_changes().keys())[0], 'repository': repo.pk, 'commit_sha': c.get_name_short(), }, ) self.project.manager.add(self.user) # post broken data to view response = self.client.post(reverse('issue:commit_diff', kwargs={ 'project': self.project.name_short, 'sqn_i': c.issue.number, }), { 'filename': 'blabla', 'repository': repo.pk, 'commit_sha': 'blubber', }, follow=True) self.assertEqual(response.context_data['diff'], ''.splitlines()) self.assertEqual(response.context_data['filename'], 'blabla') self.assertEqual(response.context_data['commit_sha'], 'blubber') # check that commits are searchable sr = SearchFrontend.query('commit body', self.user) self.assertEqual(len(sr), 2) sr = SearchFrontend.query('Commit.message ~ "commit message"', self.user) self.assertEqual(len(sr), 1) self.assertEqual(sr[0][0], "(" + c.get_name_short() + ") commit body for file 3") # examine file diffs f = repo_path + '/file4' f_open = open(f, 'wb') f_open.write('Fancy file content\n'.encode()) f_open.close() remote_repo.index.add([f]) remote_repo_master = remote_repo.index.commit('file4') Frontend.import_new_commits(repo) diff = Frontend.get_diff(repo, remote_repo_master.hexsha, 'file4') repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) self.assertEqual(diff, "@@ -0,0 +1 @@\n+Fancy file content\n") # check that commit changes are searchable sr = SearchFrontend.query('file', self.user) self.assertEqual(len(sr), 1) # check with first commit diff = Frontend.get_diff(repo, initial_sha, 'initial') repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) # TODO TESTCASE we're currently expecting an empty result because gitpython is broken as f**k self.assertEqual(diff, "") # self.assertEqual(diff, "@@ -0,0 +1 @@\n+Content in first file\n") # try with invalid filename diff = Frontend.get_diff(repo, c.get_name_short(), 'invalid') repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) self.assertEqual(diff, "") # try with invalid commit sha diff = Frontend.get_diff(repo, '1234567', 'invalid') repo.refresh_from_db() self.assertEqual(repo.conn_ok, True) self.assertEqual(diff, "") # try with broken repo path (not yet checked out prj_name_short = self.project.name_short self.project.name_short = 'AAA' self.project.save() diff = Frontend.get_diff(repo, initial_sha, 'initial') repo.refresh_from_db() self.assertEqual(repo.conn_ok, False) self.assertEqual(diff, "") self.project.name_short = prj_name_short self.project.save() # delete the key files from the server os.unlink(repo.rsa_priv_path.path) os.unlink(repo.rsa_pub_path.path) # delete the key files locally os.unlink(tmp1.name) os.unlink(tmp2.name) # clean up locally shutil.rmtree(repo_path, ignore_errors=True) shutil.rmtree(repo.get_local_repo_path(), ignore_errors=True)
class ValidatorTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify those element they need to be created in setUp, instead of here cls.user = get_user_model().objects.create_user('test', '*****@*****.**', 'test1234') cls.project = Project(creator=cls.user, name_short='PRJ') cls.project.save() cls.project.developer.add(cls.user) cls.kanbancol = KanbanColumn(project=cls.project, position=4, name='test') cls.kanbancol.save() def setUp(self): self.client.force_login(self.user) # NOTE: this element gets modified by some of those tests, so this shall NOT be created in setUpTestData() self.issue = Issue(project=self.project, due_date='2016-12-16', kanbancol=self.kanbancol, storypoints='3') self.issue.save() self.time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") def test_date_is_present_or_past(self): valid_testobjs = { 'datetime-today': timezone.now(), 'datetime-past': timezone.now() - timedelta(days=1), } for obj in valid_testobjs: date_is_present_or_past(valid_testobjs[obj]) invalid_testobjs = { 'datetime-future': timezone.now() + timedelta(days=1), 'invalid-object': self, } for obj in invalid_testobjs: self.assertRaises(ValidationError, date_is_present_or_past, invalid_testobjs[obj]) def test_logged_time_is_positive(self): valid_testobjs = { 'timedelta-positive': timedelta(days=1), 'timedelta-positive2': timedelta(minutes=1) } for obj in valid_testobjs: logged_time_is_positive(valid_testobjs[obj]) invalid_testobjs = { 'timedelta-negative': timedelta(), 'timedelta-negative2': timedelta(minutes=-1), 'invalid-object': self } for obj in invalid_testobjs: self.assertRaises(ValidationError, logged_time_is_positive, invalid_testobjs[obj]) def test_logtime_time_syntax(self): valid = ['1d3h5m', '1d3h', '1d5m', '1d', '2h15m', '3h', '10m', '1d 3h 5m', '1d 3h', '1d 5m', '2h 15m', '0d0h1m'] invalid = ['asd', '12', '3h5', '1d 5h', '3h1d5m', '5m1h', '3h 1d 5m', '5m 1h', '3hm'] invalid_not_positive = ['0d', '0h', '0m', '0d0h0m', '0h0m', '0d0m'] for v in valid: response = self.client.post(reverse('timelog:loginfo'), {'time': v, 'created_at': self.time, 'issue': self.issue.id}, follow=True) self.assertNotContains(response, "Invalid time syntax" or "The logged time must be at least one minute") for v in invalid: response = self.client.post(reverse('timelog:loginfo'), {'time': v, 'created_at': self.time, 'issue': self.issue.id}, follow=True) self.assertContains(response, "Invalid time syntax") for v in invalid_not_positive: response = self.client.post(reverse('timelog:loginfo'), {'time': v, 'created_at': self.time, 'issue': self.issue.id}, follow=True) self.assertContains(response, "The logged time must be at least one minute")
class AttachmentTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify those elements they need to be created in setUp, instead of here cls.user = get_user_model().objects.create_user('a', 'b', 'c') cls.user2 = get_user_model().objects.create_user('d', 'e', 'f') def setUp(self): # NOTE: these element gets modified by some of those tests, so they should NOT be created in setUpTestData() self.client.force_login(self.user) self.project = Project(creator=self.user, name_short=proj_short) self.project.save() self.project.manager.add(self.user) self.project.developer.add(self.user) self.column = KanbanColumn(name='Column', position=4, project=self.project) self.column.save() self.issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") self.issue.save() def test_view_and_template(self): # TODO TESTCASE invite_users # use view_and_template() # TODO which views? # - issue:download_attachment # - issue:delete_attachment # - ... pass def test_redirect_to_login_and_login_required(self): self.client.logout() # TODO TESTCASE invite_users # redirect_to_login_and_login_required() # TODO which views? # - issue:download_attachment pass def test_attachments_from_other_issues_of_same_project_invisible(self): # TODO TESTCASE pass def test_upload_attachment_with_get_request_disabled(self): # TODO TESTCASE pass def test_file_size_restriction(self): # verify that the allowed file size (attachment) is actually limited huge_file = TEST_FILE_PATH+'/16mb.txt' f = open(huge_file, "r") file_dict = { "file": f, } response = self.client.post(reverse('issue:detail', kwargs={'project': self.project.name_short, 'sqn_i': self.issue.number}), file_dict) f.close() # TODO TESTCASE execute in try-except block and delete the file in except self.assertContains(response, "The uploaded file exceeds the allowed file size of: ") def test_create_and_download_attachment(self): # create file for uploading filecontent = 'Hello World' temp = tempfile.NamedTemporaryFile(delete=False) temp.write(filecontent.encode()) temp.close() f = File(open(temp.name, 'r')) attachment = Attachment(file=f, creator=self.user, issue=self.issue) attachment.save() f.close() # delete the uploaded file locally os.unlink(temp.name) issue = Issue.objects.get(pk=self.issue.pk) self.assertEqual(self.issue.attachments.count(), 1) attachment = self.issue.attachments.first() self.assertEqual(attachment.creator, self.user) self.assertEqual(attachment.seqnum, 1) self.assertEqual(attachment.issue.nextAttachmentId, 2) response = self.client.get(reverse('issue:download_attachment', kwargs={'project': proj_short, 'sqn_i': 1, 'sqn_a': attachment.seqnum})) self.assertEqual(response.status_code, 200) self.assertEqual("application/octet-stream", response.get('Content-Type')) self.assertEqual(response.resolver_match.func.__name__, AttachmentDownloadView.as_view().__name__) # delete the uploaded file from the server os.unlink(MEDIA_ROOT + '/' + attachment.file.name) def test_attachment_delete(self): # create sample attachment filecontent = 'Hello World' temp = tempfile.NamedTemporaryFile(delete=False) temp.write(filecontent.encode()) temp.close() f = File(open(temp.name, 'r')) attachment = Attachment(file=f, creator=self.user, issue=self.issue) attachment.save() f.close() filePath = attachment.file.path self.assertTrue(os.path.isfile(filePath)) # delete the attachment response = self.client.get(reverse('issue:delete_attachment', kwargs={'project': self.project.name_short, 'sqn_i': self.issue.number, 'sqn_a': attachment.seqnum}), follow=True) self.assertRedirects(response, reverse('issue:detail', kwargs={'project': self.project.name_short, 'sqn_i': self.issue.number})) self.assertFalse(self.issue.attachments.all().exists()) self.assertFalse(os.path.isfile(filePath)) # delete temp file locally os.unlink(temp.name)
def test_issue_kanban_moveleftright(self): self.assertEqual(self.project.kanbancol.count(), 4) issue1 = Issue(title="issue1", project=self.project, kanbancol=self.project.kanbancol.first()) issue1.save() self.assertEqual(issue1.get_left_kCol_for_issue(), -1) self.assertEqual(issue1.get_right_kCol_for_issue(), 1) # move issue to next row and check results issue1.kanbancol = self.project.kanbancol.get(position='1') issue1.save() self.assertEqual(issue1.get_left_kCol_for_issue(), 0) self.assertEqual(issue1.get_right_kCol_for_issue(), 2) # move issue to last row and check results issue1.kanbancol = self.project.kanbancol.get(position='3') issue1.save() self.assertEqual(issue1.get_left_kCol_for_issue(), 2) self.assertEqual(issue1.get_right_kCol_for_issue(), -1) # test view function response = self.client.post( reverse('issue:setkanbancol', kwargs={'project': self.project.name_short}), { 'sqn_k': '0', 'sqn_i': issue1.number, }, follow=True) self.assertRedirects( response, reverse('issue:projList', kwargs={'project': self.project.name_short})) issue1.refresh_from_db() self.assertEqual(issue1.kanbancol.position, 0) # check if error handling works (set col too high) response = self.client.post( reverse('issue:setkanbancol', kwargs={'project': self.project.name_short}), { 'sqn_k': '4', 'sqn_i': issue1.number, }, follow=True) self.assertRedirects( response, reverse('issue:projList', kwargs={'project': self.project.name_short})) self.assertEqual(len(list(response.context['messages'])), 1) issue1.refresh_from_db() self.assertEqual(issue1.kanbancol.position, 0) # check with invalid issue response = self.client.post( reverse('issue:setkanbancol', kwargs={'project': self.project.name_short}), { 'sqn_k': '2', 'sqn_i': issue1.number + 1, }, follow=True) self.assertRedirects( response, reverse('issue:projList', kwargs={'project': self.project.name_short})) self.assertEqual(len(list(response.context['messages'])), 1) issue1.refresh_from_db() self.assertEqual(issue1.kanbancol.position, 0)
class TimelogTest(StaticSeleniumTestCase): def setUp(self): self.user = get_user_model().objects.create_user( 'test', '*****@*****.**', 'test') self.project = Project(creator=self.user, name_short='PRJ') self.project.save() self.project.developer.add(self.user) # NOTE: those elements get modified by some of those tests, so this shall NOT be created in setUpTestData() self.issue = Issue(title='issue title', project=self.project, due_date='2016-12-16', storypoints='3') self.issue.save() self.issue2 = Issue(title='title2', project=self.project, due_date='2016-12-16', storypoints='3') self.issue2.save() self.issue.assignee.add(self.user) # Uses the cookie hack from: # https://stackoverflow.com/questions/22494583/login-with-code-when-using-liveservertestcase-with-django client = Client() client.login(username='******', password='******') self.cookie = client.cookies['sessionid'] self.selenium.get("{}{}".format(self.live_server_url, reverse('timelog:loginfo'))) self.selenium.add_cookie({ 'name': 'sessionid', 'value': self.cookie.value, 'secure': False, 'path': '/' }) self.selenium.refresh() def test_workflow(self): driver = self.selenium driver.get(self.live_server_url + reverse('landing_page:home')) driver.find_element_by_link_text("Projects").click() driver.find_element_by_link_text("Create new project").click() driver.find_element_by_id("id_name").clear() driver.find_element_by_id("id_name").send_keys("Test-Project") driver.find_element_by_id("id_name_short").clear() driver.find_element_by_id("id_name_short").send_keys("TTT") driver.find_element_by_id("id_description").clear() driver.find_element_by_id("id_description").send_keys("Description") driver.find_element_by_id("id_submit_create").click() driver.find_element_by_link_text("Board").click() driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys("Issue" + Keys.RETURN) driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys("Another" + Keys.RETURN) driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys("So funny" + Keys.RETURN) driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys(">2 +10m" + Keys.RETURN) driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys(">3 +1h" + Keys.RETURN) driver.find_element_by_id("expression").clear() driver.find_element_by_id("expression").send_keys(">1 +15m" + Keys.RETURN) driver.find_element_by_link_text("TTT-1").click() self.assertIn("15 Minutes", driver.find_element_by_id("issue_detail_log_1").text) driver.find_element_by_link_text("TTT").click() driver.find_element_by_link_text("TTT-2").click() self.assertIn("10 Minutes", driver.find_element_by_id("issue_detail_log_1").text) driver.find_element_by_link_text("TTT").click() driver.find_element_by_link_text("TTT-3").click() self.assertIn("1 Hour", driver.find_element_by_id("issue_detail_log_1").text) driver.find_element_by_css_selector( "#issue_detail_log_1 > div > a > span.glyphicon.glyphicon-pencil" ).click() driver.find_element_by_id("id_time").clear() driver.find_element_by_id("id_time").send_keys("50m") driver.find_element_by_id("id_submit_save_timelog_change").click() self.assertIn("50 Minutes", driver.find_element_by_id("issue_detail_log_1").text) driver.find_element_by_link_text("Timelogging").click() self.assertEqual( "total: 1 Hour and 15 Minutes", driver.find_element_by_css_selector("li.list-group-item > b").text) driver.find_element_by_id("timelog").click() self.assertIn( "10 Minutes", driver.find_element_by_css_selector( "#log_1 > div.row > div.col-xs-5").text) self.assertIn( "15 Minutes", driver.find_element_by_css_selector( "#log_2 > div.row > div.col-xs-5").text) self.assertIn( "50 Minutes", driver.find_element_by_css_selector( "#log_3 > div.row > div.col-xs-5").text) driver.find_element_by_link_text("Activity").click() driver.find_element_by_link_text("Last Week").click() driver.find_element_by_id("dropdownMenu1").click() driver.find_element_by_id("content").click() driver.find_element_by_id("dropdownMenu1").click() driver.find_element_by_link_text("Activity").click() driver.find_element_by_id("dropdownMenu1").click() driver.find_element_by_xpath( "//div[@id='content']/div/div[2]/div/div/ul/li[2]/a/b").click() driver.find_element_by_id("cal-heatmap-previous").click() driver.find_element_by_id("cal-heatmap-next").click() driver.find_element_by_css_selector("i.caret").click() driver.find_element_by_link_text("Profile").click() self.assertIn( "test", driver.find_element_by_css_selector("h1.page-header").text) driver.find_element_by_id("show_actions").click() def test_reachable_and_elements_exist(self): # TODO TESTCASE # TODO for each site check it is available + check (some) content like the title + check existence of forms # and their form elements by their ids! # TODO timelog:loginfo # TODO project/<proj_pattern>/issue/<issue_sqn>/log pass def test_time_field_required(self): # TODO TESTCASE # TODO timelog:loginfo # TODO project/<proj_pattern>/issue/<issue_sqn>/log pass def test_issue_loglist(self): # TODO project/<proj_pattern>/issue/<issue_sqn>/logs # TODO this should only test if the elements created with models are listed here pass # TODO this should only test if the elements created with models are listed here (s.b.) def test_overview_log_form(self): driver = self.selenium driver.get(self.live_server_url + reverse('timelog:loginfo')) driver.find_element_by_id("id_time").send_keys("2h") self.assertEqual( len(Select(driver.find_element_by_id("id_issue")).options), 2) Select(driver.find_element_by_id("id_issue")).select_by_visible_text( "issue title") driver.find_element_by_css_selector(".save").click() entry = driver.find_element_by_id("log_1") self.assertIn('issue title', entry.text) self.assertIn('2 Hours', entry.text) driver.find_element_by_id("log_edit_link_1").click() driver.find_element_by_id("id_time").clear() driver.find_element_by_id("id_time").send_keys("1h") driver.find_element_by_css_selector(".save").click() driver.get(self.live_server_url + reverse('timelog:loginfo')) entry = driver.find_element_by_id("log_1") self.assertIn('issue title', entry.text) self.assertIn('1 Hour', entry.text) self.issue2.assignee.add(self.user) self.selenium.refresh() self.assertEqual( len(Select(driver.find_element_by_id("id_issue")).options), 3) driver.find_element_by_id("id_time").send_keys("5h") Select(driver.find_element_by_id("id_issue")).select_by_visible_text( "title2") driver.find_element_by_css_selector(".save").click() driver.get('{}{}'.format( self.live_server_url, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': self.issue2.number }))) entry = driver.find_element_by_id("issue_detail_log_1") self.assertIn('5 Hours', entry.text) driver.get(self.live_server_url + reverse('timelog:loginfo')) driver.find_element_by_id("log_delete_link_2").click() driver.find_element_by_id('id_submit_delete').click() driver.get('{}{}'.format( self.live_server_url, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': self.issue2.number }))) self.assertNotIn('5 Hours', driver.page_source) def test_issue_detail_log_form(self): driver = self.selenium t = Timelog(user=self.user, issue=self.issue, time=timedelta(hours=3)) t.save() driver.get('{}{}'.format( self.live_server_url, reverse('issue:detail', kwargs={ 'project': self.project.name_short, 'sqn_i': self.issue.number }))) entry = driver.find_element_by_id("issue_detail_log_1") self.assertIn('3 Hour', entry.text) def test_delete_timelog(self): # TODO TESTCASE pass def test_keep_and_dont_delete_timelog(self): # TODO # TODO TESTCASE pass
def test_start_and_stop_sprint(self): # self.sprint should not have started self.assertNotEqual(self.sprint.seqnum, -1) project = Project(creator=self.user, name_short='APF') project.save() project.manager.add(self.user) n = Sprint.objects.get_new_sprints().count() sprint = Sprint(project=project) sprint.save() self.assertEqual(Sprint.objects.get_new_sprints().count(), (n+1)) self.assertTrue(sprint in Sprint.objects.get_new_sprints()) self.assertFalse(sprint in Sprint.objects.get_current_sprints()) self.assertFalse(sprint in Sprint.objects.get_old_sprints()) self.assertIsNone(project.currentsprint) # columns and issues for testing archiving function kanbancol3 = KanbanColumn.objects.get(name='Todo', project=project) kanbancol3.type = 'Done' kanbancol3.save() kanbancol2 = KanbanColumn.objects.get(name='In Progress', project=project) kanbancol1 = KanbanColumn.objects.get(name='Done', project=project) kanbancol1.type = 'ToDo' kanbancol1.save() issue = Issue(title="Test-Issue", kanbancol=kanbancol1, project=project, type="Bug", sprint=sprint) issue.save() issue2 = Issue(title="Test-Issue2", kanbancol=kanbancol2, project=project, type="Bug", sprint=sprint) issue2.save() issue3 = Issue(title="Test-Issue3", kanbancol=kanbancol3, project=project, type="Bug", sprint=sprint) issue3.save() issue4 = Issue(title="Test-Issue4", kanbancol=kanbancol3, project=project, type="Bug") issue4.save() # issues (except issue4) should be in not started sprint self.assertFalse(issue in Issue.objects.without_sprint()) self.assertFalse(issue in Issue.objects.current_sprint()) self.assertFalse(issue in Issue.objects.archived()) self.assertFalse(project.has_active_sprint()) # start sprint response = self.client.post(reverse('sprint:startsprint', kwargs={'project': project.name_short, 'sqn_s': sprint.seqnum})) self.assertRedirects(response, reverse('backlog:backlog', kwargs={'project': project.name_short, 'sqn_s': sprint.seqnum})) sprint.refresh_from_db() project.refresh_from_db() # sprint should be current sprint self.assertIsNotNone(project.currentsprint) self.assertEqual(project.currentsprint.seqnum, sprint.seqnum) self.assertTrue(project.has_active_sprint()) self.assertFalse(sprint in Sprint.objects.get_new_sprints()) self.assertTrue(sprint in Sprint.objects.get_current_sprints()) self.assertFalse(sprint in Sprint.objects.get_old_sprints()) # no issues archived until now self.assertFalse(issue in Issue.objects.without_sprint()) self.assertTrue(issue in Issue.objects.current_sprint()) self.assertFalse(issue in Issue.objects.archived()) self.assertFalse(issue2 in Issue.objects.archived()) self.assertFalse(issue3 in Issue.objects.archived()) self.assertFalse(issue4 in Issue.objects.archived()) # set sprint inactive(and archive) response = self.client.post(reverse('sprint:stopsprint', kwargs={'project': project.name_short, 'sqn_s': sprint.seqnum}), {'sprint': 'new', 'move_to_new_sprint': []}, follow=True) self.assertRedirects(response, reverse('backlog:backlog', kwargs={'project': project.name_short})) sprint.refresh_from_db() project.refresh_from_db() # sprint should no more be current sprint. not-done-issues back in backlog (not archived, without sprint) self.assertFalse(sprint in Sprint.objects.get_new_sprints()) self.assertFalse(sprint in Sprint.objects.get_current_sprints()) self.assertTrue(sprint in Sprint.objects.get_old_sprints()) self.assertIsNone(project.currentsprint) self.assertFalse(project.has_active_sprint()) self.assertTrue(issue in Issue.objects.without_sprint()) self.assertFalse(issue in Issue.objects.current_sprint()) # issue3 is archived because of column type and finished sprint, # issue (wrong type), issue2 (wrong type) and 4 (wrong sprint) arent self.assertFalse(issue in Issue.objects.archived()) self.assertFalse(issue2 in Issue.objects.archived()) self.assertTrue(issue3 in Issue.objects.archived()) self.assertFalse(issue4 in Issue.objects.archived()) # issue and issue4 are in same column, both without sprint issue4.kanbancol = kanbancol1 issue4.save() # archive column of issue and issue4 values = {'pos_c': kanbancol1.position} response = self.client.post(reverse('issue:archivecol', kwargs={'project': project.name_short}), values) self.assertRedirects(response, reverse('sprint:sprintboard', kwargs={'project': project.name_short})) issue4.refresh_from_db() self.assertTrue(issue in Issue.objects.archived()) # archived by archivecol self.assertFalse(issue2 in Issue.objects.archived()) # not archived wrong type self.assertTrue(issue3 in Issue.objects.archived()) # archived by stopsprint self.assertTrue(issue4 in Issue.objects.archived()) # archived by archivecol
class ApiPermissionTest(APITestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify those elements they need to be created in setUp(), instead of here cls.user1 = get_user_model().objects.create_user('user1', 'mail', 'c') cls.user2 = get_user_model().objects.create_user('user2', 'othermail', 'c') def setUp(self): # NOTE: these elements get modified by some testcases, so they should NOT be created in setUpTestData() self.project = Project(creator=self.user1, name_short='asdf') self.project.save() self.project.developer.add(self.user1) self.project.developer.add(self.user2) self.project.manager.add(self.user1) self.project2 = Project(creator=self.user2, name_short='asdg') self.project2.save() self.project2.manager.add(self.user2) self.issue = Issue(title='test', project=self.project) self.issue.save() self.issue2 = Issue(title='test', project=self.project2) self.issue2.save() self.comment = Comment(text='test', creator=self.user1, issue=self.issue) self.comment.save() self.comment2 = Comment(text='test', creator=self.user2, issue=self.issue2) self.comment2.save() self.log = Timelog(time=datetime.timedelta(hours=2), user=self.user1, issue=self.issue) self.log.save() self.log2 = Timelog(time=datetime.timedelta(hours=2), user=self.user2, issue=self.issue2) self.log2.save() self.user1_auth = 'Basic ' + base64.b64encode('user1:c'.encode()).decode() self.user2_auth = 'Basic ' + base64.b64encode('user2:c'.encode()).decode() self.client.credentials(HTTP_AUTHORIZATION=self.user1_auth) def try_project_put_patch_delete(self, expected): project_data = { 'name': 'yoflow', } response = self.client.patch(reverse('api:project-detail', kwargs={'name_short': self.project.name_short}), data=project_data) self.assertEqual(response.status_code, expected) response = self.client.put(reverse('api:project-detail', kwargs={'name_short': self.project.name_short}), data=project_data) self.assertEqual(response.status_code, expected) response = self.client.delete(reverse('api:project-detail', kwargs={'name_short': self.project.name_short})) self.assertEqual(response.status_code, expected) def test_project_permissions(self): # project1 granted response = self.client.get(reverse('api:project-detail', kwargs={'name_short': self.project.name_short})) self.assertEqual(response.status_code, status.HTTP_200_OK) # project2 denied for user1 response = self.client.get(reverse('api:project-detail', kwargs={'name_short': self.project2.name_short})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # project2 granted for user2 self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) response = self.client.get(reverse('api:project-detail', kwargs={'name_short': self.project2.name_short})) self.assertEqual(response.status_code, status.HTTP_200_OK) # project1 user2 put, patch, delete denied (no manager) self.try_project_put_patch_delete(status.HTTP_403_FORBIDDEN) # no credentials self.client.credentials() self.try_project_put_patch_delete(status.HTTP_401_UNAUTHORIZED) def test_project_issues_get_post_permissions(self): issue_data = { 'title': 'laalalla' } # project1 granted response = self.client.get(reverse('api:project_issues-list', kwargs={'project': self.project.name_short})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues-list', kwargs={'project': self.project.name_short}), issue_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # project2 denied for user1 response = self.client.get(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) response = self.client.post(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short}), issue_data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # project2 granted for user2 self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) response = self.client.get(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short}), issue_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # no credentials self.client.credentials() response = self.client.get(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short})) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) response = self.client.post(reverse('api:project_issues-list', kwargs={'project': self.project2.name_short}), issue_data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def try_issue_put_patch_delete(self, expected): issue_data = { 'title': 'yoflow', } url = 'api:project_issues-detail' response = self.client.get(reverse(url, kwargs={'project': self.project2.name_short, 'number': self.issue2.number})) self.assertIn(response.status_code, expected) response = self.client.patch(reverse(url, kwargs={'project': self.project2.name_short, 'number': self.issue2.number}), data=issue_data) self.assertIn(response.status_code, expected) response = self.client.put(reverse(url, kwargs={'project': self.project2.name_short, 'number': self.issue2.number}), data=issue_data) self.assertIn(response.status_code, expected) response = self.client.delete(reverse(url, kwargs={'project': self.project2.name_short, 'number': self.issue2.number})) self.assertIn(response.status_code, expected) def test_project_issues_detail_put_patch_delete_permissions(self): # user1 not in project1 self.try_issue_put_patch_delete([status.HTTP_403_FORBIDDEN]) # user2 in project2 self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) self.try_issue_put_patch_delete([status.HTTP_200_OK, status.HTTP_204_NO_CONTENT]) # not credentials self.client.credentials() self.try_issue_put_patch_delete([status.HTTP_401_UNAUTHORIZED]) def test_project_issue_comments_get_post_permissions(self): comment_data = { 'text': 'laalalla' } # project1 granted response = self.client.get(reverse('api:project_issues_comments-list', kwargs={'project': self.project.name_short, 'issue': self.issue.number})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues_comments-list', kwargs={'project': self.project.name_short, 'issue': self.issue.number}), comment_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # project2 denied for user1 response = self.client.get(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) response = self.client.post(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), comment_data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # project2 granted for user2 self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) response = self.client.get(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), comment_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # no credentials self.client.credentials() response = self.client.get(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) response = self.client.post(reverse('api:project_issues_comments-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), comment_data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def try_issue_comments_put_patch_delete(self, expected): comment_data = { 'text': 'lalala', } url = 'api:project_issues_comments-detail' response = self.client.get(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'seqnum': self.comment2.seqnum})) self.assertIn(response.status_code, expected) response = self.client.patch(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'seqnum': self.comment2.seqnum}), data=comment_data) self.assertIn(response.status_code, expected) response = self.client.put(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'seqnum': self.comment2.seqnum}), data=comment_data) self.assertIn(response.status_code, expected) response = self.client.delete(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'seqnum': self.comment2.seqnum})) self.assertIn(response.status_code, expected) def test_project_issues_comments_detail_put_patch_delete_permissions(self): # TODO TESTCASE difference manager/not manager # user1 not in project1 self.try_issue_comments_put_patch_delete([status.HTTP_403_FORBIDDEN]) self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) # user2 in project2 self.try_issue_comments_put_patch_delete([status.HTTP_200_OK, status.HTTP_204_NO_CONTENT]) self.client.credentials() self.try_issue_comments_put_patch_delete([status.HTTP_401_UNAUTHORIZED]) def test_project_issue_timelogs_get_post_permissions(self): log_data = { 'time': '2h5m' } # project1 granted response = self.client.get(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project.name_short, 'issue': self.issue.number})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project.name_short, 'issue': self.issue.number}), log_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # project2 denied for user1 response = self.client.get(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) response = self.client.post(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), log_data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # project2 granted for user2 self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) response = self.client.get(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.post(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), log_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # no credentials self.client.credentials() response = self.client.get(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number})) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) response = self.client.post(reverse('api:project_issues_timelogs-list', kwargs={'project': self.project2.name_short, 'issue': self.issue2.number}), log_data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # helper function def try_issue_timelogs_put_patch_delete(self, expected): log_data = { 'time': '2h5m' } url = 'api:project_issues_timelogs-detail' response = self.client.get(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'number': self.log2.number})) self.assertIn(response.status_code, expected) response = self.client.patch(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'number': self.log2.number}), data=log_data) self.assertIn(response.status_code, expected) response = self.client.put(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'number': self.log2.number}), data=log_data) self.assertIn(response.status_code, expected) response = self.client.delete(reverse(url, kwargs={'project': self.project2.name_short, 'issue': self.issue2.number, 'number': self.log2.number})) self.assertIn(response.status_code, expected) def test_project_issues_timelogs_detail_put_patch_delete_permissions(self): # TODO TESTCASE difference manager/not manager # user1 not in project1 self.try_issue_timelogs_put_patch_delete([status.HTTP_403_FORBIDDEN]) self.client.credentials(HTTP_AUTHORIZATION=self.user2_auth) # user2 in project2 self.try_issue_timelogs_put_patch_delete([status.HTTP_200_OK, status.HTTP_204_NO_CONTENT]) self.client.credentials() self.try_issue_timelogs_put_patch_delete([status.HTTP_401_UNAUTHORIZED])
class IssueSearchTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify those elements they need to be created in setUp, instead of here cls.user = get_user_model().objects.create_user('a', 'b', 'c') def setUp(self): self.project = Project(creator=self.user, name_short='PRJ') self.project.save() self.project.manager.add(self.user) # create issue, comment and attachment self.issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.project.kanbancol.first(), type="Bug") self.issue.save() self.comment = Comment(issue=self.issue, creator=self.user) self.comment.save() self.attachment = Attachment(issue=self.issue, creator=self.user) self.attachment.save() def test_check_credentials(self): for i in [self.project, self.issue, self.comment, self.attachment]: self.assertEqual( getattr(i, 'user_has_read_permissions')(self.user), 1) self.assertEqual( getattr(i, 'user_has_write_permissions')(self.user), 1) # remove write permissions user2 = get_user_model().objects.create_user('d', 'e', 'f') self.project.developer.add(self.user) self.project.manager.remove(self.user) self.comment.creator = user2 self.comment.save() self.attachment.creator = user2 self.attachment.save() # issue and project do distinguish between read and write permissions for i in [self.project, self.issue]: self.assertEqual( getattr(i, 'user_has_read_permissions')(self.user), 1) self.assertEqual( getattr(i, 'user_has_write_permissions')(self.user), 0) for i in [self.comment, self.attachment]: self.assertEqual( getattr(i, 'user_has_read_permissions')(self.user), 1) self.assertEqual( getattr(i, 'user_has_write_permissions')(self.user), 0) # remove read permissions self.project.developer.remove(self.user) for i in [self.project, self.issue, self.comment, self.attachment]: self.assertEqual( getattr(i, 'user_has_read_permissions')(self.user), 0) self.assertEqual( getattr(i, 'user_has_write_permissions')(self.user), 0)
def test_assign_tag_view(self): issue = Issue(title="Test-Issue", project=self.project, kanbancol=self.column, type="Bug") issue.save() t0 = 'first_tag' tag0 = Tag(tag_text=t0, project=self.project) tag0.save() t1 = 'second_tag' tag1 = Tag(tag_text=t1, project=self.project) tag1.save() t2 = 'third_tag' tag2 = Tag(tag_text=t2, project=self.project) tag2.save() values = { # first_tag 'tags': (1) } response = self.client.post( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': 1 }), values) # please don't ask me why the following (which would be a way better assert) doesn't work # self.assertContains(response, t0) self.assertIn(t0, str(response.content)) self.assertEqual(Issue.objects.count(), 1) # TODO """ self.assertEqual(issue.tags.count(), 1) self.assertEqual(issue.tags.first().text, t0) """ response = self.client.get( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': 1 })) return # TODO TODO TODO the following doesn't work because t0 doesn't appear in the response anymore self.assertContains(response, t0) # self.assertIn(t0, str(response.content)) values = { # 'tags': [t2, t1], 'tags': [(3), (2)], } response = self.client.post( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': 1 }), values) self.assertContains(response, t0) self.assertContains(response, t2) self.assertContains(response, t1) response = self.client.get( reverse('issue:edit', kwargs={ 'project': self.project.name_short, 'sqn_i': 1 })) self.assertContains(response, t0) self.assertContains(response, t2) self.assertContains(response, t1)
class MailNotificationTest(TestCase): @classmethod def setUpTestData(cls): # NOTE: if you modify these elements they need to be created in setUp(), instead of here cls.user1 = get_user_model().objects.create_user('user1', '', 'c') cls.user2 = get_user_model().objects.create_user('user2', 'othermail', 'c') def setUp(self): # NOTE: these elements get modified by some testcases, so they should NOT be created in setUpTestData() # notify user2 on new issues self.user2.set_preference("notify_mail", json.dumps({'PRJ': ['NewIssue']})) self.project = Project(creator=self.user1, name_short='PRJ', name='Project') self.project.save() self.project.developer.add(self.user1) self.project.developer.add(self.user2) self.project.manager.add(self.user1) self.issue1 = Issue(creator=self.user1, title='test', project=self.project) self.issue1.save() def test_workflow(self): self.assertEqual(len(Notification.objects.all()), 1) # mail to user2 should have been sent # TODO TESTCASE temporarily disabled due to failing build on jenkins # self.assertEqual(len(outbox), 1) # rcvmail = outbox[0] # self.assertEqual(rcvmail.subject, "[Iguana] Notification from project \"Project\"") # self.assertEqual(rcvmail.to, ['othermail']) # self.assertIn("Someone created issue \"test\".", rcvmail.body) # self.assertIn("If you want to see it, visit https://" + HOST + self.issue1.get_absolute_url(), rcvmail.body) def test_task(self): # test invalid pk self.assertEqual(send_discussion_mail(4711), 1) # test empty mail address noti = Notification(user=self.user1, issue=self.issue1) noti.save() ntype = Notitype(type='NewIssue') ntype.save() noti.type.add(ntype) self.assertEqual(send_discussion_mail(noti.pk), 2) # test no notification necessary self.user1.email = 'mail' self.user1.save() self.assertEqual(send_discussion_mail(noti.pk), 3) self.user1.set_preference('notify_mail', json.dumps({'PRJ': ['Mention', 'NewComment']})) self.assertEqual(send_discussion_mail(noti.pk), 3) # test mail send self.user1.set_preference('notify_mail', json.dumps({'PRJ': ['Mention', 'NewComment', 'NewIssue']})) self.assertEqual(send_discussion_mail(noti.pk), 0) # assert that last mail sent goes to user1 self.assertEqual(mail.outbox[len(mail.outbox)-1].to, ['mail']) def test_leave_project(self): self.client.force_login(self.user2) self.user2.set_preference('notify_mail', json.dumps({'PRJ': ['Mention', 'NewComment', 'NewIssue']})) self.user1.set_preference('notify_mail', json.dumps({'PRJ': ['Mention', 'NewComment', 'NewIssue']})) props = json.dumps(self.user2.get_preference('notify_mail')) self.assertIn('PRJ', props) props = json.dumps(self.user1.get_preference('notify_mail')) self.assertIn('PRJ', props) response = self.client.post(reverse('project:leave', kwargs={'project': 'PRJ'}), {'delete': 'true'}, follow=True) self.assertEqual(response.status_code, 200) self.assertNotIn(self.user2, self.project.manager.all()) props = json.dumps(self.user2.get_preference('notify_mail')) self.assertNotIn('PRJ', props) props = json.dumps(self.user1.get_preference('notify_mail')) self.assertIn('PRJ', props)