diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2018-05-15 12:01:07 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2018-05-15 12:01:07 +0200 |
commit | bbba6d7e62fb9d8bca635d57604fd503fb3c4645 (patch) | |
tree | 75e6d810370791ba991d007ab67b0ca265ab7ec6 /spec | |
parent | f16f2b599412ed1514ba96d81758b9a2e6fd9c1f (diff) | |
parent | a78b1b27b86d34c00e1b0631e967d637f8a6714b (diff) | |
download | gitlab-ce-bbba6d7e62fb9d8bca635d57604fd503fb3c4645.tar.gz |
Merge branch 'master' into feature/gb/add-regexp-variables-expression
* master: (76 commits)
Conflicts:
lib/gitlab/untrusted_regexp.rb
Diffstat (limited to 'spec')
38 files changed, 1140 insertions, 362 deletions
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index f4c99ea4064..58bb91a0c80 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -51,6 +51,21 @@ describe SendFileUpload do end end + context 'with attachment' do + subject { controller.send_upload(uploader, attachment: 'test.js') } + + it 'sends a file with content-type of text/plain' do + expected_params = { + content_type: 'text/plain', + filename: 'test.js', + disposition: 'attachment' + } + expect(controller).to receive(:send_file).with(uploader.path, expected_params) + + subject + end + end + context 'when remote file is used' do before do stub_uploads_object_storage(uploader: uploader_class) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 2281cb420d9..a08fcea27a5 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -490,43 +490,43 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do id: job.id end - context 'when job has a trace artifact' do + context "when job has a trace artifact" do let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } it 'returns a trace' do response = subject expect(response).to have_gitlab_http_status(:ok) - expect(response.content_type).to eq 'text/plain; charset=utf-8' - expect(response.body).to eq job.job_artifacts_trace.open.read + expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") + expect(response.body).to eq(job.job_artifacts_trace.open.read) end end - context 'when job has a trace file' do + context "when job has a trace file" do let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) } - it 'send a trace file' do + it "send a trace file" do response = subject expect(response).to have_gitlab_http_status(:ok) - expect(response.content_type).to eq 'text/plain; charset=utf-8' - expect(response.body).to eq 'BUILD TRACE' + expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") + expect(response.body).to eq("BUILD TRACE") end end - context 'when job has a trace in database' do + context "when job has a trace in database" do let(:job) { create(:ci_build, pipeline: pipeline) } before do - job.update_column(:trace, 'Sample trace') + job.update_column(:trace, "Sample trace") end - it 'send a trace file' do + it "send a trace file" do response = subject expect(response).to have_gitlab_http_status(:ok) - expect(response.content_type).to eq 'text/plain; charset=utf-8' - expect(response.body).to eq 'Sample trace' + expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") + expect(response.body).to eq("Sample trace") end end diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index 98566f907f9..0430762c1ff 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -4,8 +4,8 @@ FactoryBot.define do name 'test-cluster' trait :project do - after(:create) do |cluster, evaluator| - cluster.projects << create(:project) + before(:create) do |cluster, evaluator| + cluster.projects << create(:project, :repository) end end diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb new file mode 100644 index 00000000000..5ddb5894624 --- /dev/null +++ b/spec/features/groups/members/filter_members_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +feature 'Groups > Members > Filter members' do + let(:user) { create(:user) } + let(:user_with_2fa) { create(:user, :two_factor_via_otp) } + let(:group) { create(:group) } + + background do + group.add_owner(user) + group.add_master(user_with_2fa) + + sign_in(user) + end + + scenario 'shows all members' do + visit_members_list + + expect(first_member).to include(user.name) + expect(second_member).to include(user_with_2fa.name) + expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone') + end + + scenario 'shows only 2FA members' do + visit_members_list(two_factor: 'enabled') + + expect(first_member).to include(user_with_2fa.name) + expect(members_list.size).to eq(1) + expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled') + end + + scenario 'shows only non 2FA members' do + visit_members_list(two_factor: 'disabled') + + expect(first_member).to include(user.name) + expect(members_list.size).to eq(1) + expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled') + end + + def visit_members_list(options = {}) + visit group_group_members_path(group.to_param, options) + end + + def members_list + page.all('ul.content-list > li') + end + + def first_member + members_list.first.text + end + + def second_member + members_list.last.text + end +end diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index 8af4b157cd8..9613e22bf24 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -10,6 +10,7 @@ describe "Internal references", :js do let(:public_project_user) { public_project.owner } let(:public_project) { create(:project, :public, :repository) } let(:public_project_issue) { create(:issue, project: public_project) } + let(:public_project_merge_request) { create(:merge_request, source_project: public_project) } context "when referencing to open issue" do context "from private project" do @@ -77,4 +78,63 @@ describe "Internal references", :js do end end end + + context "when referencing to open merge request" do + context "from private project" do + context "from issue" do + before do + sign_in(private_project_user) + + visit(project_issue_path(private_project, private_project_issue)) + + add_note("##{public_project_merge_request.to_reference(private_project)}") + end + + context "when user doesn't have access to private project" do + before do + sign_in(public_project_user) + + visit(project_merge_request_path(public_project, public_project_merge_request)) + end + + it { expect(page).not_to have_css(".note") } + end + end + + context "from merge request" do + before do + sign_in(private_project_user) + + visit(project_merge_request_path(private_project, private_project_merge_request)) + + add_note("##{public_project_merge_request.to_reference(private_project)}") + end + + context "when user doesn't have access to private project" do + before do + sign_in(public_project_user) + + visit(project_merge_request_path(public_project, public_project_merge_request)) + end + + it "doesn't show any references" do + page.within(".merge-request-details") do + expect(page).not_to have_content("#merge-requests .merge-requests-title") + end + end + end + + context "when user has access to private project" do + before do + visit(project_merge_request_path(public_project, public_project_merge_request)) + end + + it "shows references" do + expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}") + .and have_content(private_project_user.name) + end + end + end + end + end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index fe334b531f0..a8a627d8806 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -182,6 +182,7 @@ feature 'Gcp Cluster', :js do it 'user sees a login page' do expect(page).to have_css('.signin-with-google') + expect(page).to have_link('Google account') end end diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb new file mode 100644 index 00000000000..6397df086a7 --- /dev/null +++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb @@ -0,0 +1,170 @@ +require "spec_helper" + +describe "User adds a comment on a commit", :js do + include Spec::Support::Helpers::Features::NotesHelpers + include RepoHelpers + + let(:comment_text) { "XML attached" } + let(:another_comment_text) { "SVG attached" } + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.add_developer(user) + end + + context "inline view" do + before do + visit(project_commit_path(project, sample_commit.id)) + end + + it "adds a comment" do + page.within(".js-main-target-form") do + expect(page).not_to have_link("Cancel") + + emoji = ":+1:" + + fill_in("note[note]", with: "#{comment_text} #{emoji}") + + # Check on `Preview` tab + click_link("Preview") + + expect(find(".js-md-preview")).to have_content(comment_text).and have_css("gl-emoji") + expect(page).not_to have_css(".js-note-text") + + # Check on the `Write` tab + click_link("Write") + + expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}") + + # Submit comment from the `Preview` tab to get rid of a separate `it` block + # which would specially tests if everything gets cleared from the note form. + click_link("Preview") + click_button("Comment") + end + + wait_for_requests + + page.within(".note") do + expect(page).to have_content(comment_text).and have_css("gl-emoji") + end + + page.within(".js-main-target-form") do + expect(page).to have_field("note[note]", with: "").and have_no_css(".js-md-preview") + end + end + + context "when commenting on diff" do + it "adds a comment" do + page.within(".diff-file:nth-of-type(1)") do + # Open a form for a comment and check UI elements are visible and acting as expecting. + click_diff_line(sample_commit.line_code) + + expect(page).to have_css(".js-temp-notes-holder form.new-note") + .and have_css(".js-close-discussion-note-form", text: "Cancel") + + # The `Cancel` button closes the current form. The page should not have any open forms after that. + find(".js-close-discussion-note-form").click + + expect(page).not_to have_css("form.new_note") + + # Try to open the same form twice. There should be only one form opened. + click_diff_line(sample_commit.line_code) + click_diff_line(sample_commit.line_code) + + expect(page).to have_css("form.new-note", count: 1) + + # Fill in a form. + page.within("form[data-line-code='#{sample_commit.line_code}']") do + fill_in("note[note]", with: "#{comment_text} :smile:") + end + + # Open another form and check we have two forms now (because the first one is filled in). + click_diff_line(sample_commit.del_line_code) + + expect(page).to have_field("note[note]", with: "#{comment_text} :smile:") + .and have_field("note[note]", with: "") + + # Test Preview feature for both forms. + page.within("form[data-line-code='#{sample_commit.line_code}']") do + click_link("Preview") + end + + page.within("form[data-line-code='#{sample_commit.del_line_code}']") do + fill_in("note[note]", with: another_comment_text) + + click_link("Preview") + end + + expect(page).to have_css(".js-md-preview", visible: true, count: 2) + .and have_content(comment_text) + .and have_content(another_comment_text) + .and have_xpath("//gl-emoji[@data-name='smile']") + + # Test UI elements, then submit. + page.within("form[data-line-code='#{sample_commit.line_code}']") do + expect(find(".js-note-text", visible: false).text).to eq("") + expect(page).to have_css('.js-md-write-button') + + click_button("Comment") + end + + expect(page).to have_button("Reply...").and have_no_css("form.new_note") + end + + # A comment should be added and visible. + page.within(".diff-file:nth-of-type(1) .note") do + expect(page).to have_content(comment_text).and have_xpath("//gl-emoji[@data-name='smile']") + end + end + end + end + + context "side-by-side view" do + before do + visit(project_commit_path(project, sample_commit.id, view: "parallel")) + end + + it "adds a comment" do + new_comment = "New comment" + old_comment = "Old comment" + + # Left side. + click_parallel_diff_line(sample_commit.del_line_code) + + page.within(".diff-file:nth-of-type(1) form[data-line-code='#{sample_commit.del_line_code}']") do + fill_in("note[note]", with: old_comment) + click_button("Comment") + end + + page.within(".diff-file:nth-of-type(1) .notes_content.parallel.old") do + expect(page).to have_content(old_comment) + end + + # Right side. + click_parallel_diff_line(sample_commit.line_code) + + page.within(".diff-file:nth-of-type(1) form[data-line-code='#{sample_commit.line_code}']") do + fill_in("note[note]", with: new_comment) + click_button("Comment") + end + + wait_for_requests + + expect(all(".diff-file:nth-of-type(1) .notes_content.parallel.new")[1].text).to have_content(new_comment) + end + end + + private + + def click_diff_line(line) + find(".line_holder[id='#{line}'] td:nth-of-type(1)").hover + find(".line_holder[id='#{line}'] button").click + end + + def click_parallel_diff_line(line) + find(".line_holder.parallel td[id='#{line}']").find(:xpath, 'preceding-sibling::*[1][self::td]').hover + find(".line_holder.parallel button[data-line-code='#{line}']").click + end +end diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb new file mode 100644 index 00000000000..a727cab4ac7 --- /dev/null +++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb @@ -0,0 +1,37 @@ +require "spec_helper" + +describe "User deletes comments on a commit", :js do + include Spec::Support::Helpers::Features::NotesHelpers + include RepoHelpers + + let(:comment_text) { "XML attached" } + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.add_developer(user) + + visit(project_commit_path(project, sample_commit.id)) + + add_note(comment_text) + end + + it "deletes comment" do + page.within(".note") do + expect(page).to have_content(comment_text) + end + + page.within(".main-notes-list") do + note = find(".note") + note.hover + + find(".more-actions").click + find(".more-actions .dropdown-menu li", match: :first) + + accept_confirm { find(".js-note-delete").click } + end + + expect(page).not_to have_css(".note") + end +end diff --git a/spec/features/projects/commit/comments/user_edits_comments_spec.rb b/spec/features/projects/commit/comments/user_edits_comments_spec.rb new file mode 100644 index 00000000000..75bccd99f59 --- /dev/null +++ b/spec/features/projects/commit/comments/user_edits_comments_spec.rb @@ -0,0 +1,42 @@ +require "spec_helper" + +describe "User edits a comment on a commit", :js do + include Spec::Support::Helpers::Features::NotesHelpers + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.add_developer(user) + + visit(project_commit_path(project, sample_commit.id)) + + add_note("XML attached") + end + + it "edits comment" do + NEW_COMMENT_TEXT = "+1 Awesome!".freeze + + page.within(".main-notes-list") do + note = find(".note") + note.hover + + note.find(".js-note-edit").click + end + + page.find(".current-note-edit-form textarea") + + page.within(".current-note-edit-form") do + fill_in("note[note]", with: NEW_COMMENT_TEXT) + click_button("Save comment") + end + + wait_for_requests + + page.within(".note") do + expect(page).to have_content(NEW_COMMENT_TEXT) + end + end +end diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb deleted file mode 100644 index 5174f793367..00000000000 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require "spec_helper" - -describe "User comments on commit", :js do - include Spec::Support::Helpers::Features::NotesHelpers - include RepoHelpers - - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - - COMMENT_TEXT = "XML attached".freeze - - before do - sign_in(user) - project.add_developer(user) - - visit(project_commit_path(project, sample_commit.id)) - end - - context "when adding new comment" do - it "adds comment" do - EMOJI = ":+1:".freeze - - page.within(".js-main-target-form") do - expect(page).not_to have_link("Cancel") - - fill_in("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}") - - # Check on `Preview` tab - click_link("Preview") - - expect(find(".js-md-preview")).to have_content(COMMENT_TEXT).and have_css("gl-emoji") - expect(page).not_to have_css(".js-note-text") - - # Check on `Write` tab - click_link("Write") - - expect(page).to have_field("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}") - - # Submit comment from the `Preview` tab to get rid of a separate `it` block - # which would specially tests if everything gets cleared from the note form. - click_link("Preview") - click_button("Comment") - end - - wait_for_requests - - page.within(".note") do - expect(page).to have_content(COMMENT_TEXT).and have_css("gl-emoji") - end - - page.within(".js-main-target-form") do - expect(page).to have_field("note[note]", with: "").and have_no_css(".js-md-preview") - end - end - end - - context "when editing comment" do - before do - add_note(COMMENT_TEXT) - end - - it "edits comment" do - NEW_COMMENT_TEXT = "+1 Awesome!".freeze - - page.within(".main-notes-list") do - note = find(".note") - note.hover - - note.find(".js-note-edit").click - end - - page.find(".current-note-edit-form textarea") - - page.within(".current-note-edit-form") do - fill_in("note[note]", with: NEW_COMMENT_TEXT) - click_button("Save comment") - end - - wait_for_requests - - page.within(".note") do - expect(page).to have_content(NEW_COMMENT_TEXT) - end - end - end - - context "when deleting comment" do - before do - add_note(COMMENT_TEXT) - end - - it "deletes comment" do - page.within(".note") do - expect(page).to have_content(COMMENT_TEXT) - end - - page.within(".main-notes-list") do - note = find(".note") - note.hover - - find(".more-actions").click - find(".more-actions .dropdown-menu li", match: :first) - - accept_confirm { find(".js-note-delete").click } - end - - expect(page).not_to have_css(".note") - end - end -end diff --git a/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb b/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb index f285c6c8783..1f21ef7b382 100644 --- a/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb +++ b/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb @@ -1,32 +1,84 @@ -require 'spec_helper' +require "spec_helper" -describe 'User creates a merge request', :js do +describe "User creates a merge request", :js do + include ProjectForksHelper + + let(:title) { "Some feature" } let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do project.add_master(user) sign_in(user) + end + it "creates a merge request" do visit(project_new_merge_request_path(project)) - end - it 'creates a merge request' do - find('.js-source-branch').click - click_link('fix') + find(".js-source-branch").click + click_link("fix") - find('.js-target-branch').click - click_link('feature') + find(".js-target-branch").click + click_link("feature") - click_button('Compare branches') + click_button("Compare branches") - fill_in('merge_request_title', with: 'Wiki Feature') - click_button('Submit merge request') + fill_in("Title", with: title) + click_button("Submit merge request") - page.within('.merge-request') do - expect(page).to have_content('Wiki Feature') + page.within(".merge-request") do + expect(page).to have_content(title) end + end + + context "to a forked project" do + let(:forked_project) { fork_project(project, user, namespace: user.namespace, repository: true) } + + it "creates a merge request" do + visit(project_new_merge_request_path(forked_project)) + + expect(page).to have_content("Source branch").and have_content("Target branch") + expect(find("#merge_request_target_project_id", visible: false).value).to eq(project.id.to_s) + + click_button("Compare branches and continue") + + expect(page).to have_content("You must select source and target branch") + + first(".js-source-project").click + first(".dropdown-source-project a", text: forked_project.full_path) + + first(".js-target-project").click + first(".dropdown-target-project a", text: project.full_path) + + first(".js-source-branch").click - wait_for_requests + wait_for_requests + + source_branch = "fix" + + first(".js-source-branch-dropdown .dropdown-content a", text: source_branch).click + + click_button("Compare branches and continue") + + expect(page).to have_css("h3.page-title", text: "New Merge Request") + + page.within("form#new_merge_request") do + fill_in("Title", with: title) + end + + click_button("Assignee") + + expect(find(".js-assignee-search")["data-project-id"]).to eq(project.id.to_s) + + page.within(".dropdown-menu-user") do + expect(page).to have_content("Unassigned") + .and have_content(user.name) + .and have_content(project.users.first.name) + end + + click_button("Submit merge request") + + expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.name}:#{source_branch} into master") + end end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 90e28483c6c..9c165b17704 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -125,7 +125,7 @@ describe 'Pipelines', :js do context 'when canceling' do before do find('.js-pipelines-cancel-button').click - find('.js-primary-button').click + find('.js-modal-primary-action').click wait_for_requests end @@ -156,7 +156,6 @@ describe 'Pipelines', :js do context 'when retrying' do before do find('.js-pipelines-retry-button').click - find('.js-primary-button').click wait_for_requests end @@ -256,7 +255,7 @@ describe 'Pipelines', :js do context 'when canceling' do before do find('.js-pipelines-cancel-button').click - find('.js-primary-button').click + find('.js-modal-primary-action').click end it 'indicates that pipeline was canceled' do diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb index ab9420fc38f..2c67cec6b67 100644 --- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'User deletes wiki page' do +feature 'User deletes wiki page', :js do let(:user) { create(:user) } let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } @@ -13,6 +13,7 @@ feature 'User deletes wiki page' do it 'deletes a page' do click_on('Edit') click_on('Delete') + find('.js-modal-primary-action').click expect(page).to have_content('Page was successfully deleted') end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 94a2b289e64..6f968a2c590 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -437,5 +437,107 @@ feature 'Login' do expect(current_path).to eq(root_path) end + + context 'when 2FA is required for the user' do + before do + group = create(:group, require_two_factor_authentication: true) + group.add_developer(user) + end + + context 'when the user did not enable 2FA' do + it 'asks to set 2FA before asking to accept the terms' do + visit new_user_session_path + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: '12345678' + + click_button 'Sign in' + + expect_to_be_on_terms_page + click_button 'Accept terms' + + expect(current_path).to eq(profile_two_factor_auth_path) + + fill_in 'pin_code', with: user.reload.current_otp + + click_button 'Register with two-factor app' + click_link 'Proceed' + + expect(current_path).to eq(profile_account_path) + end + end + + context 'when the user already enabled 2FA' do + before do + user.update!(otp_required_for_login: true, + otp_secret: User.generate_otp_secret(32)) + end + + it 'asks the user to accept the terms' do + visit new_user_session_path + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: '12345678' + click_button 'Sign in' + + fill_in 'user_otp_attempt', with: user.reload.current_otp + click_button 'Verify code' + + expect_to_be_on_terms_page + click_button 'Accept terms' + + expect(current_path).to eq(root_path) + end + end + end + + context 'when the users password is expired' do + before do + user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC')) + end + + it 'asks the user to accept the terms before setting a new password' do + visit new_user_session_path + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: '12345678' + click_button 'Sign in' + + expect_to_be_on_terms_page + click_button 'Accept terms' + + expect(current_path).to eq(new_profile_password_path) + + fill_in 'user_current_password', with: '12345678' + fill_in 'user_password', with: 'new password' + fill_in 'user_password_confirmation', with: 'new password' + click_button 'Set new password' + + expect(page).to have_content('Password successfully changed') + end + end + + context 'when the user does not have an email configured' do + let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml', email: 'temp-email-for-oauth-user@gitlab.localhost') } + + before do + stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config]) + end + + it 'asks the user to accept the terms before setting an email' do + gitlab_sign_in_via('saml', user, 'my-uid') + + expect_to_be_on_terms_page + click_button 'Accept terms' + + expect(current_path).to eq(profile_path) + + fill_in 'Email', with: 'hello@world.com' + + click_button 'Update profile settings' + + expect(page).to have_content('Profile was successfully updated') + end + end end end diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index bf6b5fa3d6a..f9469adbfe3 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -81,4 +81,22 @@ describe 'Users > Terms' do expect(find_field('issue_description').value).to eq("We don't want to lose what the user typed") end end + + context 'when the terms are enforced' do + before do + enforce_terms + end + + context 'signing out', :js do + it 'allows the user to sign out without a response' do + visit terms_path + + find('.header-user-dropdown-toggle').click + click_link('Sign out') + + expect(page).to have_content('Sign in') + expect(page).to have_content('Register') + end + end + end end diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js index effacbcff4e..a34a1add4e0 100644 --- a/spec/javascripts/helpers/vue_mount_component_helper.js +++ b/spec/javascripts/helpers/vue_mount_component_helper.js @@ -1,14 +1,30 @@ +import Vue from 'vue'; + +const mountComponent = (Component, props = {}, el = null) => new Component({ + propsData: props, +}).$mount(el); + export const createComponentWithStore = (Component, store, propsData = {}) => new Component({ store, propsData, }); +export const createComponentWithMixin = (mixins = [], state = {}, props = {}, template = '<div></div>') => { + const Component = Vue.extend({ + template, + mixins, + data() { + return props; + }, + }); + + return mountComponent(Component, props); +}; + export const mountComponentWithStore = (Component, { el, props, store }) => new Component({ store, propsData: props || { }, }).$mount(el); -export default (Component, props = {}, el = null) => new Component({ - propsData: props, -}).$mount(el); +export default mountComponent; diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js deleted file mode 100644 index e0ea3649646..00000000000 --- a/spec/javascripts/pipelines/async_button_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import Vue from 'vue'; -import asyncButtonComp from '~/pipelines/components/async_button.vue'; -import eventHub from '~/pipelines/event_hub'; - -describe('Pipelines Async Button', () => { - let component; - let AsyncButtonComponent; - - beforeEach(() => { - AsyncButtonComponent = Vue.extend(asyncButtonComp); - - component = new AsyncButtonComponent({ - propsData: { - endpoint: '/foo', - title: 'Foo', - icon: 'repeat', - cssClass: 'bar', - pipelineId: 123, - type: 'explode', - }, - }).$mount(); - }); - - it('should render a button', () => { - expect(component.$el.tagName).toEqual('BUTTON'); - }); - - it('should render svg icon', () => { - expect(component.$el.querySelector('svg')).not.toBeNull(); - }); - - it('should render the provided title', () => { - expect(component.$el.getAttribute('data-original-title')).toContain('Foo'); - expect(component.$el.getAttribute('aria-label')).toContain('Foo'); - }); - - it('should render the provided cssClass', () => { - expect(component.$el.getAttribute('class')).toContain('bar'); - }); - - describe('With confirm dialog', () => { - it('should call the service when confimation is positive', () => { - eventHub.$on('openConfirmationModal', (data) => { - expect(data.pipelineId).toEqual(123); - expect(data.type).toEqual('explode'); - }); - - component = new AsyncButtonComponent({ - propsData: { - endpoint: '/foo', - title: 'Foo', - icon: 'fa fa-foo', - cssClass: 'bar', - pipelineId: 123, - type: 'explode', - }, - }).$mount(); - - component.$el.click(); - }); - }); -}); diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index de744739e42..05ca4cb9044 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import tableRowComp from '~/pipelines/components/pipelines_table_row.vue'; +import eventHub from '~/pipelines/event_hub'; describe('Pipelines Table Row', () => { const jsonFixtureName = 'pipelines/pipelines.json'; @@ -151,13 +152,37 @@ describe('Pipelines Table Row', () => { describe('actions column', () => { beforeEach(() => { - component = buildComponent(pipeline); + const withActions = Object.assign({}, pipeline); + withActions.flags.cancelable = true; + withActions.flags.retryable = true; + withActions.cancel_path = '/cancel'; + withActions.retry_path = '/retry'; + + component = buildComponent(withActions); }); it('should render the provided actions', () => { - expect( - component.$el.querySelectorAll('.table-section:nth-child(6) ul li').length, - ).toEqual(pipeline.details.manual_actions.length); + expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull(); + expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull(); + }); + + it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => { + eventHub.$on('retryPipeline', (endpoint) => { + expect(endpoint).toEqual('/retry'); + }); + + component.$el.querySelector('.js-pipelines-retry-button').click(); + expect(component.isRetrying).toEqual(true); + }); + + it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => { + eventHub.$on('openConfirmationModal', (data) => { + expect(data.endpoint).toEqual('/cancel'); + expect(data.pipelineId).toEqual(pipeline.id); + }); + + component.$el.querySelector('.js-pipelines-cancel-button').click(); + expect(component.isCancelling).toEqual(true); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js deleted file mode 100644 index cee22d5342a..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue'; -import maintainerEditComponent from '~/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('RWidgetMaintainerEdit', () => { - let Component; - let vm; - - beforeEach(() => { - Component = Vue.extend(maintainerEditComponent); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('when a maintainer is allowed to edit', () => { - beforeEach(() => { - vm = mountComponent(Component, { - maintainerEditAllowed: true, - }); - }); - - it('it renders the message', () => { - expect(vm.$el.textContent.trim()).toEqual('Allows edits from maintainers'); - }); - }); - - describe('when a maintainer is not allowed to edit', () => { - beforeEach(() => { - vm = mountComponent(Component, { - maintainerEditAllowed: false, - }); - }); - - it('hides the message', () => { - expect(vm.$el.textContent.trim()).toEqual(''); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index e55c7649d40..30918428da2 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options'; +import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import eventHub from '~/vue_merge_request_widget/event_hub'; import notify from '~/lib/utils/notify'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb index 726a3c1c83a..43b68e69131 100644 --- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb +++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb @@ -17,12 +17,8 @@ describe Gitlab::Auth::BlockedUserTracker do end context 'failed login due to blocked user' do - let(:env) do - { - 'warden.options' => { message: User::BLOCKED_MESSAGE }, - described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } } - } - end + let(:base_env) { { 'warden.options' => { message: User::BLOCKED_MESSAGE } } } + let(:env) { base_env.merge(request_env) } subject { described_class.log_if_user_blocked(env) } @@ -30,23 +26,37 @@ describe Gitlab::Auth::BlockedUserTracker do expect_any_instance_of(SystemHooksService).to receive(:execute_hooks_for).with(user, :failed_login) end - it 'logs a blocked user' do - user.block! + context 'via GitLab login' do + let(:request_env) { { described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } } } } - expect(subject).to be_truthy - end + it 'logs a blocked user' do + user.block! + + expect(subject).to be_truthy + end - it 'logs a blocked user by e-mail' do - user.block! - env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email + it 'logs a blocked user by e-mail' do + user.block! + env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email - expect(subject).to be_truthy + expect(subject).to be_truthy + end end - it 'logs a LDAP blocked user' do - user.ldap_block! + context 'via LDAP login' do + let(:request_env) { { described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'username' => user.username } } } + + it 'logs a blocked user' do + user.block! + + expect(subject).to be_truthy + end + + it 'logs a LDAP blocked user' do + user.ldap_block! - expect(subject).to be_truthy + expect(subject).to be_truthy + end end end end diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb new file mode 100644 index 00000000000..fa209bed74e --- /dev/null +++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Auth::UserAccessDeniedReason do + include TermsHelper + let(:user) { build(:user) } + + let(:reason) { described_class.new(user) } + + describe '#rejection_message' do + subject { reason.rejection_message } + + context 'when a user is blocked' do + before do + user.block! + end + + it { is_expected.to match /blocked/ } + end + + context 'a user did not accept the enforced terms' do + before do + enforce_terms + end + + it { is_expected.to match /You must accept the Terms of Service/ } + end + + context 'when the user is internal' do + let(:user) { User.ghost } + + it { is_expected.to match /This action cannot be performed by internal users/ } + end + end +end diff --git a/spec/lib/gitlab/build_access_spec.rb b/spec/lib/gitlab/build_access_spec.rb new file mode 100644 index 00000000000..08f50bf4fac --- /dev/null +++ b/spec/lib/gitlab/build_access_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::BuildAccess do + let(:user) { create(:user) } + let(:project) { create(:project) } + + describe '#can_do_action' do + subject { described_class.new(user, project: project).can_do_action?(:download_code) } + + context 'when the user can do an action on the project but cannot access git' do + before do + user.block! + project.add_developer(user) + end + + it { is_expected.to be(true) } + end + + context 'when the user cannot do an action on the project' do + it { is_expected.to be(false) } + end + end +end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 6c625596605..317a932d5a6 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::GitAccess do - set(:user) { create(:user) } + include TermsHelper + + let(:user) { create(:user) } let(:actor) { user } let(:project) { create(:project, :repository) } @@ -1040,6 +1042,96 @@ describe Gitlab::GitAccess do end end + context 'terms are enforced' do + before do + enforce_terms + end + + shared_examples 'access after accepting terms' do + let(:actions) do + [-> { pull_access_check }, + -> { push_access_check }] + end + + it 'blocks access when the user did not accept terms', :aggregate_failures do + actions.each do |action| + expect { action.call }.to raise_unauthorized(/You must accept the Terms of Service in order to perform this action/) + end + end + + it 'allows access when the user accepted the terms', :aggregate_failures do + accept_terms(user) + + actions.each do |action| + expect { action.call }.not_to raise_error + end + end + end + + describe 'as an anonymous user to a public project' do + let(:actor) { nil } + let(:project) { create(:project, :public, :repository) } + + it { expect { pull_access_check }.not_to raise_error } + end + + describe 'as a guest to a public project' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'access after accepting terms' do + let(:actions) { [-> { pull_access_check }] } + end + end + + describe 'as a reporter to the project' do + before do + project.add_reporter(user) + end + + it_behaves_like 'access after accepting terms' do + let(:actions) { [-> { pull_access_check }] } + end + end + + describe 'as a developer of the project' do + before do + project.add_developer(user) + end + + it_behaves_like 'access after accepting terms' + end + + describe 'as a master of the project' do + before do + project.add_master(user) + end + + it_behaves_like 'access after accepting terms' + end + + describe 'as an owner of the project' do + let(:project) { create(:project, :repository, namespace: user.namespace) } + + it_behaves_like 'access after accepting terms' + end + + describe 'when a ci build clones the project' do + let(:protocol) { 'http' } + let(:authentication_abilities) { [:build_download_code] } + let(:auth_result_type) { :build } + + before do + project.add_developer(user) + end + + it "doesn't block http pull" do + aggregate_failures do + expect { pull_access_check }.not_to raise_error + end + end + end + end + private def raise_unauthorized(message) diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index f030f371372..13940713dfc 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -45,25 +45,6 @@ describe ::Gitlab::RepoPath do end end - describe '.strip_storage_path' do - before do - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'), - 'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar') - }) - end - - it 'strips the storage path' do - expect(described_class.strip_storage_path('/bar/foo/qux/baz.git')).to eq('foo/qux/baz.git') - end - - it 'raises NotFoundError if no storage matches the path' do - expect { described_class.strip_storage_path('/doesnotexist/foo.git') }.to raise_error( - described_class::NotFoundError - ) - end - end - describe '.find_project' do let(:project) { create(:project) } let(:redirect) { project.route.create_redirect('foo/bar/baz') } diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index bed58d407ef..0ee7fa1e570 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -39,6 +39,14 @@ describe Gitlab::UntrustedRegexp do expect(result).to be_falsy end + + it 'can handle regular expressions in multiline mode' do + regexp = described_class.new('^\d', multiline: true) + + result = regexp === "Header\n\n1. Content" + + expect(result).to be_truthy + end end describe '#scan' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3f2eb58f009..ad094b3ed48 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe User do include ProjectForksHelper + include TermsHelper describe 'modules' do subject { described_class } @@ -2728,4 +2729,30 @@ describe User do .to change { RedirectRoute.where(path: 'foo').count }.by(-1) end end + + describe '#required_terms_not_accepted?' do + let(:user) { build(:user) } + subject { user.required_terms_not_accepted? } + + context "when terms are not enforced" do + it { is_expected.to be_falsy } + end + + context "when terms are enforced and accepted by the user" do + before do + enforce_terms + accept_terms(user) + end + + it { is_expected.to be_falsy } + end + + context "when terms are enforced but the user has not accepted" do + before do + enforce_terms + end + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 41cf2ef7225..9ca156deaa0 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -94,6 +94,19 @@ describe Ci::BuildPolicy do end end end + + context 'when maintainer is allowed to push to pipeline branch' do + let(:project) { create(:project, :public) } + let(:owner) { user } + + it 'enables update_build if user is maintainer' do + allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) + allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true) + + expect(policy).to be_allowed :update_build + expect(policy).to be_allowed :update_commit_status + end + end end describe 'rules for protected ref' do diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb index 48a8064c5fc..a5e509cfa0f 100644 --- a/spec/policies/ci/pipeline_policy_spec.rb +++ b/spec/policies/ci/pipeline_policy_spec.rb @@ -62,5 +62,17 @@ describe Ci::PipelinePolicy, :models do end end end + + context 'when maintainer is allowed to push to pipeline branch' do + let(:project) { create(:project, :public) } + let(:owner) { user } + + it 'enables update_pipeline if user is maintainer' do + allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) + allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true) + + expect(policy).to be_allowed :update_pipeline + end + end end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index ec26810e371..873673b50ef 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -90,4 +90,94 @@ describe GlobalPolicy do it { is_expected.to be_allowed(:update_custom_attribute) } end end + + shared_examples 'access allowed when terms accepted' do |ability| + it { is_expected.not_to be_allowed(ability) } + + it "allows #{ability} when the user accepted the terms" do + accept_terms(current_user) + + is_expected.to be_allowed(ability) + end + end + + describe 'API access' do + context 'regular user' do + it { is_expected.to be_allowed(:access_api) } + end + + context 'admin' do + let(:current_user) { create(:admin) } + + it { is_expected.to be_allowed(:access_api) } + end + + context 'anonymous' do + let(:current_user) { nil } + + it { is_expected.to be_allowed(:access_api) } + end + + context 'when terms are enforced' do + before do + enforce_terms + end + + context 'regular user' do + it_behaves_like 'access allowed when terms accepted', :access_api + end + + context 'admin' do + let(:current_user) { create(:admin) } + + it_behaves_like 'access allowed when terms accepted', :access_api + end + + context 'anonymous' do + let(:current_user) { nil } + + it { is_expected.to be_allowed(:access_api) } + end + end + end + + describe 'git access' do + describe 'regular user' do + it { is_expected.to be_allowed(:access_git) } + end + + describe 'admin' do + let(:current_user) { create(:admin) } + + it { is_expected.to be_allowed(:access_git) } + end + + describe 'anonymous' do + let(:current_user) { nil } + + it { is_expected.to be_allowed(:access_git) } + end + + context 'when terms are enforced' do + before do + enforce_terms + end + + context 'regular user' do + it_behaves_like 'access allowed when terms accepted', :access_git + end + + context 'admin' do + let(:current_user) { create(:admin) } + + it_behaves_like 'access allowed when terms accepted', :access_git + end + + context 'anonymous' do + let(:current_user) { nil } + + it { is_expected.to be_allowed(:access_git) } + end + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 8b9c4ac0b4b..6609f5f7afd 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -404,7 +404,7 @@ describe ProjectPolicy do ) end let(:maintainer_abilities) do - %w(create_build update_build create_pipeline update_pipeline) + %w(create_build create_pipeline) end subject { described_class.new(user, project) } diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 837389451e8..d3ab44c0d7e 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -6,6 +6,7 @@ describe API::Helpers do include API::APIGuard::HelperMethods include described_class include SentryHelper + include TermsHelper let(:user) { create(:user) } let(:admin) { create(:admin) } @@ -163,6 +164,23 @@ describe API::Helpers do expect { current_user }.to raise_error /403/ end + context 'when terms are enforced' do + before do + enforce_terms + env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + end + + it 'returns a 403 when a user has not accepted the terms' do + expect { current_user }.to raise_error /You must accept the Terms of Service/ + end + + it 'sets the current user when the user accepted the terms' do + accept_terms(user) + + expect(current_user).to eq(user) + end + end + it "sets current_user" do env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(current_user).to eq(user) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 494db30e8e0..2514dab1714 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" describe 'Git HTTP requests' do + include TermsHelper include GitHttpHelpers include WorkhorseHelpers include UserActivitiesHelpers @@ -824,4 +825,56 @@ describe 'Git HTTP requests' do end end end + + context 'when terms are enforced' do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:path) { "#{project.full_path}.git" } + let(:env) { { user: user.username, password: user.password } } + + before do + project.add_master(user) + enforce_terms + end + + it 'blocks git access when the user did not accept terms', :aggregate_failures do + clone_get(path, env) do |response| + expect(response).to have_gitlab_http_status(:forbidden) + end + + download(path, env) do |response| + expect(response).to have_gitlab_http_status(:forbidden) + end + + upload(path, env) do |response| + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when the user accepted the terms' do + before do + accept_terms(user) + end + + it 'allows clones' do + clone_get(path, env) do |response| + expect(response).to have_gitlab_http_status(:ok) + end + end + + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' + end + + context 'from CI' do + let(:build) { create(:ci_build, :running) } + let(:env) { { user: 'gitlab-ci-token', password: build.token } } + + before do + build.update!(user: user, project: project) + end + + it_behaves_like 'pulls are allowed' + end + end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index e88e86c2998..b741308e2c5 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -114,7 +114,9 @@ describe PipelineSerializer do Gitlab::GitalyClient.reset_counts end - shared_examples 'no N+1 queries' do + context 'with the same ref' do + let(:ref) { 'feature' } + it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } @@ -123,12 +125,6 @@ describe PipelineSerializer do end end - context 'with the same ref' do - let(:ref) { 'feature' } - - it_behaves_like 'no N+1 queries' - end - context 'with different refs' do def ref @sequence ||= 0 @@ -136,7 +132,16 @@ describe PipelineSerializer do "feature-#{@sequence}" end - it_behaves_like 'no N+1 queries' + it 'verifies number of queries', :request_store do + recorded = ActiveRecord::QueryRecorder.new { subject } + + # For each ref there is a permission check if maintainer can update + # pipeline. With the same ref this check is cached but if refs are + # different then there is an extra query per ref + # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 + expect(recorded.count).to be_within(1).of(51) + expect(recorded.cached_count).to eq(0) + end end def create_pipeline(status) diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index f1acfc48468..a73bd7a0268 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::RetryPipelineService, '#execute' do + include ProjectForksHelper + let(:user) { create(:user) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } @@ -266,6 +268,33 @@ describe Ci::RetryPipelineService, '#execute' do end end + context 'when maintainer is allowed to push to forked project' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:forked_project) { fork_project(project) } + let(:pipeline) { create(:ci_pipeline, project: forked_project, ref: 'fixes') } + + before do + project.add_master(user) + create(:merge_request, + source_project: forked_project, + target_project: project, + source_branch: 'fixes', + allow_maintainer_to_push: true) + create_build('rspec 1', :failed, 1) + end + + it 'allows to retry failed pipeline' do + allow_any_instance_of(Project).to receive(:fetch_branch_allows_maintainer_push?).and_return(true) + allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) + + service.execute(pipeline) + + expect(build('rspec 1')).to be_pending + expect(pipeline.reload).to be_running + end + end + def statuses pipeline.reload.statuses end diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index 1c2f9c5cf43..1685dc748bd 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -8,80 +8,22 @@ describe Clusters::CreateService do subject { described_class.new(project, user, params).execute(access_token) } context 'when provider is gcp' do - shared_context 'valid params' do - let(:params) do - { - name: 'test-cluster', - provider_type: :gcp, - provider_gcp_attributes: { - gcp_project_id: 'gcp-project', - zone: 'us-central1-a', - num_nodes: 1, - machine_type: 'machine_type-a' - } - } - end - end - - shared_context 'invalid params' do - let(:params) do - { - name: 'test-cluster', - provider_type: :gcp, - provider_gcp_attributes: { - gcp_project_id: '!!!!!!!', - zone: 'us-central1-a', - num_nodes: 1, - machine_type: 'machine_type-a' - } - } - end - end - - shared_examples 'create cluster' do - it 'creates a cluster object and performs a worker' do - expect(ClusterProvisionWorker).to receive(:perform_async) - - expect { subject } - .to change { Clusters::Cluster.count }.by(1) - .and change { Clusters::Providers::Gcp.count }.by(1) - - expect(subject.name).to eq('test-cluster') - expect(subject.user).to eq(user) - expect(subject.project).to eq(project) - expect(subject.provider.gcp_project_id).to eq('gcp-project') - expect(subject.provider.zone).to eq('us-central1-a') - expect(subject.provider.num_nodes).to eq(1) - expect(subject.provider.machine_type).to eq('machine_type-a') - expect(subject.provider.access_token).to eq(access_token) - expect(subject.platform).to be_nil - end - end - - shared_examples 'error' do - it 'returns an error' do - expect(ClusterProvisionWorker).not_to receive(:perform_async) - expect { subject }.to change { Clusters::Cluster.count }.by(0) - expect(subject.errors[:"provider_gcp.gcp_project_id"]).to be_present - end - end - context 'when project has no clusters' do context 'when correct params' do - include_context 'valid params' + include_context 'valid cluster create params' - include_examples 'create cluster' + include_examples 'create cluster service success' end context 'when invalid params' do - include_context 'invalid params' + include_context 'invalid cluster create params' - include_examples 'error' + include_examples 'create cluster service error' end end context 'when project has a cluster' do - include_context 'valid params' + include_context 'valid cluster create params' let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } it 'does not create a cluster' do diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 1dad39fdab3..57aa07cf4fa 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -159,7 +159,11 @@ module TestEnv end spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s - @gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i } + Bundler.with_original_env do + raise "gitaly spawn failed" unless system(spawn_script) + end + @gitaly_pid = Integer(File.read('tmp/tests/gitaly.pid')) + Kernel.at_exit { stop_gitaly } wait_gitaly diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb new file mode 100644 index 00000000000..43a2fd05498 --- /dev/null +++ b/spec/support/services/clusters/create_service_shared.rb @@ -0,0 +1,57 @@ +shared_context 'valid cluster create params' do + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: 'gcp-project', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a' + } + } + end +end + +shared_context 'invalid cluster create params' do + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: '!!!!!!!', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a' + } + } + end +end + +shared_examples 'create cluster service success' do + it 'creates a cluster object and performs a worker' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Providers::Gcp.count }.by(1) + + expect(subject.name).to eq('test-cluster') + expect(subject.user).to eq(user) + expect(subject.project).to eq(project) + expect(subject.provider.gcp_project_id).to eq('gcp-project') + expect(subject.provider.zone).to eq('us-central1-a') + expect(subject.provider.num_nodes).to eq(1) + expect(subject.provider.machine_type).to eq('machine_type-a') + expect(subject.provider.access_token).to eq(access_token) + expect(subject.platform).to be_nil + end +end + +shared_examples 'create cluster service error' do + it 'returns an error' do + expect(ClusterProvisionWorker).not_to receive(:perform_async) + expect { subject }.to change { Clusters::Cluster.count }.by(0) + expect(subject.errors[:"provider_gcp.gcp_project_id"]).to be_present + end +end |