diff options
author | Douwe Maan <douwe@selenight.nl> | 2016-03-21 23:22:21 +0100 |
---|---|---|
committer | Douwe Maan <douwe@selenight.nl> | 2016-03-21 23:22:21 +0100 |
commit | ae7b2ef62cdf61c990f914d776a6fdfc2bc49fa2 (patch) | |
tree | 6f0022bf04b1b566fa79b979cc9cc373cd0ebaa1 /spec | |
parent | 8d544645f0ef114586212835cf011a3e268c9ec1 (diff) | |
parent | 0305dd98b32b5a989f2b84e0810cf5ddc14abd7f (diff) | |
download | gitlab-ce-ae7b2ef62cdf61c990f914d776a6fdfc2bc49fa2.tar.gz |
Merge branch 'master' into issue_12658
# Conflicts:
# app/models/issue.rb
# app/views/projects/_home_panel.html.haml
# app/views/shared/projects/_project.html.haml
# db/schema.rb
# spec/models/project_spec.rb
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/projects/issues_controller_spec.rb | 35 | ||||
-rw-r--r-- | spec/controllers/projects/merge_requests_controller_spec.rb | 23 | ||||
-rw-r--r-- | spec/features/issues/move_spec.rb | 87 | ||||
-rw-r--r-- | spec/helpers/labels_helper_spec.rb | 6 | ||||
-rw-r--r-- | spec/helpers/projects_helper_spec.rb | 19 | ||||
-rw-r--r-- | spec/lib/banzai/filter/label_reference_filter_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/gfm/reference_rewriter_spec.rb | 89 | ||||
-rw-r--r-- | spec/lib/gitlab/reference_extractor_spec.rb | 20 | ||||
-rw-r--r-- | spec/mailers/notify_spec.rb | 27 | ||||
-rw-r--r-- | spec/models/issue_spec.rb | 59 | ||||
-rw-r--r-- | spec/models/merge_request_spec.rb | 5 | ||||
-rw-r--r-- | spec/models/project_spec.rb | 41 | ||||
-rw-r--r-- | spec/models/project_wiki_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/repository_spec.rb | 30 | ||||
-rw-r--r-- | spec/requests/api/issues_spec.rb | 34 | ||||
-rw-r--r-- | spec/requests/api/merge_requests_spec.rb | 37 | ||||
-rw-r--r-- | spec/services/issues/move_service_spec.rb | 213 | ||||
-rw-r--r-- | spec/services/system_note_service_spec.rb | 53 | ||||
-rw-r--r-- | spec/workers/repository_fork_worker_spec.rb | 19 |
19 files changed, 781 insertions, 30 deletions
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 2cd81231144..d6e4cd71ce6 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1,11 +1,11 @@ require('spec_helper') describe Projects::IssuesController do - describe "GET #index" do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:issue) { create(:issue, project: project) } + let(:project) { create(:project_empty_repo) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project) } + describe "GET #index" do before do sign_in(user) project.team << [user, :developer] @@ -41,7 +41,7 @@ describe Projects::IssuesController do end describe 'Confidential Issues' do - let(:project) { create(:empty_project, :public) } + let(:project) { create(:project_empty_repo, :public) } let(:assignee) { create(:assignee) } let(:author) { create(:user) } let(:non_member) { create(:user) } @@ -186,4 +186,29 @@ describe Projects::IssuesController do end end end + + describe "DELETE #destroy" do + context "when the user is a developer" do + before { sign_in(user) } + it "rejects a developer to destroy an issue" do + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid + expect(response.status).to eq(404) + end + end + + context "when the user is owner" do + let(:owner) { create(:user) } + let(:namespace) { create(:namespace, owner: owner) } + let(:project) { create(:project, namespace: namespace) } + + before { sign_in(owner) } + + it "deletes the issue" do + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid + + expect(response.status).to eq(302) + expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now + end + end + end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index e82fe26c7a6..c5b034dc064 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -157,6 +157,29 @@ describe Projects::MergeRequestsController do end end + describe "DELETE #destroy" do + it "denies access to users unless they're admin or project owner" do + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid + + expect(response.status).to eq(404) + end + + context "when the user is owner" do + let(:owner) { create(:user) } + let(:namespace) { create(:namespace, owner: owner) } + let(:project) { create(:project, namespace: namespace) } + + before { sign_in owner } + + it "deletes the merge request" do + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid + + expect(response.status).to eq(302) + expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now + end + end + end + describe 'GET diffs' do def go(format: 'html') get :diffs, diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb new file mode 100644 index 00000000000..6fda0c31866 --- /dev/null +++ b/spec/features/issues/move_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' + +feature 'issue move to another project' do + let(:user) { create(:user) } + let(:old_project) { create(:project) } + let(:text) { 'Some issue description' } + + let(:issue) do + create(:issue, description: text, project: old_project, author: user) + end + + background { login_as(user) } + + context 'user does not have permission to move issue' do + background do + old_project.team << [user, :guest] + + edit_issue(issue) + end + + scenario 'moving issue to another project not allowed' do + expect(page).to have_no_select('move_to_project_id') + end + end + + context 'user has permission to move issue' do + let!(:mr) { create(:merge_request, source_project: old_project) } + let(:new_project) { create(:project) } + let(:text) { 'Text with !1' } + let(:cross_reference) { old_project.to_reference } + + background do + old_project.team << [user, :reporter] + new_project.team << [user, :reporter] + + edit_issue(issue) + end + + scenario 'moving issue to another project' do + select(new_project.name_with_namespace, from: 'move_to_project_id') + click_button('Save changes') + + expect(current_url).to include project_path(new_project) + + page.within('.issue') do + expect(page).to have_content("Text with #{cross_reference}!1") + expect(page).to have_content("Moved from #{cross_reference}#1") + expect(page).to have_content(issue.title) + end + end + + context 'projects user does not have permission to move issue to exist' do + let!(:private_project) { create(:project, :private) } + let(:another_project) { create(:project) } + background { another_project.team << [user, :guest] } + + scenario 'browsing projects in projects select' do + options = [ '', 'No project', new_project.name_with_namespace ] + expect(page).to have_select('move_to_project_id', options: options) + end + end + + context 'issue has been already moved' do + let(:new_issue) { create(:issue, project: new_project) } + let(:issue) do + create(:issue, project: old_project, author: user, moved_to: new_issue) + end + + scenario 'user wants to move issue that has already been moved' do + expect(page).to have_no_select('move_to_project_id') + end + end + end + + def edit_issue(issue) + visit issue_path(issue) + page.within('.issuable-header') { click_link 'Edit' } + end + + def issue_path(issue) + namespace_project_issue_path(issue.project.namespace, issue.project, issue) + end + + def project_path(project) + namespace_project_path(new_project.namespace, new_project) + end +end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index eca8bc8ab2d..39042ff7e91 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -11,7 +11,7 @@ describe LabelsHelper do end it 'uses the instance variable' do - expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has_tooltip".*</span></a>} + expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has-tooltip".*</span></a>} end end @@ -41,8 +41,8 @@ describe LabelsHelper do context 'with a tooltip argument' do context 'set to false' do - it 'does not include the has_tooltip class' do - expect(link_to_label(label, tooltip: false)).not_to match %r{has_tooltip} + it 'does not include the has-tooltip class' do + expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip} end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index dc324f1e60e..c258cfebd73 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -86,4 +86,23 @@ describe ProjectsHelper do end end end + + describe 'default_clone_protocol' do + describe 'using HTTP' do + it 'returns HTTP' do + expect(helper).to receive(:current_user).and_return(nil) + + expect(helper.send(:default_clone_protocol)).to eq('http') + end + end + + describe 'using HTTPS' do + it 'returns HTTPS' do + allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https') + expect(helper).to receive(:current_user).and_return(nil) + + expect(helper.send(:default_clone_protocol)).to eq('https') + end + end + end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 4c1d4a2d24c..94468abcbb3 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -56,7 +56,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do describe 'label span element' do it 'includes default classes' do doc = reference_filter("Label #{reference}") - expect(doc.css('a span').first.attr('class')).to eq 'label color-label has_tooltip' + expect(doc.css('a span').first.attr('class')).to eq 'label color-label has-tooltip' end it 'includes a style attribute' do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb new file mode 100644 index 00000000000..0a7ca3ec848 --- /dev/null +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe Gitlab::Gfm::ReferenceRewriter do + let(:text) { 'some text' } + let(:old_project) { create(:project) } + let(:new_project) { create(:project) } + let(:user) { create(:user) } + + before { old_project.team << [user, :guest] } + + describe '#rewrite' do + subject do + described_class.new(text, old_project, user).rewrite(new_project) + end + + context 'multiple issues and merge requests referenced' do + let!(:issue_first) { create(:issue, project: old_project) } + let!(:issue_second) { create(:issue, project: old_project) } + let!(:merge_request) { create(:merge_request, source_project: old_project) } + + context 'plain text description' do + let(:text) { 'Description that references #1, #2 and !1' } + + it { is_expected.to include issue_first.to_reference(new_project) } + it { is_expected.to include issue_second.to_reference(new_project) } + it { is_expected.to include merge_request.to_reference(new_project) } + end + + context 'description with ignored elements' do + let(:text) do + "Hi. This references #1, but not `#2`\n" + + '<pre>and not !1</pre>' + end + + it { is_expected.to include issue_first.to_reference(new_project) } + it { is_expected.to_not include issue_second.to_reference(new_project) } + it { is_expected.to_not include merge_request.to_reference(new_project) } + end + + context 'description ambigous elements' do + context 'url' do + let(:url) { 'http://gitlab.com/#1' } + let(:text) { "This references #1, but not #{url}" } + + it { is_expected.to include url } + end + + context 'code' do + let(:text) { "#1, but not `[#1]`" } + it { is_expected.to eq "#{issue_first.to_reference(new_project)}, but not `[#1]`" } + end + + context 'code reverse' do + let(:text) { "not `#1`, but #1" } + it { is_expected.to eq "not `#1`, but #{issue_first.to_reference(new_project)}" } + end + + context 'code in random order' do + let(:text) { "#1, `#1`, #1, `#1`" } + let(:ref) { issue_first.to_reference(new_project) } + + it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" } + end + + context 'description with labels' do + let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } + let(:project_ref) { old_project.to_reference } + + context 'label referenced by id' do + let(:text) { '#1 and ~123' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } + end + + context 'label referenced by text' do + let(:text) { '#1 and ~"test"' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } + end + end + end + + context 'reference contains milestone' do + let(:milestone) { create(:milestone) } + let(:text) { "milestone ref: #{milestone.to_reference}" } + + it { is_expected.to eq text } + end + end + end +end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 65af37e24f1..7c617723e6d 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -124,4 +124,24 @@ describe Gitlab::ReferenceExtractor, lib: true do expect(extracted).to match_array([issue]) end end + + describe '#all' do + let(:issue) { create(:issue, project: project) } + let(:label) { create(:label, project: project) } + let(:text) { "Ref. #{issue.to_reference} and #{label.to_reference}" } + + before do + project.team << [project.creator, :developer] + subject.analyze(text) + end + + it 'returns all referables' do + expect(subject.all).to match_array([issue, label]) + end + end + + describe '.references_pattern' do + subject { described_class.references_pattern } + it { is_expected.to be_kind_of Regexp } + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index f910424d85b..9b47acfe0cd 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -158,6 +158,33 @@ describe Notify do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end end + + describe 'moved to another project' do + let(:new_issue) { create(:issue) } + subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) } + + it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'contains description about action taken' do + is_expected.to have_body_text 'Issue was moved to another project' + end + + it 'has the correct subject' do + is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i + end + + it 'contains link to new issue' do + new_issue_url = namespace_project_issue_path(new_issue.project.namespace, + new_issue.project, new_issue) + is_expected.to have_body_text new_issue_url + end + + it 'contains a link to the original issue' do + is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ + end + end end context 'for merge requests' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 540a62eb1f8..3c34b1d397f 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -37,6 +37,11 @@ describe Issue, models: true do subject { create(:issue) } + describe "act_as_paranoid" do + it { is_expected.to have_db_column(:deleted_at) } + it { is_expected.to have_db_index(:deleted_at) } + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(subject.to_reference).to eq "##{subject.iid}" @@ -130,12 +135,62 @@ describe Issue, models: true do end end + describe '#can_move?' do + let(:user) { create(:user) } + let(:issue) { create(:issue) } + subject { issue.can_move?(user) } + + context 'user is not a member of project issue belongs to' do + it { is_expected.to eq false} + end + + context 'user is reporter in project issue belongs to' do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + + before { project.team << [user, :reporter] } + + it { is_expected.to eq true } + + context 'checking destination project also' do + subject { issue.can_move?(user, to_project) } + let(:to_project) { create(:project) } + + context 'destination project allowed' do + before { to_project.team << [user, :reporter] } + it { is_expected.to eq true } + end + + context 'destination project not allowed' do + before { to_project.team << [user, :guest] } + it { is_expected.to eq false } + end + end + end + end + + describe '#moved?' do + let(:issue) { create(:issue) } + subject { issue.moved? } + + context 'issue not moved' do + it { is_expected.to eq false } + end + + context 'issue already moved' do + let(:moved_to_issue) { create(:issue) } + let(:issue) { create(:issue, moved_to: moved_to_issue) } + + it { is_expected.to eq true } + end + end + describe '#related_branches' do it "selects the right branches" do allow(subject.project.repository).to receive(:branch_names). - and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) + and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) - expect(subject.related_branches).to eq [subject.to_branch_name] + expect(subject.related_branches).to eq([subject.to_branch_name]) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f2f07e4ee17..bd0a4ebe337 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -49,6 +49,11 @@ describe MergeRequest, models: true do it { is_expected.to include_module(Taskable) } end + describe "act_as_paranoid" do + it { is_expected.to have_db_column(:deleted_at) } + it { is_expected.to have_db_index(:deleted_at) } + end + describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dba7ffc8565..757324184bd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -732,4 +732,45 @@ describe Project, models: true do it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey } end end + + describe '#create_repository' do + let(:project) { create(:project) } + let(:shell) { Gitlab::Shell.new } + + before do + allow(project).to receive(:gitlab_shell).and_return(shell) + end + + context 'using a regular repository' do + it 'creates the repository' do + expect(shell).to receive(:add_repository). + with(project.path_with_namespace). + and_return(true) + + expect(project.repository).to receive(:after_create) + + expect(project.create_repository).to eq(true) + end + + it 'adds an error if the repository could not be created' do + expect(shell).to receive(:add_repository). + with(project.path_with_namespace). + and_return(false) + + expect(project.repository).not_to receive(:after_create) + + expect(project.create_repository).to eq(false) + expect(project.errors).not_to be_empty + end + end + + context 'using a forked repository' do + it 'does nothing' do + expect(project).to receive(:forked?).and_return(true) + expect(shell).not_to receive(:add_repository) + + project.create_repository + end + end + end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index a2085df5bcd..532e3f013fd 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -244,6 +244,18 @@ describe ProjectWiki, models: true do end end + describe '#create_repo!' do + it 'creates a repository' do + expect(subject).to receive(:init_repo). + with(subject.path_with_namespace). + and_return(true) + + expect(subject.repository).to receive(:after_create) + + expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki) + end + end + private def create_temp_repo(path) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index a57229a4fdf..7eac70ae948 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -537,6 +537,12 @@ describe Repository, models: true do repository.before_delete end + + it 'flushes the exists cache' do + expect(repository).to receive(:expire_exists_cache) + + repository.before_delete + end end describe 'when a repository exists' do @@ -593,6 +599,12 @@ describe Repository, models: true do repository.after_import end + + it 'flushes the exists cache' do + expect(repository).to receive(:expire_exists_cache) + + repository.after_import + end end describe '#after_push_commit' do @@ -619,6 +631,14 @@ describe Repository, models: true do end end + describe '#after_create' do + it 'flushes the exists cache' do + expect(repository).to receive(:expire_exists_cache) + + repository.after_create + end + end + describe "#main_language" do it 'shows the main language of the project' do expect(repository.main_language).to eq("Ruby") @@ -781,6 +801,16 @@ describe Repository, models: true do end end + describe '#expire_exists_cache' do + let(:cache) { repository.send(:cache) } + + it 'expires the cache' do + expect(cache).to receive(:expire).with(:exists?) + + repository.expire_exists_cache + end + end + describe '#build_cache' do let(:cache) { repository.send(:cache) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index bb2ab058003..ce55cb7b0ae 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - let(:user) { create(:user) } - let(:non_member) { create(:user) } - let(:author) { create(:author) } - let(:assignee) { create(:assignee) } - let(:admin) { create(:admin) } - let!(:project) { create(:project, :public, namespace: user.namespace ) } + let(:user) { create(:user) } + let(:non_member) { create(:user) } + let(:author) { create(:author) } + let(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + let!(:project) { create(:project, :public, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -469,9 +469,25 @@ describe API::API, api: true do end describe "DELETE /projects/:id/issues/:issue_id" do - it "should delete a project issue" do - delete api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response.status).to eq(405) + it "rejects a non member from deleting an issue" do + delete api("/projects/#{project.id}/issues/#{issue.id}", non_member) + expect(response.status).to be(403) + end + + it "rejects a developer from deleting an issue" do + delete api("/projects/#{project.id}/issues/#{issue.id}", author) + expect(response.status).to be(403) + end + + context "when the user is project owner" do + let(:owner) { create(:user) } + let(:project) { create(:project, namespace: owner.namespace) } + + it "deletes the issue if an admin requests it" do + delete api("/projects/#{project.id}/issues/#{issue.id}", owner) + expect(response.status).to eq(200) + expect(json_response['state']).to eq 'opened' + end end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4fd1df25568..c9175a4d6eb 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -2,15 +2,17 @@ require "spec_helper" describe API::API, api: true do include ApiHelpers - let(:base_time) { Time.now } - let(:user) { create(:user) } - let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } + let(:base_time) { Time.now } + let(:user) { create(:user) } + let(:admin) { create(:user, :admin) } + let(:non_member) { create(:user) } + let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) } - let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } - let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do project.team << [user, :reporters] @@ -315,6 +317,29 @@ describe API::API, api: true do end end + describe "DELETE /projects/:id/merge_requests/:merge_request_id" do + context "when the user is developer" do + let(:developer) { create(:user) } + + before do + project.team << [developer, :developer] + end + + it "denies the deletion of the merge request" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) + expect(response.status).to be(403) + end + end + + context "when the user is project owner" do + it "destroys the merge request owners can destroy" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) + + expect(response.status).to eq(200) + end + end + end + describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do it "should return merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb new file mode 100644 index 00000000000..14cc20e529a --- /dev/null +++ b/spec/services/issues/move_service_spec.rb @@ -0,0 +1,213 @@ +require 'spec_helper' + +describe Issues::MoveService, services: true do + let(:user) { create(:user) } + let(:author) { create(:user) } + let(:title) { 'Some issue' } + let(:description) { 'Some issue description' } + let(:old_project) { create(:project) } + let(:new_project) { create(:project) } + + let(:old_issue) do + create(:issue, title: title, description: description, + project: old_project, author: author) + end + + let(:move_service) do + described_class.new(old_project, user) + end + + shared_context 'user can move issue' do + before do + old_project.team << [user, :reporter] + new_project.team << [user, :reporter] + end + end + + describe '#execute' do + shared_context 'issue move executed' do + let!(:new_issue) { move_service.execute(old_issue, new_project) } + end + + context 'issue movable' do + include_context 'user can move issue' + + context 'generic issue' do + include_context 'issue move executed' + + it 'creates a new issue in a new project' do + expect(new_issue.project).to eq new_project + end + + it 'rewrites issue title' do + expect(new_issue.title).to eq title + end + + it 'rewrites issue description' do + expect(new_issue.description).to eq description + end + + it 'adds system note to old issue at the end' do + expect(old_issue.notes.last.note).to match /^Moved to/ + end + + it 'adds system note to new issue at the end' do + expect(new_issue.notes.last.note).to match /^Moved from/ + end + + it 'closes old issue' do + expect(old_issue.closed?).to be true + end + + it 'persists new issue' do + expect(new_issue.persisted?).to be true + end + + it 'persists all changes' do + expect(old_issue.changed?).to be false + expect(new_issue.changed?).to be false + end + + it 'preserves author' do + expect(new_issue.author).to eq author + end + + it 'removes data that is invalid in new context' do + expect(new_issue.milestone).to be_nil + expect(new_issue.labels).to be_empty + end + + it 'creates a new internal id for issue' do + expect(new_issue.iid).to be 1 + end + + it 'marks issue as moved' do + expect(old_issue.moved?).to eq true + expect(old_issue.moved_to).to eq new_issue + end + end + + context 'issue with notes' do + context 'notes without references' do + let(:notes_params) do + [{ system: false, note: 'Some comment 1' }, + { system: true, note: 'Some system note' }, + { system: false, note: 'Some comment 2' }] + end + + let(:notes_contents) { notes_params.map { |n| n[:note] } } + + before do + note_params = { noteable: old_issue, project: old_project, author: author } + notes_params.each do |note| + create(:note, note_params.merge(note)) + end + end + + include_context 'issue move executed' + + let(:all_notes) { new_issue.notes.order('id ASC') } + let(:system_notes) { all_notes.system } + let(:user_notes) { all_notes.user } + + it 'rewrites existing notes in valid order' do + expect(all_notes.pluck(:note).first(3)).to eq notes_contents + end + + it 'adds a system note about move after rewritten notes' do + expect(system_notes.last.note).to match /^Moved from/ + end + + it 'preserves orignal author of comment' do + expect(user_notes.pluck(:author_id)).to all(eq(author.id)) + end + + it 'preserves time when note has been created at' do + expect(old_issue.notes.first.created_at) + .to eq new_issue.notes.first.created_at + end + end + + context 'notes with references' do + before do + create(:merge_request, source_project: old_project) + create(:note, noteable: old_issue, project: old_project, author: author, + note: 'Note with reference to merge request !1') + end + + include_context 'issue move executed' + let(:new_note) { new_issue.notes.first } + + it 'rewrites references using a cross reference to old project' do + expect(new_note.note) + .to eq "Note with reference to merge request #{old_project.to_reference}!1" + end + end + end + + describe 'rewritting references' do + include_context 'issue move executed' + + context 'issue reference' do + let(:another_issue) { create(:issue, project: old_project) } + let(:description) { "Some description #{another_issue.to_reference}" } + + it 'rewrites referenced issues creating cross project reference' do + expect(new_issue.description) + .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" + end + end + end + + context 'moving to same project' do + let(:new_project) { old_project } + + it 'raises error' do + expect { move_service.execute(old_issue, new_project) } + .to raise_error(StandardError, /Cannot move issue/) + end + end + end + + describe 'move permissions' do + let(:move) { move_service.execute(old_issue, new_project) } + + context 'user is reporter in both projects' do + include_context 'user can move issue' + it { expect { move }.to_not raise_error } + end + + context 'user is reporter only in new project' do + before { new_project.team << [user, :reporter] } + it { expect { move }.to raise_error(StandardError, /permissions/) } + end + + context 'user is reporter only in old project' do + before { old_project.team << [user, :reporter] } + it { expect { move }.to raise_error(StandardError, /permissions/) } + end + + context 'user is reporter in one project and guest in another' do + before do + new_project.team << [user, :guest] + old_project.team << [user, :reporter] + end + + it { expect { move }.to raise_error(StandardError, /permissions/) } + end + + context 'issue has already been moved' do + include_context 'user can move issue' + + let(:moved_to_issue) { create(:issue) } + + let(:old_issue) do + create(:issue, project: old_project, author: author, + moved_to: moved_to_issue) + end + + it { expect { move }.to raise_error(StandardError, /permissions/) } + end + end + end +end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 8e6292014d4..240eae10052 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -453,6 +453,59 @@ describe SystemNoteService, services: true do end end + describe '.noteable_moved' do + let(:new_project) { create(:project) } + let(:new_noteable) { create(:issue, project: new_project) } + + subject do + described_class.noteable_moved(noteable, project, new_noteable, author, direction: direction) + end + + shared_examples 'cross project mentionable' do + include GitlabMarkdownHelper + + it 'should contain cross reference to new noteable' do + expect(subject.note).to include cross_project_reference(new_project, new_noteable) + end + + it 'should mention referenced noteable' do + expect(subject.note).to include new_noteable.to_reference + end + + it 'should mention referenced project' do + expect(subject.note).to include new_project.to_reference + end + end + + context 'moved to' do + let(:direction) { :to } + + it_behaves_like 'cross project mentionable' + + it 'should notify about noteable being moved to' do + expect(subject.note).to match /Moved to/ + end + end + + context 'moved from' do + let(:direction) { :from } + + it_behaves_like 'cross project mentionable' + + it 'should notify about noteable being moved from' do + expect(subject.note).to match /Moved from/ + end + end + + context 'invalid direction' do + let(:direction) { :invalid } + + it 'should raise error' do + expect { subject }.to raise_error StandardError, /Invalid direction/ + end + end + end + include JiraServiceHelper describe 'JIRA integration' do diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 172537474ee..4ef05eb29d2 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -3,12 +3,17 @@ require 'spec_helper' describe RepositoryForkWorker do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } + let(:shell) { Gitlab::Shell.new } subject { RepositoryForkWorker.new } + before do + allow(subject).to receive(:gitlab_shell).and_return(shell) + end + describe "#perform" do it "creates a new repository from a fork" do - expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with( + expect(shell).to receive(:fork_repository).with( project.path_with_namespace, fork_project.namespace.path ).and_return(true) @@ -19,20 +24,26 @@ describe RepositoryForkWorker do fork_project.namespace.path) end - it 'flushes the empty caches' do - expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository). + it 'flushes various caches' do + expect(shell).to receive(:fork_repository). with(project.path_with_namespace, fork_project.namespace.path). and_return(true) expect_any_instance_of(Repository).to receive(:expire_emptiness_caches). and_call_original + expect_any_instance_of(Repository).to receive(:expire_exists_cache). + and_call_original + subject.perform(project.id, project.path_with_namespace, fork_project.namespace.path) end it "handles bad fork" do - expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false) + expect(shell).to receive(:fork_repository).and_return(false) + + expect(subject.logger).to receive(:error) + subject.perform( project.id, project.path_with_namespace, |