def test_change_persistence(self): SearchFrontend.query('Project.name_short == "PRJ"', self.user) # we now should have a non-persistent search self.assertEqual(Search.objects.filter(creator=self.user).count(), 1) self.client.force_login(self.user) response = self.client.post(reverse('search:makepersistent'), {'pk': '1'}) self.assertRedirects(response, reverse('search:advanced')) self.assertEqual(Search.objects.filter(creator=self.user, persistent=True).count(), 1) # try using already persistent object (should lead to 404) response = self.client.post(reverse('search:makepersistent'), {'pk': '1'}) self.assertEqual(response.status_code, 404) # should also work for missing pks response = self.client.post(reverse('search:makepersistent'), {'pk': '2'}, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, "Your account doesn't have access to this page.") # test deleting persistent items response = self.client.post(reverse('search:delpersistent'), {'pk': '1'}) self.assertRedirects(response, reverse('search:advanced')) self.assertEqual(Search.objects.filter(creator=self.user).count(), 0) # try deleting non-persistent item (should lead to 404) SearchFrontend.query('Project.name_short == "PRJ"', self.user) self.assertEqual(Search.objects.get(pk='2').persistent, False) response = self.client.post(reverse('search:delpersistent'), {'pk': '2'}) self.assertEqual(response.status_code, 404) # try deleting non-present pk response = self.client.post(reverse('search:delpersistent'), {'pk': '3'}, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, "Your account doesn't have access to this page.")
def test_fulltext_search(self): q = SearchFrontend.query('Issue', self.user) self.assertEqual(len(q), 4) q = SearchFrontend.query('iSSuE', self.user) self.assertEqual(len(q), 4) q = SearchFrontend.query('pro', self.user) self.assertEqual(len(q), 1) q = SearchFrontend.query('pRo', self.user) self.assertEqual(len(q), 1)
def test_search_for_tag(self): tag = Tag(tag_text='Test', project=self.project) tag.save() self.issue.tags.add(tag) result = SearchFrontend.query('test', self.user) self.assertEqual(len(result), 2) result = SearchFrontend.query('Issue.tags.tag_text ~~ "es"', self.user) self.assertEqual(len(result), 1)
def test_share_with_project_0(self): expression = 'User.username ~ "a"' SearchFrontend.query(expression, self.user) # we now should have a non-persistent search self.assertEqual(Search.objects.filter(creator=self.user).count(), 1) self.client.force_login(self.user) response = self.client.post(reverse('search:makepersistent'), {'pk': '1'}) self.assertRedirects(response, reverse('search:advanced')) self.assertEqual(Search.objects.filter(creator=self.user, persistent=True).count(), 1) search = Search.objects.first() search.shared_with.add(self.project) # we are creator of search, modifying should work description = 'new description' response = self.client.post(reverse('search:edit', kwargs={'sqn_sh': '1'}), {'description': description, 'searchexpression': expression}, ) self.assertRedirects(response, reverse('search:advanced')) search.refresh_from_db() self.assertEqual(search.description, description) # try with different creator, should not work search.creator = self.user2 search.save() description = 'another description' response = self.client.post(reverse('search:edit', kwargs={'sqn_sh': '1'}), {'description': description, 'searchexpression': expression}, follow=True, ) self.assertContains(response, "Your account doesn't have access to this page.") search.refresh_from_db() self.assertNotEqual(search.description, description) # make user a manager for the project the search is shared with => should work search.shared_with.add(self.project) self.project.manager.add(self.user) description = 'even another description' response = self.client.post(reverse('search:edit', kwargs={'sqn_sh': '1'}), {'description': description, 'searchexpression': expression}, follow=True, ) self.assertRedirects(response, reverse('search:advanced')) search.refresh_from_db() self.assertEqual(search.description, description)
def post(self, request, *args, **kwargs): # result consists of a list of information about discovered objects: # [title, link] try: result = SearchFrontend.query(request.POST['expression'], self.request.user) except: # this can happen when less than three chars were given to fullext search messages.add_message( request, messages.ERROR, _('Please search for at least three characters')) result = [] # prepare set of types contained in search result typeset = defaultdict(lambda: 0) for title, url, model in result: typeset[model] += 1 # filter result list by object if necessary filterobj = "" if 'filterobj' in request.POST: filterobj = request.POST['filterobj'] result = [x for x in result if x[2] == filterobj] return TemplateResponse( request, self.template_name, { 'qresult': result, 'typeset': sorted(typeset.items()), 'qstring': request.POST['expression'], 'filterobj': filterobj, 'searchable_fields': searchable_fields, 'compare': comp_expressions })
def test_constraints(self): comment = Comment(creator=self.user2, issue=self.issuep2, text="blub, sehr kreativ", when=timezone.now()) comment.save() comment = Comment(creator=self.user, issue=self.issue, text="blub", when=timezone.now()) comment.save() # contains expressions and the expected size of the resulting queryset tests = { 'Project.name_short == "PRJ"': 0, 'Project.issue.title ~~ "Issue"': 1, 'Issue.type == "Bug"': 0, '(Issue.project.name_short ~~ "PR") OR (Issue.title ~~ "Bling")': 1, 'Comment.text ~~ "blub"': 1, 'Comment.text ~ "b.{2}b"': 1, } # use user2 for query this time (has only access to 2nd project) for t in tests: q = SearchFrontend.query(t, self.user2) self.assertEqual(len(q), tests[t]) self.assertEqual(Search.objects.filter(creator=self.user2, searchexpression=t).count(), 1)
def test_share_with_project(self): expression = 'User.username ~ "a"' SearchFrontend.query(expression, self.user) # we now should have a non-persistent search self.assertEqual(Search.objects.filter(creator=self.user).count(), 1) search = Search.objects.first() search.persistent = True search.shared_with.add(self.project) search.save() self.client.force_login(self.user2) response = self.client.post(reverse('project:search', kwargs={'project': 'PRJ'}), follow=True) self.assertContains(response, "Your account doesn't have access to this page.") self.assertEqual(Search.objects.filter(creator=self.user, persistent=True).count(), 1)
def post(self, request, *args, **kwargs): # result consists of a list of information about discovered objects: # [title, link] try: result = SearchFrontend.query(request.POST['expression'], self.request.user) except ValueError: # this can happen when less than three chars were given to full text search messages.add_message(request, messages.ERROR, _('Please search for at least three characters')) result = [] # prepare set of types contained in search result # with/-out project filter typeset = defaultdict(lambda: [0, 0]) # with/-out type filter projects = defaultdict(lambda: [0, 0]) # with/-out type filter not_proj_related = defaultdict(lambda: [0, 0]) for title, url, model, rel_project in result: typeset[model][1] += 1 if FILTER_PROJ not in request.POST or rel_project == request.POST[FILTER_PROJ]: typeset[model][0] += 1 # amount for this project with active type filter if _(NOT_PROJ_RELATED) == rel_project: not_proj_related[rel_project][1] += 1 # amount for this project with active type filter if FILTER_TYPE not in request.POST or model == request.POST[FILTER_TYPE]: not_proj_related[rel_project][0] += 1 else: projects[rel_project][1] += 1 # amount for this project with active type filter if FILTER_TYPE not in request.POST or model == request.POST[FILTER_TYPE]: projects[rel_project][0] += 1 # filter result list by object if necessary filtertype = "" if FILTER_TYPE in request.POST: filtertype = request.POST[FILTER_TYPE] result = [x for x in result if x[2] == filtertype] filterproj = "" if FILTER_PROJ in request.POST: filterproj = request.POST[FILTER_PROJ] result = [x for x in result if x[3] == filterproj] return TemplateResponse(request, self.template_name, {'qresult': result, 'typeset': sorted(typeset.items()), # append the NOT_PROJ_RELATED entry always to the end 'projects': sorted(projects.items())+list(not_proj_related.items()), 'qstring': request.POST['expression'], FILTER_TYPE: filtertype, FILTER_PROJ: filterproj, 'searchable_fields': searchable_fields, 'compare': comp_expressions })
def test_search_for_tag(self): tag1 = Tag(tag_text='Test', project=Project.objects.get(name_short=p0_short)) tag1.save() tag2 = Tag(tag_text='Test-Tag', project=Project.objects.get(name_short=p0_short)) tag2.save() result = SearchFrontend.query('test', self.user) self.assertEqual(len(result), 2) result = SearchFrontend.query('Test-Tag', self.user) self.assertEqual(len(result), 1) # test fieldcheckings result = SearchFrontend.query('Tag.project.name_short ~~ "a"', self.user) self.assertEqual(len(result), 0) # test permission check user = get_user_model().objects.create_user('b', '*****@*****.**', 'b1234567') result = SearchFrontend.query('test', user) self.assertEqual(len(result), 0)
def test_share_with_project(self): expression = 'User.username ~ "a"' SearchFrontend.query(expression, self.user) # we now should have a non-persistent search self.assertEqual(Search.objects.filter(creator=self.user).count(), 1) search = Search.objects.first() search.persistent = True search.shared_with.add(self.project) search.save() self.client.force_login(self.user2) user_doesnt_pass_test_and_gets_404(self, 'project:search', address_kwargs={'project': 'PRJ'}) self.assertEqual( Search.objects.filter(creator=self.user, persistent=True).count(), 1)
def test_change_persistence(self): SearchFrontend.query('Project.name_short == "PRJ"', self.user) # we now should have a non-persistent search self.assertEqual(Search.objects.filter(creator=self.user).count(), 1) self.client.force_login(self.user) response = self.client.post(reverse('search:makepersistent'), {'pk': '1'}) self.assertRedirects(response, reverse('search:advanced')) self.assertEqual( Search.objects.filter(creator=self.user, persistent=True).count(), 1) # try using already persistent object (should lead to 404) response = self.client.post(reverse('search:makepersistent'), {'pk': '1'}) self.assertEqual(response.status_code, 404) # should also work for missing pks user_doesnt_pass_test_and_gets_404(self, 'search:makepersistent', get_kwargs={'pk': '2'}) # test deleting persistent items response = self.client.post(reverse('search:delpersistent'), {'pk': '1'}) self.assertRedirects(response, reverse('search:advanced')) self.assertEqual(Search.objects.filter(creator=self.user).count(), 0) # try deleting non-persistent item (should lead to 404) SearchFrontend.query('Project.name_short == "PRJ"', self.user) self.assertEqual(Search.objects.get(pk='2').persistent, False) response = self.client.post(reverse('search:delpersistent'), {'pk': '2'}) self.assertEqual(response.status_code, 404) # try deleting non-present pk user_doesnt_pass_test_and_gets_404(self, 'search:delpersistent', get_kwargs={'pk': '3'})
def test_permissions(self): expression = '(User.username ~~ "a")' q = SearchFrontend.query(expression, self.user) self.assertEqual(Search.objects.count(), 1) search = Search.objects.first() self.assertEqual(str(search), expression) self.assertEqual(search.user_has_read_permissions(self.user), True) self.assertEqual(search.user_has_write_permissions(self.user), True) self.assertEqual(search.user_has_read_permissions(self.user2), False) self.assertEqual(search.user_has_write_permissions(self.user2), False) # set user2 as creator => user1 should not have read / write permissions search.creator = self.user2 search.save() self.assertEqual(search.user_has_read_permissions(self.user), False) self.assertEqual(search.user_has_write_permissions(self.user), False) # share with project2 => user2 has read but not write permissions search.creator = self.user search.save() search.shared_with.add(self.project2) self.assertEqual(search.user_has_read_permissions(self.user2), True) self.assertEqual(search.user_has_write_permissions(self.user2), False) # share with project => user2 has no read and write permissions search.shared_with.clear() search.shared_with.add(self.project) self.assertEqual(search.user_has_read_permissions(self.user2), False) self.assertEqual(search.user_has_write_permissions(self.user2), False)
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)
def test_new_search(self): driver = self.selenium driver.get('{}{}'.format(self.live_server_url, reverse('landing_page:home'))) driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element( By.NAME, "expression").send_keys("Project.name_short ~~ \"PRJ\"") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertEqual( self.project.name, driver.find_element( By.CSS_SELECTOR, ".list-group > li:nth-child(2) > h4:nth-child(1) > a:nth-child(1)" ).text) self.assertEqual( self.project2.name, driver.find_element( By.CSS_SELECTOR, ".list-group > li:nth-child(1) > h4:nth-child(1) > a:nth-child(1)" ).text) driver.find_element(By.CSS_SELECTOR, "span.caret").click() self.assertIn("Project.name_short ~~ "PRJ"", driver.page_source) driver.find_element(By.CSS_SELECTOR, "#search1 > button.btn.btn-default").click() self.assertEqual( self.project.name, driver.find_element( By.CSS_SELECTOR, ".list-group > li:nth-child(2) > h4:nth-child(1) > a:nth-child(1)" ).text) self.assertEqual( self.project2.name, driver.find_element( By.CSS_SELECTOR, ".list-group > li:nth-child(1) > h4:nth-child(1) > a:nth-child(1)" ).text) driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element( By.NAME, "expression").send_keys("Issue.title ~~ \"Issue for Proj1\"") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertEqual( "(PRJ-2) Another Issue for Proj1", driver.find_element( By.CSS_SELECTOR, ".list-group > li:nth-child(2) > h4:nth-child(1) > a:nth-child(1)" ).text) driver.find_element(By.CSS_SELECTOR, "a.dropdown-toggle").click() self.assertIn("Issue.title ~~ "Issue for Proj1"", driver.page_source) driver.find_element(By.NAME, "expression").clear() driver.find_element( By.NAME, "expression").send_keys("User.username == \"michgibtsnicht\"") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertEqual("No items matching your query found", driver.find_element(By.CSS_SELECTOR, ".alert").text) driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element( By.NAME, "expression").send_keys("Comment.invalidfield ~~ \"b\"") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertEqual("No items matching your query found", driver.find_element(By.CSS_SELECTOR, ".alert").text) driver.find_element(By.CSS_SELECTOR, "span.glyphicon.glyphicon-search").click() driver.find_element(By.NAME, "expression").clear() driver.find_element(By.NAME, "expression").send_keys("()aaa") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertEqual("No items matching your query found", driver.find_element(By.CSS_SELECTOR, ".alert").text) # full-text search driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element(By.NAME, "expression").send_keys("iss") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertIn( "Issue", driver.find_element( By.CSS_SELECTOR, "#filtertype_Issue > button:nth-child(4)").text) self.assertIn( "Project", driver.find_element( By.CSS_SELECTOR, "#filtertype_Project > button:nth-child(4)").text) self.assertIn( "User", driver.find_element(By.CSS_SELECTOR, "#filtertype_User > button:nth-child(4)").text) self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-9 ul").text.split('\n')), 10) driver.find_element(By.CSS_SELECTOR, "#filtertype_Issue > button:nth-child(4)").click() # check that Issue button is now active self.assertEqual( driver.find_element( By.CSS_SELECTOR, "button.list-group-item:nth-child(3)").get_attribute('class'), 'list-group-item active') # check result list lengths for all buttons self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-9 ul").text.split('\n')), 3) driver.find_element( By.CSS_SELECTOR, "#filtertype_Project > button:nth-child(4)").click() self.assertEqual( driver.find_element( By.CSS_SELECTOR, "button.list-group-item:nth-child(3)").get_attribute('class'), 'list-group-item active') self.assertEqual( driver.find_element( By.CSS_SELECTOR, "#filtertype_Issue > button:nth-child(4)").get_attribute( 'class'), 'list-group-item') self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-9 ul").text.split('\n')), 1) driver.find_element(By.CSS_SELECTOR, "#filtertype_User > button:nth-child(4)").click() self.assertEqual( driver.find_element( By.CSS_SELECTOR, "button.list-group-item:nth-child(3)").get_attribute('class'), 'list-group-item active') self.assertEqual( driver.find_element( By.CSS_SELECTOR, "#filtertype_Project > button:nth-child(4)").get_attribute( 'class'), 'list-group-item') self.assertEqual( driver.find_element( By.CSS_SELECTOR, "#filtertype_Issue > button:nth-child(4)").get_attribute( 'class'), 'list-group-item') self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-9 ul").text.split('\n')), 1) # disable currently selected button and check result list size and highlighting driver.find_element(By.CSS_SELECTOR, "button.list-group-item:nth-child(3)").click() self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-9 ul").text.split('\n')), 10) self.assertEqual( driver.find_element( By.CSS_SELECTOR, "#filtertype_User > button:nth-child(4)").get_attribute( 'class'), 'list-group-item') # test empty search string (should fail) driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element(By.NAME, "expression").send_keys("") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertIn('Please search for at least three characters', driver.page_source) # should always fail with less than three chars driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.NAME, "expression").clear() driver.find_element(By.NAME, "expression").send_keys("aa") driver.find_element(By.CSS_SELECTOR, "button.btn.btn-default").click() self.assertIn('Please search for at least three characters', driver.page_source) # test marking search persistent qstring = 'Issue.type == "Bug"' q = SearchFrontend.query(qstring, self.user) self.assertEqual(len(q), 2) driver.find_element(By.CSS_SELECTOR, "span.caret").click() driver.find_element(By.ID, "recent-searches-link").click() driver.find_element(By.CSS_SELECTOR, "button.btn.btn-link").click() driver.find_element( By.CSS_SELECTOR, "div.col-md-6:nth-child(2) > div:nth-child(1) > table:nth-child(2) >" + " tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > a:nth-child(1)" ).click() self.assertEqual( driver.find_element(By.ID, "id_searchexpression").get_attribute("value"), qstring) driver.find_element(By.ID, "id_description").clear() driver.find_element(By.ID, "id_description").send_keys("Fancy filter") self.assertEqual( Search.objects.get(searchexpression=qstring).persistent, True) driver.find_element(By.CSS_SELECTOR, ".select2-selection__rendered").click() time.sleep(1) for i in driver.find_elements(By.CSS_SELECTOR, '#select2-id_shared_with-results li'): if i.text == self.project.name: i.click() break driver.find_element(By.CSS_SELECTOR, ".save").click() driver.find_element(By.LINK_TEXT, "Projects").click() driver.find_element(By.LINK_TEXT, self.project.name).click() driver.find_element(By.LINK_TEXT, "Settings").click() driver.find_element(By.LINK_TEXT, "Search filters").click() elem = driver.find_element(By.CSS_SELECTOR, ".btn-link") self.assertEqual(elem.text, "Fancy filter") elem.click() self.assertEqual( len( driver.find_element(By.CSS_SELECTOR, ".col-md-12 ul").text.split("\n")), 3) # test delete driver.get('{}{}'.format(self.live_server_url, reverse('search:advanced'))) driver.find_element( By.CSS_SELECTOR, "div.col-md-6:nth-child(2) > div:nth-child(1) > " + "table:nth-child(2) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(3) > " + "form:nth-child(1) > button:nth-child(3)").click() self.assertEqual(len(Search.objects.filter(searchexpression=qstring)), 0)
def test_parser(self): user = self.user project = self.project project2 = self.project2 # contains expressions and the expected size of the resulting queryset tests = { 'Project.name_short == "PRJ"': 1, 'Project.issue.title ~~ "Issue"': 2, 'Issue.type == "Bug"': 2, 'Issue.type != "Bug"': 2, 'Issue.number >= 2': 2, 'Issue.number >= 2 LIMIT 1': 1, 'Issue.number >= 2 SORT ASC Issue.number LIMIT 2': 2, 'Issue.number >= 2 LIMIT 2 SORT ASC Issue.number': 2, 'Issue.due_date <= 20051005': 1, '(Project.name_short ~~ "PRJ") AND (Project.issue.title == "Blub-Issue")': 1, '(Issue.project.name_short ~~ "PRJ") AND (Issue.title ~~ "Issue")': 3, '(Issue.project.name_short ~~ "PR") AND (Issue.title ~~ "Issue")': 4, '(Issue.project.name_short ~~ "PR") OR (Issue.title ~~ "Bling")': 4, '(Issue.project.name_short ~~ "PR") OR (Issue.title ~~ "Bling") SORT ASC Issue__number': 4, '(Issue.project.name_short ~~ "PR") OR (Issue.title ~~ "Bling") SORT DESC Issue__number': 4, '(Issue.project.name_short ~~ "PR") OR (Issue.title ~~ "Bling") LIMIT 3': 3, 'Comment.text == "blub"': 0, '(User.username ~~ "a")': 1, } for t in tests: q = SearchFrontend.query(t, user) self.assertEqual(len(q), tests[t]) self.assertEqual(Search.objects.filter(creator=user, searchexpression=t).count(), 1) # check minlength checking self.assertRaises(ValueError, SearchFrontend.query, 'aa', user) # minlength checking must also work when combining # short expressions by AND or OR with valid expressions self.assertRaises(ValueError, SearchFrontend.query, 'Bla AND OR Blub', user) self.assertRaises(ValueError, SearchFrontend.query, 'Bla OR OR Blub', user) self.assertRaises(ValueError, SearchFrontend.query, 'Bla OR AND Blub', user) # check duplicate checking expression = 'Issue.type == "Bug"' q = SearchFrontend.query(expression, user) self.assertEqual(len(q), 2) self.assertEqual(Search.objects.filter(creator=user, searchexpression=expression).count(), 1) # assert that non-persistent searches are removed from model self.assertEqual(Search.objects.filter(creator=user, persistent=False).count(), 10) # expressions that shall not be stored in the database tests = { # fulltext searches 'iss': 4, 'Blu AND iss': 1, 'Blu AND iss OR Bla AND iss OR Bli AND iss': 3, 'Bli AND lin AND sue': 1, 'Bling OR Bla': 2, 'Bli OR Iss AND Bla': 2, # broken regexes 'Comment.text ~ "?{42}.': 0, } for t in tests: q = SearchFrontend.query(t, user) self.assertEqual(len(q), tests[t]) self.assertEqual(Search.objects.filter(creator=user, searchexpression=t).count(), 0) # negative tests tests = [ 'Project.name_short == "PRJ" AND OR Project.namme ~~ "T"', 'Issue.due_date >= 20121340', 'Issue.due_date >= 20100101 SORT Issue.number', 'Issue.due_date >= 20100101 SORT ASC', 'Issue.due_date >= 20100101 LIMIT', 'Project.name <> "PRJ"', 'Project.name <= PRJ', 'Project.name', 'LIMIT 3', 'Project.invalidfield ~~ "blubber"', 'Invalidobject.field ~~ "blubber"', ] for t in tests: self.assertRaises(Exception, parser.compile, t) # set searchable_fields to invalid values and check behavior Project.searchable_fields.append('invalidfield') self.assertRaises(Exception, parser.compile, 'Project.invalidfield ~~ "blubber"') Project.searchable_fields = [] q = SearchFrontend.query('blubber', user) self.assertEqual(len(q), 0) # assert that non-persistent searches are removed from model self.assertEqual(Search.objects.filter(creator=user, persistent=False).count(), 10)
def test_search_for_tag(self): result = SearchFrontend.query('test', self.user) self.assertEqual(len(result), 2) result = SearchFrontend.query('Issue.tags.tag_text ~~ "es"', self.user) self.assertEqual(len(result), 1)