diff options
Diffstat (limited to 'spec')
51 files changed, 1351 insertions, 423 deletions
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 5d9403c23ac..b4a22a46b51 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -900,5 +900,37 @@ describe Projects::IssuesController do expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note]) end + + context 'with cross-reference system note', :request_store do + let(:new_issue) { create(:issue) } + let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } + + before do + create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference) + end + + it 'filters notes that the user should not see' do + get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + + expect(JSON.parse(response.body).count).to eq(1) + end + + it 'does not result in N+1 queries' do + # Instantiate the controller variables to ensure QueryRecorder has an accurate base count + get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + + RequestStore.clear! + + control_count = ActiveRecord::QueryRecorder.new do + get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + end.count + + RequestStore.clear! + + create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) + + expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count) + end + end end end diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 9261acda9dc..7437c469a72 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -159,7 +159,7 @@ feature 'Issues > User uses quick actions', js: true do describe 'move the issue to another project' do let(:issue) { create(:issue, project: project) } - context 'when the project is valid', js: true do + context 'when the project is valid' do let(:target_project) { create(:project, :public) } before do @@ -180,7 +180,7 @@ feature 'Issues > User uses quick actions', js: true do end end - context 'when the project is valid but the user not authorized', js: true do + context 'when the project is valid but the user not authorized' do let(:project_unauthorized) {create(:project, :public)} before do @@ -196,7 +196,7 @@ feature 'Issues > User uses quick actions', js: true do end end - context 'when the project is invalid', js: true do + context 'when the project is invalid' do before do sign_in(user) visit project_issue_path(project, issue) @@ -210,7 +210,7 @@ feature 'Issues > User uses quick actions', js: true do end end - context 'when the user issues multiple commands', js: true do + context 'when the user issues multiple commands' do let(:target_project) { create(:project, :public) } let(:milestone) { create(:milestone, title: '1.0', project: project) } let(:target_milestone) { create(:milestone, title: '1.0', project: target_project) } diff --git a/spec/features/profiles/user_visits_profile_account_page_spec.rb b/spec/features/profiles/user_visits_profile_account_page_spec.rb index 8c7233c77ad..a8c08a680d7 100644 --- a/spec/features/profiles/user_visits_profile_account_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_account_page_spec.rb @@ -10,7 +10,6 @@ describe 'User visits the profile account page' do end it 'shows correct menu item' do - expect(find('.sidebar-top-level-items > li.active')).to have_content('Account') - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) + expect(page).to have_active_navigation('Account') end end diff --git a/spec/features/profiles/user_visits_profile_authentication_log_page_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_page_spec.rb deleted file mode 100644 index ffb504cc573..00000000000 --- a/spec/features/profiles/user_visits_profile_authentication_log_page_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe 'User visits the authentication log page' do - let(:user) { create(:user) } - - before do - sign_in(user) - - visit(audit_log_profile_path) - end - - it 'shows correct menu item' do - expect(find('.sidebar-top-level-items > li.active')).to have_content('Authentication log') - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) - end -end diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb new file mode 100644 index 00000000000..a50ebb29e01 --- /dev/null +++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'User visits the authentication log' do + let(:user) { create(:user) } + + before do + sign_in(user) + + visit(audit_log_profile_path) + end + + it 'shows correct menu item' do + expect(page).to have_active_navigation('Authentication log') + end +end diff --git a/spec/features/profiles/user_visits_profile_page_spec.rb b/spec/features/profiles/user_visits_profile_page_spec.rb deleted file mode 100644 index 3bf6d718bc7..00000000000 --- a/spec/features/profiles/user_visits_profile_page_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe 'User visits the profile page' do - let(:user) { create(:user) } - - before do - sign_in(user) - - visit(profile_path) - end - - it 'shows correct menu item' do - expect(find('.sidebar-top-level-items > li.active')).to have_content('Profile') - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) - end -end diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index d1776b3d7c2..924ee0e4174 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -10,8 +10,7 @@ describe 'User visits the profile preferences page' do end it 'shows correct menu item' do - expect(find('.sidebar-top-level-items > li.active')).to have_content('Preferences') - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) + expect(page).to have_active_navigation('Preferences') end describe 'User changes their syntax highlighting theme', :js do diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb new file mode 100644 index 00000000000..6601d3039ed --- /dev/null +++ b/spec/features/profiles/user_visits_profile_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'User visits their profile' do + let(:user) { create(:user) } + + before do + sign_in(user) + + visit(profile_path) + end + + it 'shows correct menu item' do + expect(page).to have_active_navigation('Profile') + end +end diff --git a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb index 0b7a63b54b4..685bf44619d 100644 --- a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb @@ -10,7 +10,6 @@ describe 'User visits the profile SSH keys page' do end it 'shows correct menu item' do - expect(find('.sidebar-top-level-items > li.active')).to have_content('SSH Keys') - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) + expect(page).to have_active_navigation('SSH Keys') end end diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb new file mode 100644 index 00000000000..221f1d7757e --- /dev/null +++ b/spec/features/projects/commit/user_reverts_commit_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'User reverts a commit', :js do + include RepoHelpers + + let(:project) { create(:project, :repository, namespace: user.namespace) } + let(:user) { create(:user) } + + before do + sign_in(user) + + visit(project_commit_path(project, sample_commit.id)) + + find('.header-action-buttons .dropdown').click + find('a[href="#modal-revert-commit"]').click + end + + context 'without creating a new merge request' do + before do + page.within('#modal-revert-commit') do + uncheck('create_merge_request') + click_button('Revert') + end + end + + it 'reverts a commit' do + expect(page).to have_content('The commit has been successfully reverted.') + end + + it 'does not revert a previously reverted commit' do + # Visit the comment again once it was reverted. + visit project_commit_path(project, sample_commit.id) + + find('.header-action-buttons .dropdown').click + find('a[href="#modal-revert-commit"]').click + + page.within('#modal-revert-commit') do + uncheck('create_merge_request') + click_button('Revert') + end + + expect(page).to have_content('Sorry, we cannot revert this commit automatically.') + end + end + + context 'with creating a new merge request' do + it 'reverts a commit' do + page.within('#modal-revert-commit') do + click_button('Revert') + end + + expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.') + expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master") + end + end +end diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb new file mode 100644 index 00000000000..1bd2098af6d --- /dev/null +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'User comments on a snippet', :js do + let(:project) { create(:project) } + let!(:snippet) { create(:project_snippet, project: project, author: user) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_snippet_path(project, snippet)) + end + + it 'leaves a comment on a snippet' do + page.within('.js-main-target-form') do + fill_in('note_note', with: 'Good snippet!') + click_button('Comment') + end + + wait_for_requests + + expect(page).to have_content('Good snippet!') + end +end diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb new file mode 100644 index 00000000000..ca5f7981c33 --- /dev/null +++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'User deletes a snippet' do + let(:project) { create(:project) } + let!(:snippet) { create(:project_snippet, project: project, author: user) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_snippet_path(project, snippet)) + end + + it 'deletes a snippet' do + first(:link, 'Delete').click + + expect(page).not_to have_content(snippet.title) + end +end diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb new file mode 100644 index 00000000000..09a390443cf --- /dev/null +++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'User updates a snippet' do + let(:project) { create(:project) } + let!(:snippet) { create(:project_snippet, project: project, author: user) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_snippet_path(project, snippet)) + end + + it 'updates a snippet' do + page.within('.detail-page-header') do + first(:link, 'Edit').click + end + + fill_in('project_snippet_title', with: 'Snippet new title') + click_button('Save') + + expect(page).to have_content('Snippet new title') + end +end diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb new file mode 100644 index 00000000000..e9992e00ca8 --- /dev/null +++ b/spec/features/projects/snippets/user_views_snippets_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'User views snippets' do + let(:project) { create(:project) } + let!(:project_snippet) { create(:project_snippet, project: project, author: user) } + let!(:snippet) { create(:snippet, author: user) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_snippets_path(project)) + end + + it 'shows snippets' do + expect(page).to have_content(project_snippet.title) + expect(page).not_to have_content(snippet.title) + end +end diff --git a/spec/features/projects/user_archives_project_spec.rb b/spec/features/projects/user_archives_project_spec.rb new file mode 100644 index 00000000000..72063d13c2a --- /dev/null +++ b/spec/features/projects/user_archives_project_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'User archives a project' do + let(:user) { create(:user) } + + before do + project.add_master(user) + + sign_in(user) + end + + context 'when a project is archived' do + let(:project) { create(:project, :archived, namespace: user.namespace) } + + before do + visit(edit_project_path(project)) + end + + it 'unarchives a project' do + expect(page).to have_content('Unarchive project') + + click_link('Unarchive') + + expect(page).not_to have_content('Archived project') + end + end + + context 'when a project is unarchived' do + let(:project) { create(:project, :repository, namespace: user.namespace) } + + before do + visit(edit_project_path(project)) + end + + it 'archives a project' do + expect(page).to have_content('Archive project') + + click_link('Archive') + + expect(page).to have_content('Archived') + end + end +end diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb new file mode 100644 index 00000000000..0ed797a62ea --- /dev/null +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe 'User searches for code' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository, namespace: user.namespace) } + + context 'when signed in' do + before do + project.add_master(user) + sign_in(user) + end + + it 'finds a file' do + visit(project_path(project)) + + page.within('.search') do + fill_in('search', with: 'application.js') + click_button('Go') + end + + click_link('Code') + + expect(page).to have_selector('.file-content .code') + expect(page).to have_selector("span.line[lang='javascript']") + end + + context 'when on a project page', :js do + before do + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds code' do + find('.js-search-project-dropdown').trigger('click') + + page.within('.project-filter') do + click_link(project.name_with_namespace) + end + + fill_in('dashboard_search', with: 'rspec') + find('.btn-search').trigger('click') + + page.within('.results') do + expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions') + end + end + end + end + + context 'when signed out' do + let(:project) { create(:project, :public, :repository) } + + before do + visit(project_path(project)) + end + + it 'finds code' do + fill_in('search', with: 'rspec') + click_button('Go') + + page.within('.results') do + expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions') + end + end + end +end diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb new file mode 100644 index 00000000000..c7c469a262c --- /dev/null +++ b/spec/features/search/user_searches_for_comments_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'User searches for comments' do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + project.add_reporter(user) + sign_in(user) + + visit(project_path(project)) + end + + context 'when a comment is in commits' do + context 'when comment belongs to an invalid commit' do + let(:comment) { create(:note_on_commit, author: user, project: project, commit_id: 12345678, note: 'Bug here') } + + it 'finds a commit' do + page.within('.search') do + fill_in('search', with: comment.note) + click_button('Go') + end + + click_link('Comments') + + expect(page).to have_text('Commit deleted') + expect(page).to have_text('12345678') + end + end + end + + context 'when a comment is in a snippet' do + let(:snippet) { create(:project_snippet, :private, project: project, author: user, title: 'Some title') } + let(:comment) { create(:note, noteable: snippet, author: user, note: 'Supercalifragilisticexpialidocious', project: project) } + + it 'finds a snippet' do + page.within('.search') do + fill_in('search', with: comment.note) + click_button('Go') + end + + click_link('Comments') + + expect(page).to have_link(snippet.title) + end + end +end diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb new file mode 100644 index 00000000000..28cae444588 --- /dev/null +++ b/spec/features/search/user_searches_for_commits_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe 'User searches for commits' do + let(:project) { create(:project, :repository) } + let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } + let(:user) { create(:user) } + + before do + project.add_reporter(user) + sign_in(user) + + visit(search_path(project_id: project.id)) + end + + context 'when searching by SHA' do + it 'finds a commit and redirects to its page' do + fill_in('search', with: sha) + click_button('Search') + + expect(page).to have_current_path(project_commit_path(project, sha)) + end + + it 'finds a commit in uppercase and redirects to its page' do + fill_in('search', with: sha.upcase) + click_button('Search') + + expect(page).to have_current_path(project_commit_path(project, sha)) + end + end + + context 'when searching by message' do + it 'finds a commit and holds on /search page' do + create_commit('Message referencing another sha: "deadbeef"', project, user, 'master') + + fill_in('search', with: 'deadbeef') + click_button('Search') + + expect(page).to have_current_path('/search', only_path: true) + end + + it 'finds multiple commits' do + fill_in('search', with: 'See merge request') + click_button('Search') + click_link('Commits') + + expect(page).to have_selector('.commit-row-description', count: 9) + end + end +end diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb new file mode 100644 index 00000000000..630a81b1c5e --- /dev/null +++ b/spec/features/search/user_searches_for_issues_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe 'User searches for issues', :js do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let!(:issue1) { create(:issue, title: 'Foo', project: project) } + let!(:issue2) { create(:issue, title: 'Bar', project: project) } + + context 'when signed in' do + before do + project.add_master(user) + sign_in(user) + + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds an issue' do + fill_in('dashboard_search', with: issue1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Issues') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title) + end + end + + context 'when on a project page' do + it 'finds an issue' do + find('.js-search-project-dropdown').trigger('click') + + page.within('.project-filter') do + click_link(project.name_with_namespace) + end + + fill_in('dashboard_search', with: issue1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Issues') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title) + end + end + end + end + + context 'when signed out' do + let(:project) { create(:project, :public) } + + before do + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds an issue' do + fill_in('dashboard_search', with: issue1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Issues') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title) + end + end + end +end diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb new file mode 100644 index 00000000000..116256682f4 --- /dev/null +++ b/spec/features/search/user_searches_for_merge_requests_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'User searches for merge requests', :js do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let!(:merge_request1) { create(:merge_request, title: 'Foo', source_project: project, target_project: project) } + let!(:merge_request2) { create(:merge_request, :simple, title: 'Bar', source_project: project, target_project: project) } + + before do + project.add_master(user) + sign_in(user) + + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds a merge request' do + fill_in('dashboard_search', with: merge_request1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Merge requests') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title) + end + end + + context 'when on a project page' do + it 'finds a merge request' do + find('.js-search-project-dropdown').trigger('click') + + page.within('.project-filter') do + click_link(project.name_with_namespace) + end + + fill_in('dashboard_search', with: merge_request1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Merge requests') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title) + end + end + end +end diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb new file mode 100644 index 00000000000..4fa9fe9ce8c --- /dev/null +++ b/spec/features/search/user_searches_for_milestones_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'User searches for milestones', :js do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let!(:milestone1) { create(:milestone, title: 'Foo', project: project) } + let!(:milestone2) { create(:milestone, title: 'Bar', project: project) } + + before do + project.add_master(user) + sign_in(user) + + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds a milestone' do + fill_in('dashboard_search', with: milestone1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Milestones') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title) + end + end + + context 'when on a project page' do + it 'finds a milestone' do + find('.js-search-project-dropdown').trigger('click') + + page.within('.project-filter') do + click_link(project.name_with_namespace) + end + + fill_in('dashboard_search', with: milestone1.title) + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Milestones') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title) + end + end + end +end diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb new file mode 100644 index 00000000000..242e437e41c --- /dev/null +++ b/spec/features/search/user_searches_for_projects_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'User searches for projects' do + let!(:project) { create(:project, :public, name: 'Shop') } + + context 'when signed out' do + include_examples 'top right search form' + + it 'finds a project' do + visit(search_path) + + fill_in('dashboard_search', with: project.name[0..3]) + click_button('Search') + + expect(page).to have_link(project.name) + end + + it 'preserves the group being searched in' do + visit(search_path(group_id: project.namespace.id)) + + fill_in('search', with: 'foo') + click_button('Search') + + expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s) + end + + it 'preserves the project being searched in' do + visit(search_path(project_id: project.id)) + + fill_in('search', with: 'foo') + click_button('Search') + + expect(find('#project_id', visible: false).value).to eq(project.id.to_s) + end + end +end diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb new file mode 100644 index 00000000000..1ea56479ecc --- /dev/null +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'User searches for wiki pages', :js do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'test_wiki', content: 'Some Wiki content' }) } + + before do + project.add_master(user) + sign_in(user) + + visit(search_path) + end + + include_examples 'top right search form' + + it 'finds a page' do + find('.js-search-project-dropdown').trigger('click') + + page.within('.project-filter') do + click_link(project.name_with_namespace) + end + + fill_in('dashboard_search', with: 'content') + find('.btn-search').trigger('click') + + page.within('.search-filter') do + click_link('Wiki') + end + + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(wiki_page.title) + end + end +end diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb new file mode 100644 index 00000000000..5ddea36add5 --- /dev/null +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +describe 'User uses header search field' do + include FilteredSearchHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_reporter(user) + sign_in(user) + + visit(project_path(project)) + end + + it 'starts searching by pressing the enter key', :js do + fill_in('search', with: 'gitlab') + find('#search').native.send_keys(:enter) + + page.within('.breadcrumbs-sub-title') do + expect(page).to have_content('Search') + end + end + + it 'contains location badge' do + expect(page).to have_selector('.has-location-badge') + end + + context 'when clicking the search field', :js do + before do + page.find('#search').click + end + + it 'shows category search dropdown' do + expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i) + end + + context 'when clicking issues' do + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + + it 'shows assigned issues' do + find('.dropdown-menu').click_link('Issues assigned to me') + + expect(page).to have_selector('.filtered-search') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty + end + + it 'shows created issues' do + find('.dropdown-menu').click_link("Issues I've created") + + expect(page).to have_selector('.filtered-search') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty + end + end + + context 'when clicking merge requests' do + let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } + + it 'shows assigned merge requests' do + find('.dropdown-menu').click_link('Merge requests assigned to me') + + expect(page).to have_selector('.merge-requests-holder') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty + end + + it 'shows created merge requests' do + find('.dropdown-menu').click_link("Merge requests I've created") + + expect(page).to have_selector('.merge-requests-holder') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty + end + end + end + + context 'when entering text into the search field', :js do + before do + page.within('.search-input-wrap') do + fill_in('search', with: project.name[0..3]) + end + end + + it 'does not display the category search dropdown' do + expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i) + end + end +end diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb new file mode 100644 index 00000000000..95f3eb5e805 --- /dev/null +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe 'User uses search filters', :js do + let(:group) { create(:group) } + let!(:group_project) { create(:project, group: group) } + let(:project) { create(:project, namespace: user.namespace) } + let(:user) { create(:user) } + + before do + project.add_reporter(user) + group.add_owner(user) + sign_in(user) + + visit(search_path) + end + + context' when filtering by group' do + it 'shows group projects' do + find('.js-search-group-dropdown').trigger('click') + + wait_for_requests + + page.within('.search-holder') do + click_link(group.name) + end + + expect(find('.js-search-group-dropdown')).to have_content(group.name) + + page.within('.project-filter') do + find('.js-search-project-dropdown').trigger('click') + + wait_for_requests + + expect(page).to have_link(group_project.name_with_namespace) + end + end + end + + context' when filtering by project' do + it 'shows a project' do + page.within('.project-filter') do + find('.js-search-project-dropdown').trigger('click') + + wait_for_requests + + click_link(project.name_with_namespace) + end + + expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace) + end + end +end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb deleted file mode 100644 index 8f6d0bb9d1b..00000000000 --- a/spec/features/search_spec.rb +++ /dev/null @@ -1,310 +0,0 @@ -require 'spec_helper' - -describe "Search" do - include FilteredSearchHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } - let!(:issue) { create(:issue, project: project, assignees: [user]) } - let!(:issue2) { create(:issue, project: project, author: user) } - - before do - sign_in(user) - project.team << [user, :reporter] - visit search_path - end - - it 'does not show top right search form' do - expect(page).not_to have_selector('.search') - end - - context 'search filters', js: true do - let(:group) { create(:group) } - let!(:group_project) { create(:project, group: group) } - - before do - group.add_owner(user) - end - - it 'shows group name after filtering' do - find('.js-search-group-dropdown').trigger('click') - wait_for_requests - - page.within '.search-holder' do - click_link group.name - end - - expect(find('.js-search-group-dropdown')).to have_content(group.name) - end - - it 'filters by group projects after filtering by group' do - find('.js-search-group-dropdown').trigger('click') - wait_for_requests - - page.within '.search-holder' do - click_link group.name - end - - expect(find('.js-search-group-dropdown')).to have_content(group.name) - - page.within('.project-filter') do - find('.js-search-project-dropdown').trigger('click') - wait_for_requests - - expect(page).to have_link(group_project.name_with_namespace) - end - end - - it 'shows project name after filtering' do - page.within('.project-filter') do - find('.js-search-project-dropdown').trigger('click') - wait_for_requests - - click_link project.name_with_namespace - end - - expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace) - end - end - - describe 'searching for Projects' do - it 'finds a project' do - page.within '.search-holder' do - fill_in "search", with: project.name[0..3] - click_button "Search" - end - - expect(page).to have_content project.name - end - end - - context 'search for comments' do - context 'when comment belongs to a invalid commit' do - let(:project) { create(:project, :repository) } - let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') } - - before do - note.update_attributes(commit_id: 12345678) - end - - it 'finds comment' do - visit project_path(project) - - page.within '.search' do - fill_in 'search', with: note.note - click_button 'Go' - end - - click_link 'Comments' - - expect(page).to have_text("Commit deleted") - expect(page).to have_text("12345678") - end - end - - it 'finds a snippet' do - snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title') - note = create(:note, - noteable: snippet, - author: user, - note: 'Supercalifragilisticexpialidocious', - project: project) - # Must visit project dashboard since global search won't search - # everything (e.g. comments, snippets, etc.) - visit project_path(project) - - page.within '.search' do - fill_in 'search', with: note.note - click_button 'Go' - end - - click_link 'Comments' - - expect(page).to have_link(snippet.title) - end - - it 'finds a commit' do - project = create(:project, :repository) { |p| p.add_reporter(user) } - visit project_path(project) - - page.within '.search' do - fill_in 'search', with: 'add' - click_button 'Go' - end - - click_link "Commits" - - expect(page).to have_selector('.commit-row-description') - end - - it 'finds a code' do - project = create(:project, :repository) { |p| p.add_reporter(user) } - visit project_path(project) - - page.within '.search' do - fill_in 'search', with: 'application.js' - click_button 'Go' - end - - click_link "Code" - - expect(page).to have_selector('.file-content .code') - - expect(page).to have_selector("span.line[lang='javascript']") - end - end - - describe 'Right header search field' do - it 'allows enter key to search', js: true do - visit project_path(project) - fill_in 'search', with: 'gitlab' - find('#search').native.send_keys(:enter) - - page.within '.breadcrumbs-sub-title' do - expect(page).to have_content 'Search' - end - end - - describe 'Search in project page' do - before do - visit project_path(project) - end - - it 'shows top right search form' do - expect(page).to have_selector('#search') - end - - it 'contains location badge in top right search form' do - expect(page).to have_selector('.has-location-badge') - end - - context 'clicking the search field', js: true do - it 'shows category search dropdown' do - page.find('#search').click - - expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i) - end - end - - context 'click the links in the category search dropdown', js: true do - let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } - - before do - page.find('#search').click - end - - it 'takes user to her issues page when issues assigned is clicked' do - find('.dropdown-menu').click_link 'Issues assigned to me' - - expect(page).to have_selector('.filtered-search') - expect_tokens([assignee_token(user.name)]) - expect_filtered_search_input_empty - end - - it 'takes user to her issues page when issues authored is clicked' do - find('.dropdown-menu').click_link "Issues I've created" - - expect(page).to have_selector('.filtered-search') - expect_tokens([author_token(user.name)]) - expect_filtered_search_input_empty - end - - it 'takes user to her MR page when MR assigned is clicked' do - find('.dropdown-menu').click_link 'Merge requests assigned to me' - - expect(page).to have_selector('.merge-requests-holder') - expect_tokens([assignee_token(user.name)]) - expect_filtered_search_input_empty - end - - it 'takes user to her MR page when MR authored is clicked' do - find('.dropdown-menu').click_link "Merge requests I've created" - - expect(page).to have_selector('.merge-requests-holder') - expect_tokens([author_token(user.name)]) - expect_filtered_search_input_empty - end - end - - context 'entering text into the search field', js: true do - before do - page.within '.search-input-wrap' do - fill_in "search", with: project.name[0..3] - end - end - - it 'does not display the category search dropdown' do - expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i) - end - end - end - end - - describe 'search for commits' do - let(:project) { create(:project, :repository) } - - before do - visit search_path(project_id: project.id) - end - - it 'redirects to commit page when search by sha and only commit found' do - fill_in 'search', with: '6d394385cf567f80a8fd85055db1ab4c5295806f' - - click_button 'Search' - - expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) - end - - it 'redirects to single commit regardless of query case' do - fill_in 'search', with: '6D394385cf' - - click_button 'Search' - - expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) - end - - it 'holds on /search page when the only commit is found by message' do - create_commit('Message referencing another sha: "deadbeef" ', project, user, 'master') - - fill_in 'search', with: 'deadbeef' - click_button 'Search' - - expect(page).to have_current_path('/search', only_path: true) - end - - it 'shows multiple matching commits' do - fill_in 'search', with: 'See merge request' - - click_button 'Search' - click_link 'Commits' - - expect(page).to have_selector('.commit-row-description', count: 9) - end - end - - context 'anonymous user' do - let(:project) { create(:project, :public) } - - before do - sign_out(user) - end - - it 'preserves the group being searched in' do - visit search_path(group_id: project.namespace.id) - - fill_in 'search', with: 'foo' - click_button 'Search' - - expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s) - end - - it 'preserves the project being searched in' do - visit search_path(project_id: project.id) - - fill_in 'search', with: 'foo' - click_button 'Search' - - expect(find('#project_id', visible: false).value).to eq(project.id.to_s) - end - end -end diff --git a/spec/finders/autocomplete_users_finder_spec.rb b/spec/finders/autocomplete_users_finder_spec.rb new file mode 100644 index 00000000000..684af74d750 --- /dev/null +++ b/spec/finders/autocomplete_users_finder_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe AutocompleteUsersFinder do + describe '#execute' do + let!(:user1) { create(:user, username: 'johndoe') } + let!(:user2) { create(:user, :blocked, username: 'notsorandom') } + let!(:external_user) { create(:user, :external) } + let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } + let(:current_user) { create(:user) } + let(:params) { {} } + + let(:project) { nil } + let(:group) { nil } + + subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a } + + context 'when current_user not passed or nil' do + let(:current_user) { nil } + + it { is_expected.to match_array([]) } + end + + context 'when project passed' do + let(:project) { create(:project) } + + it { is_expected.to match_array([project.owner]) } + + context 'when author_id passed' do + let(:params) { { author_id: user2.id } } + + it { is_expected.to match_array([project.owner, user2]) } + end + end + + context 'when group passed and project not passed' do + let(:group) { create(:group, :public) } + + before do + group.add_users([user1], GroupMember::DEVELOPER) + end + + it { is_expected.to match_array([user1]) } + end + + it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } + + context 'when filtered by search' do + let(:params) { { search: 'johndoe' } } + + it { is_expected.to match_array([user1]) } + end + + context 'when filtered by skip_users' do + let(:params) { { skip_users: [omniauth_user.id, current_user.id] } } + + it { is_expected.to match_array([user1, external_user]) } + end + + context 'when todos exist' do + let!(:pending_todo1) { create(:todo, user: current_user, author: user1, state: :pending) } + let!(:pending_todo2) { create(:todo, user: external_user, author: omniauth_user, state: :pending) } + let!(:done_todo1) { create(:todo, user: current_user, author: external_user, state: :done) } + let!(:done_todo2) { create(:todo, user: user1, author: external_user, state: :done) } + + context 'when filtered by todo_filter without todo_state_filter' do + let(:params) { { todo_filter: true } } + + it { is_expected.to match_array([]) } + end + + context 'when filtered by todo_filter with pending todo_state_filter' do + let(:params) { { todo_filter: true, todo_state_filter: 'pending' } } + + it { is_expected.to match_array([user1]) } + end + + context 'when filtered by todo_filter with done todo_state_filter' do + let(:params) { { todo_filter: true, todo_state_filter: 'done' } } + + it { is_expected.to match_array([external_user]) } + end + end + + context 'when filtered by current_user' do + let(:current_user) { user2 } + let(:params) { { current_user: true } } + + it { is_expected.to match_array([user2, user1, external_user, omniauth_user]) } + end + + context 'when filtered by author_id' do + let(:params) { { author_id: user2.id } } + + it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) } + end + end +end diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb index 37cc08b3038..1a78196e33d 100644 --- a/spec/initializers/doorkeeper_spec.rb +++ b/spec/initializers/doorkeeper_spec.rb @@ -9,8 +9,8 @@ describe Doorkeeper.configuration do end describe '#optional_scopes' do - it 'matches Gitlab::Auth::OPTIONAL_SCOPES' do - expect(subject.optional_scopes).to eq Gitlab::Auth::OPTIONAL_SCOPES - Gitlab::Auth::REGISTRY_SCOPES + it 'matches Gitlab::Auth.optional_scopes' do + expect(subject.optional_scopes).to eq Gitlab::Auth.optional_scopes - Gitlab::Auth::REGISTRY_SCOPES end end diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js index f8b37c0edde..f4b4d7980a4 100644 --- a/spec/javascripts/fly_out_nav_spec.js +++ b/spec/javascripts/fly_out_nav_spec.js @@ -271,12 +271,19 @@ describe('Fly out sidebar navigation', () => { }); it('sets transform of sub-items', () => { + const sidebar = document.createElement('div'); const subItems = el.querySelector('.sidebar-sub-level-items'); + + sidebar.style.width = '200px'; + + document.body.appendChild(sidebar); + + setSidebar(sidebar); showSubLevelItems(el); expect( subItems.style.transform, - ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`); + ).toBe(`translate3d(200px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`); }); it('sets is-above when element is above', () => { diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 4f4a27e4c41..af1db2c3455 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -16,20 +16,20 @@ describe Gitlab::Auth do expect(subject::DEFAULT_SCOPES).to eq [:api] end - it 'OPTIONAL_SCOPES contains all non-default scopes' do + it 'optional_scopes contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject::OPTIONAL_SCOPES).to eq %i[read_user read_registry openid] + expect(subject.optional_scopes).to eq %i[read_user read_registry openid] end - context 'REGISTRY_SCOPES' do + context 'registry_scopes' do context 'when registry is disabled' do before do stub_container_registry_config(enabled: false) end it 'is empty' do - expect(subject::REGISTRY_SCOPES).to eq [] + expect(subject.registry_scopes).to eq [] end end @@ -39,7 +39,7 @@ describe Gitlab::Auth do end it 'contains all registry related scopes' do - expect(subject::REGISTRY_SCOPES).to eq %i[read_registry] + expect(subject.registry_scopes).to eq %i[read_registry] end end end diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb new file mode 100644 index 00000000000..5c471cbdeda --- /dev/null +++ b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :migration, schema: 20170907170235 do + let!(:redirect_routes) { table(:redirect_routes) } + let!(:routes) { table(:routes) } + + before do + routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') + routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') + routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') + routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4') + routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5') + + # Valid redirects + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2') + redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3') + + # Conflicting redirects + redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') + end + + it 'deletes the conflicting redirect_routes in the range' do + expect(redirect_routes.count).to eq(8) + + expect do + described_class.new.perform(1, 3) + end.to change { redirect_routes.where("path like 'foo%'").count }.from(5).to(2) + + expect do + described_class.new.perform(4, 5) + end.to change { redirect_routes.where("path like 'foo%'").count }.from(2).to(0) + + expect(redirect_routes.count).to eq(3) + end +end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 1bcdc369c44..3c8350b3aad 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -914,4 +914,126 @@ describe Gitlab::Database::MigrationHelpers do .to raise_error(RuntimeError, /Your database user is not allowed/) end end + + describe '#bulk_queue_background_migration_jobs_by_range', :sidekiq do + context 'when the model has an ID column' do + let!(:id1) { create(:user).id } + let!(:id2) { create(:user).id } + let!(:id3) { create(:user).id } + + before do + User.class_eval do + include EachBatch + end + end + + context 'with enough rows to bulk queue jobs more than once' do + before do + stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1) + end + + it 'queues jobs correctly' do + Sidekiq::Testing.fake! do + model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) + expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) + end + end + + it 'queues jobs in groups of buffer size 1' do + expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id1, id2]]]) + expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id3, id3]]]) + + model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) + end + end + + context 'with not enough rows to bulk queue jobs more than once' do + it 'queues jobs correctly' do + Sidekiq::Testing.fake! do + model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) + expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) + end + end + + it 'queues jobs in bulk all at once (big buffer size)' do + expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id1, id2]], + ['FooJob', [id3, id3]]]) + + model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) + end + end + + context 'without specifying batch_size' do + it 'queues jobs correctly' do + Sidekiq::Testing.fake! do + model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob') + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]]) + end + end + end + end + + context "when the model doesn't have an ID column" do + it 'raises error (for now)' do + expect do + model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob') + end.to raise_error(StandardError, /does not have an ID/) + end + end + end + + describe '#queue_background_migration_jobs_by_range_at_intervals', :sidekiq do + context 'when the model has an ID column' do + let!(:id1) { create(:user).id } + let!(:id2) { create(:user).id } + let!(:id3) { create(:user).id } + + around do |example| + Timecop.freeze { example.run } + end + + before do + User.class_eval do + include EachBatch + end + end + + context 'with batch_size option' do + it 'queues jobs correctly' do + Sidekiq::Testing.fake! do + model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds, batch_size: 2) + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) + expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) + expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f) + end + end + end + + context 'without batch_size option' do + it 'queues jobs correctly' do + Sidekiq::Testing.fake! do + model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds) + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]]) + expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f) + end + end + end + end + + context "when the model doesn't have an ID column" do + it 'raises error (for now)' do + expect do + model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds) + end.to raise_error(StandardError, /does not have an ID/) + end + end + end end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 8beebc10040..4fa30d8df8b 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -1761,17 +1761,9 @@ describe Gitlab::Diff::PositionTracer do let(:merge_commit) do update_file_again_commit - committer = repository.user_to_committer(current_user) - - options = { - message: "Merge branches", - author: committer, - committer: committer - } - merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project) - repository.merge(current_user, merge_request.diff_head_sha, merge_request, options) + repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches") project.commit(branch_name) end diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb index 08010c2d0e2..8dc83a6db7f 100644 --- a/spec/lib/gitlab/group_hierarchy_spec.rb +++ b/spec/lib/gitlab/group_hierarchy_spec.rb @@ -23,6 +23,11 @@ describe Gitlab::GroupHierarchy, :postgresql do expect(relation).to include(parent, child1, child2) end + + it 'does not allow the use of #update_all' do + expect { relation.update_all(share_with_group_lock: false) } + .to raise_error(ActiveRecord::ReadOnlyRecord) + end end describe '#base_and_descendants' do @@ -43,6 +48,11 @@ describe Gitlab::GroupHierarchy, :postgresql do expect(relation).to include(parent, child1, child2) end + + it 'does not allow the use of #update_all' do + expect { relation.update_all(share_with_group_lock: false) } + .to raise_error(ActiveRecord::ReadOnlyRecord) + end end describe '#all_groups' do @@ -73,5 +83,10 @@ describe Gitlab::GroupHierarchy, :postgresql do expect(relation).to include(child2) end + + it 'does not allow the use of #update_all' do + expect { relation.update_all(share_with_group_lock: false) } + .to raise_error(ActiveRecord::ReadOnlyRecord) + end end end diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb new file mode 100644 index 00000000000..1df2477da51 --- /dev/null +++ b/spec/migrations/delete_conflicting_redirect_routes_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170907170235_delete_conflicting_redirect_routes') + +describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do + let!(:redirect_routes) { table(:redirect_routes) } + let!(:routes) { table(:routes) } + + around do |example| + Timecop.freeze { example.run } + end + + before do + stub_const("DeleteConflictingRedirectRoutes::BATCH_SIZE", 2) + stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2) + + routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') + routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') + routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') + routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4') + routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5') + + # Valid redirects + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2') + redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3') + + # Conflicting redirects + redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4') + redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]]) + expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(12.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]]) + expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(24.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]]) + expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(36.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end + + it 'schedules background migrations' do + Sidekiq::Testing.inline! do + expect do + migrate! + end.to change { redirect_routes.count }.from(8).to(3) + end + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 95da97b7bc5..77f0be6b120 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1439,4 +1439,24 @@ describe Ci::Pipeline, :mailer do it_behaves_like 'not sending any notification' end end + + describe '#latest_builds_with_artifacts' do + let!(:pipeline) { create(:ci_pipeline, :success) } + + let!(:build) do + create(:ci_build, :success, :artifacts, pipeline: pipeline) + end + + it 'returns the latest builds' do + expect(pipeline.latest_builds_with_artifacts).to eq([build]) + end + + it 'memoizes the returned relation' do + query_count = ActiveRecord::QueryRecorder + .new { 2.times { pipeline.latest_builds_with_artifacts.to_a } } + .count + + expect(query_count).to eq(1) + end + end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 11e64a0f877..e3cfa149e3a 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -207,11 +207,6 @@ eos context 'of a merge commit' do let(:repository) { project.repository } - let(:commit_options) do - author = repository.user_to_committer(user) - { message: 'Test message', committer: author, author: author } - end - let(:merge_request) do create(:merge_request, source_branch: 'video', @@ -224,7 +219,7 @@ eos merge_commit_id = repository.merge(user, merge_request.diff_head_sha, merge_request, - commit_options) + 'Test message') repository.commit(merge_commit_id) end diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 9c99c3e5c08..fadc8bfeb61 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -140,18 +140,6 @@ describe GpgKey do end end - describe 'notification', :mailer do - let(:user) { create(:user) } - - it 'sends a notification' do - perform_enqueued_jobs do - create(:gpg_key, user: user) - end - - should_email(user) - end - end - describe '#revoke' do it 'invalidates all associated gpg signatures and destroys the key' do gpg_key = create :gpg_key diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 96baeaff0a4..dbc4aba8547 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -169,16 +169,4 @@ describe Key, :mailer do expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key) end end - - describe 'notification' do - let(:user) { create(:user) } - - it 'sends a notification' do - perform_enqueued_jobs do - create(:key, user: user) - end - - should_email(user) - end - end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48fc77423ff..78226c6c3fa 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2682,4 +2682,60 @@ describe Project do end end end + + describe '#latest_successful_builds_for' do + let(:project) { build(:project) } + + before do + allow(project).to receive(:default_branch).and_return('master') + end + + context 'without a ref' do + it 'returns a pipeline for the default branch' do + expect(project) + .to receive(:latest_successful_pipeline_for_default_branch) + + project.latest_successful_pipeline_for + end + end + + context 'with the ref set to the default branch' do + it 'returns a pipeline for the default branch' do + expect(project) + .to receive(:latest_successful_pipeline_for_default_branch) + + project.latest_successful_pipeline_for(project.default_branch) + end + end + + context 'with a ref that is not the default branch' do + it 'returns the latest successful pipeline for the given ref' do + expect(project.pipelines).to receive(:latest_successful_for).with('foo') + + project.latest_successful_pipeline_for('foo') + end + end + end + + describe '#latest_successful_pipeline_for_default_branch' do + let(:project) { build(:project) } + + before do + allow(project).to receive(:default_branch).and_return('master') + end + + it 'memoizes and returns the latest successful pipeline for the default branch' do + pipeline = double(:pipeline) + + expect(project.pipelines).to receive(:latest_successful_for) + .with(project.default_branch) + .and_return(pipeline) + .once + + 2.times do + expect(project.latest_successful_pipeline_for_default_branch) + .to eq(pipeline) + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 53280f2c1cf..60cd7e70055 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -10,10 +10,7 @@ describe Repository, models: true do let(:user) { create(:user) } let(:git_user) { Gitlab::Git::User.from_gitlab(user) } - let(:commit_options) do - author = repository.user_to_committer(user) - { message: 'Test message', committer: author, author: author } - end + let(:message) { 'Test message' } let(:merge_commit) do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) @@ -21,7 +18,7 @@ describe Repository, models: true do merge_commit_id = repository.merge(user, merge_request.diff_head_sha, merge_request, - commit_options) + message) repository.commit(merge_commit_id) end @@ -1287,10 +1284,7 @@ describe Repository, models: true do describe '#merge' do let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) } - let(:commit_options) do - author = repository.user_to_committer(user) - { message: 'Test \r\n\r\n message', committer: author, author: author } - end + let(:message) { 'Test \r\n\r\n message' } it 'merges the code and returns the commit id' do expect(merge_commit).to be_present @@ -1298,19 +1292,19 @@ describe Repository, models: true do end it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do - merge_commit_id = merge(repository, user, merge_request, commit_options) + merge_commit_id = merge(repository, user, merge_request, message) expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) end it 'removes carriage returns from commit message' do - merge_commit_id = merge(repository, user, merge_request, commit_options) + merge_commit_id = merge(repository, user, merge_request, message) - expect(repository.commit(merge_commit_id).message).to eq(commit_options[:message].delete("\r")) + expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r")) end - def merge(repository, user, merge_request, options = {}) - repository.merge(user, merge_request.diff_head_sha, merge_request, options) + def merge(repository, user, merge_request, message) + repository.merge(user, merge_request.diff_head_sha, merge_request, message) end end diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb new file mode 100644 index 00000000000..7a604c0cadd --- /dev/null +++ b/spec/services/deploy_keys/create_service_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe DeployKeys::CreateService do + let(:user) { create(:user) } + let(:params) { attributes_for(:deploy_key) } + + subject { described_class.new(user, params) } + + it "creates a deploy key" do + expect { subject.execute }.to change { DeployKey.where(params.merge(user: user)).count }.by(1) + end +end diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb new file mode 100644 index 00000000000..20382a3a618 --- /dev/null +++ b/spec/services/gpg_keys/create_service_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe GpgKeys::CreateService do + let(:user) { create(:user) } + let(:params) { attributes_for(:gpg_key) } + + subject { described_class.new(user, params) } + + context 'notification', :mailer do + it 'sends a notification' do + perform_enqueued_jobs do + subject.execute + end + should_email(user) + end + end + + it 'creates a gpg key' do + expect { subject.execute }.to change { user.gpg_keys.where(params).count }.by(1) + end +end diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb new file mode 100644 index 00000000000..bcb436c1e46 --- /dev/null +++ b/spec/services/keys/create_service_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Keys::CreateService do + let(:user) { create(:user) } + let(:params) { attributes_for(:key) } + + subject { described_class.new(user, params) } + + context 'notification', :mailer do + it 'sends a notification' do + perform_enqueued_jobs do + subject.execute + end + should_email(user) + end + end + + it 'creates a key' do + expect { subject.execute }.to change { user.keys.where(params).count }.by(1) + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 2af2485eeed..64e676f22a0 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -150,9 +150,7 @@ describe MergeRequests::RefreshService do context 'manual merge of source branch' do before do # Merge master -> feature branch - author = { email: 'test@gitlab.com', time: Time.now, name: "Me" } - commit_options = { message: 'Test message', committer: author, author: author } - @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, commit_options) + @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, 'Test message') commit = @project.repository.commit('feature') service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature') reload_mrs diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 3e493148b32..f4b36eb7eeb 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -84,7 +84,6 @@ describe NotificationService, :mailer do let!(:key) { create(:personal_key, key_options) } it { expect(notification.new_key(key)).to be_truthy } - it { should_email(key.user) } describe 'never emails the ghost user' do let(:key_options) { { user: User.ghost } } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 92cc9a37795..c551083ac90 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -57,6 +57,21 @@ describe Projects::UpdateService, '#execute' do end end end + + context 'When project visibility is higher than parent group' do + let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + before do + project.update(namespace: group, visibility_level: group.visibility_level) + end + + it 'does not update project visibility level' do + result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' }) + expect(project.reload).to be_internal + end + end end describe 'when updating project that has forks' do @@ -148,7 +163,7 @@ describe Projects::UpdateService, '#execute' do result = update_project(project, admin, path: 'existing') expect(result).to include(status: :error) - expect(result[:message]).to match('Project could not be updated!') + expect(result[:message]).to match('There is already a repository with that name on disk') expect(project).not_to be_valid expect(project.errors.messages).to have_key(:base) expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') @@ -159,8 +174,10 @@ describe Projects::UpdateService, '#execute' do it 'returns an error result when record cannot be updated' do result = update_project(project, admin, { name: 'foo&bar' }) - expect(result).to eq({ status: :error, - message: 'Project could not be updated!' }) + expect(result).to eq({ + status: :error, + message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." + }) end end diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb new file mode 100644 index 00000000000..5b6d9c1a4df --- /dev/null +++ b/spec/support/matchers/navigation_matcher.rb @@ -0,0 +1,6 @@ +RSpec::Matchers.define :have_active_navigation do |expected| + match do |page| + expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) + expect(page.find('.sidebar-top-level-items > li.active')).to have_content(expected) + end +end diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb new file mode 100644 index 00000000000..25ebbf011d5 --- /dev/null +++ b/spec/support/shared_examples/features/search_shared_examples.rb @@ -0,0 +1,5 @@ +shared_examples 'top right search form' do + it 'does not show top right search form' do + expect(page).not_to have_selector('.search') + end +end diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index 9695f35bd25..78a2ff73746 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -26,11 +26,9 @@ module StubGitlabCalls end def stub_container_registry_config(registry_settings) + allow(Gitlab.config.registry).to receive_messages(registry_settings) allow(Auth::ContainerRegistryAuthenticationService) .to receive(:full_access_token).and_return('token') - - allow(Gitlab.config.registry).to receive_messages(registry_settings) - load 'lib/gitlab/auth.rb' end def stub_container_registry_tags(repository: :any, tags:) diff --git a/spec/views/shared/milestones/_issuable.html.haml.rb b/spec/views/shared/milestones/_issuable.html.haml.rb new file mode 100644 index 00000000000..0a3f877cae0 --- /dev/null +++ b/spec/views/shared/milestones/_issuable.html.haml.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'shared/milestones/_issuable.html.haml' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:milestone) { create(:milestone, project: project) } + let(:issuable) { create(:issue, project: project, assignees: [user]) } + + before do + assign(:project, project) + assign(:milestone, milestone) + end + + it 'avatar links to issues page' do + render 'shared/milestones/issuable', issuable: issuable, show_project_name: true + + expect(rendered).to have_css("a[href='#{project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id, state: 'all')}']") + end +end |