diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-21 03:10:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-21 03:10:44 +0300 |
commit | c7a46b04196859929e8e4c04fbcbf8490f228edf (patch) | |
tree | d378b8cdd9f49903ed6f61810f61fb61217b6e3e /spec/services | |
parent | 5c42c9355afa2bd5f95000b294ae6053f1d9219f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/services')
-rw-r--r-- | spec/services/issue_links/create_service_spec.rb | 176 | ||||
-rw-r--r-- | spec/services/issue_links/destroy_service_spec.rb | 61 | ||||
-rw-r--r-- | spec/services/issue_links/list_service_spec.rb | 194 | ||||
-rw-r--r-- | spec/services/issues/duplicate_service_spec.rb | 11 | ||||
-rw-r--r-- | spec/services/issues/move_service_spec.rb | 39 | ||||
-rw-r--r-- | spec/services/notes/quick_actions_service_spec.rb | 36 | ||||
-rw-r--r-- | spec/services/quick_actions/interpret_service_spec.rb | 97 | ||||
-rw-r--r-- | spec/services/system_note_service_spec.rb | 34 | ||||
-rw-r--r-- | spec/services/system_notes/issuables_service_spec.rb | 32 |
9 files changed, 677 insertions, 3 deletions
diff --git a/spec/services/issue_links/create_service_spec.rb b/spec/services/issue_links/create_service_spec.rb new file mode 100644 index 00000000000..a60950e64cb --- /dev/null +++ b/spec/services/issue_links/create_service_spec.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IssueLinks::CreateService do + describe '#execute' do + let(:namespace) { create :namespace } + let(:project) { create :project, namespace: namespace } + let(:issue) { create :issue, project: project } + let(:user) { create :user } + let(:params) do + {} + end + + before do + project.add_developer(user) + end + + subject { described_class.new(issue, user, params).execute } + + context 'when the reference list is empty' do + let(:params) do + { issuable_references: [] } + end + + it 'returns error' do + is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404) + end + end + + context 'when Issue not found' do + let(:params) do + { issuable_references: ["##{non_existing_record_iid}"] } + end + + it 'returns error' do + is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404) + end + + it 'no relationship is created' do + expect { subject }.not_to change(IssueLink, :count) + end + end + + context 'when user has no permission to target project Issue' do + let(:target_issuable) { create :issue } + + let(:params) do + { issuable_references: [target_issuable.to_reference(project)] } + end + + it 'returns error' do + target_issuable.project.add_guest(user) + + is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404) + end + + it 'no relationship is created' do + expect { subject }.not_to change(IssueLink, :count) + end + end + + context 'source and target are the same issue' do + let(:params) do + { issuable_references: [issue.to_reference] } + end + + it 'does not create notes' do + expect(SystemNoteService).not_to receive(:relate_issue) + + subject + end + + it 'no relationship is created' do + expect { subject }.not_to change(IssueLink, :count) + end + end + + context 'when there is an issue to relate' do + let(:issue_a) { create :issue, project: project } + let(:another_project) { create :project, namespace: project.namespace } + let(:another_project_issue) { create :issue, project: another_project } + + let(:issue_a_ref) { issue_a.to_reference } + let(:another_project_issue_ref) { another_project_issue.to_reference(project) } + + let(:params) do + { issuable_references: [issue_a_ref, another_project_issue_ref] } + end + + before do + another_project.add_developer(user) + end + + it 'creates relationships' do + expect { subject }.to change(IssueLink, :count).from(0).to(2) + + expect(IssueLink.find_by!(target: issue_a)).to have_attributes(source: issue, link_type: 'relates_to') + expect(IssueLink.find_by!(target: another_project_issue)).to have_attributes(source: issue, link_type: 'relates_to') + end + + it 'returns success status' do + is_expected.to eq(status: :success) + end + + it 'creates notes' do + # First two-way relation notes + expect(SystemNoteService).to receive(:relate_issue) + .with(issue, issue_a, user) + expect(SystemNoteService).to receive(:relate_issue) + .with(issue_a, issue, user) + + # Second two-way relation notes + expect(SystemNoteService).to receive(:relate_issue) + .with(issue, another_project_issue, user) + expect(SystemNoteService).to receive(:relate_issue) + .with(another_project_issue, issue, user) + + subject + end + end + + context 'when reference of any already related issue is present' do + let(:issue_a) { create :issue, project: project } + let(:issue_b) { create :issue, project: project } + let(:issue_c) { create :issue, project: project } + + before do + create :issue_link, source: issue, target: issue_b, link_type: IssueLink::TYPE_RELATES_TO + create :issue_link, source: issue, target: issue_c, link_type: IssueLink::TYPE_RELATES_TO + end + + let(:params) do + { + issuable_references: [ + issue_a.to_reference, + issue_b.to_reference, + issue_c.to_reference + ], + link_type: IssueLink::TYPE_RELATES_TO + } + end + + it 'creates notes only for new relations' do + expect(SystemNoteService).to receive(:relate_issue).with(issue, issue_a, anything) + expect(SystemNoteService).to receive(:relate_issue).with(issue_a, issue, anything) + expect(SystemNoteService).not_to receive(:relate_issue).with(issue, issue_b, anything) + expect(SystemNoteService).not_to receive(:relate_issue).with(issue_b, issue, anything) + expect(SystemNoteService).not_to receive(:relate_issue).with(issue, issue_c, anything) + expect(SystemNoteService).not_to receive(:relate_issue).with(issue_c, issue, anything) + + subject + end + end + + context 'when there are invalid references' do + let(:issue_a) { create :issue, project: project } + + let(:params) do + { issuable_references: [issue.to_reference, issue_a.to_reference] } + end + + it 'creates links only for valid references' do + expect { subject }.to change { IssueLink.count }.by(1) + end + + it 'returns error status' do + expect(subject).to eq( + status: :error, + http_status: 422, + message: "#{issue.to_reference} cannot be added: cannot be related to itself" + ) + end + end + end +end diff --git a/spec/services/issue_links/destroy_service_spec.rb b/spec/services/issue_links/destroy_service_spec.rb new file mode 100644 index 00000000000..3f5b7ccf9ba --- /dev/null +++ b/spec/services/issue_links/destroy_service_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IssueLinks::DestroyService do + describe '#execute' do + let(:project) { create(:project_empty_repo) } + let(:user) { create(:user) } + + subject { described_class.new(issue_link, user).execute } + + context 'when successfully removes an issue link' do + let(:issue_a) { create(:issue, project: project) } + let(:issue_b) { create(:issue, project: project) } + + let!(:issue_link) { create(:issue_link, source: issue_a, target: issue_b) } + + before do + project.add_reporter(user) + end + + it 'removes related issue' do + expect { subject }.to change(IssueLink, :count).from(1).to(0) + end + + it 'creates notes' do + # Two-way notes creation + expect(SystemNoteService).to receive(:unrelate_issue) + .with(issue_link.source, issue_link.target, user) + expect(SystemNoteService).to receive(:unrelate_issue) + .with(issue_link.target, issue_link.source, user) + + subject + end + + it 'returns success message' do + is_expected.to eq(message: 'Relation was removed', status: :success) + end + end + + context 'when failing to remove an issue link' do + let(:unauthorized_project) { create(:project) } + let(:issue_a) { create(:issue, project: project) } + let(:issue_b) { create(:issue, project: unauthorized_project) } + + let!(:issue_link) { create(:issue_link, source: issue_a, target: issue_b) } + + it 'does not remove relation' do + expect { subject }.not_to change(IssueLink, :count).from(1) + end + + it 'does not create notes' do + expect(SystemNoteService).not_to receive(:unrelate_issue) + end + + it 'returns error message' do + is_expected.to eq(message: 'No Issue Link found', status: :error, http_status: 404) + end + end + end +end diff --git a/spec/services/issue_links/list_service_spec.rb b/spec/services/issue_links/list_service_spec.rb new file mode 100644 index 00000000000..7a3ba845c7c --- /dev/null +++ b/spec/services/issue_links/list_service_spec.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IssueLinks::ListService do + let(:user) { create :user } + let(:project) { create(:project_empty_repo, :private) } + let(:issue) { create :issue, project: project } + let(:user_role) { :developer } + + before do + project.add_role(user, user_role) + end + + describe '#execute' do + subject { described_class.new(issue, user).execute } + + context 'user can see all issues' do + let(:issue_b) { create :issue, project: project } + let(:issue_c) { create :issue, project: project } + let(:issue_d) { create :issue, project: project } + + let!(:issue_link_c) do + create(:issue_link, source: issue_d, + target: issue) + end + + let!(:issue_link_b) do + create(:issue_link, source: issue, + target: issue_c) + end + + let!(:issue_link_a) do + create(:issue_link, source: issue, + target: issue_b) + end + + it 'ensures no N+1 queries are made' do + control_count = ActiveRecord::QueryRecorder.new { subject }.count + + project = create :project, :public + milestone = create :milestone, project: project + issue_x = create :issue, project: project, milestone: milestone + issue_y = create :issue, project: project, assignees: [user] + issue_z = create :issue, project: project + create :issue_link, source: issue_x, target: issue_y + create :issue_link, source: issue_x, target: issue_z + create :issue_link, source: issue_y, target: issue_z + + expect { subject }.not_to exceed_query_limit(control_count) + end + + it 'returns related issues JSON' do + expect(subject.size).to eq(3) + + expect(subject).to include(include(id: issue_b.id, + title: issue_b.title, + state: issue_b.state, + reference: issue_b.to_reference(project), + path: "/#{project.full_path}/-/issues/#{issue_b.iid}", + relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_a.id}")) + + expect(subject).to include(include(id: issue_c.id, + title: issue_c.title, + state: issue_c.state, + reference: issue_c.to_reference(project), + path: "/#{project.full_path}/-/issues/#{issue_c.iid}", + relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_b.id}")) + + expect(subject).to include(include(id: issue_d.id, + title: issue_d.title, + state: issue_d.state, + reference: issue_d.to_reference(project), + path: "/#{project.full_path}/-/issues/#{issue_d.iid}", + relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_c.id}")) + end + end + + context 'referencing a public project issue' do + let(:public_project) { create :project, :public } + let(:issue_b) { create :issue, project: public_project } + + let!(:issue_link) do + create(:issue_link, source: issue, target: issue_b) + end + + it 'presents issue' do + expect(subject.size).to eq(1) + end + end + + context 'referencing issue with removed relationships' do + context 'when referenced a deleted issue' do + let(:issue_b) { create :issue, project: project } + let!(:issue_link) do + create(:issue_link, source: issue, target: issue_b) + end + + it 'ignores issue' do + issue_b.destroy! + + is_expected.to eq([]) + end + end + + context 'when referenced an issue with deleted project' do + let(:issue_b) { create :issue, project: project } + let!(:issue_link) do + create(:issue_link, source: issue, target: issue_b) + end + + it 'ignores issue' do + project.destroy! + + is_expected.to eq([]) + end + end + + context 'when referenced an issue with deleted namespace' do + let(:issue_b) { create :issue, project: project } + let!(:issue_link) do + create(:issue_link, source: issue, target: issue_b) + end + + it 'ignores issue' do + project.namespace.destroy! + + is_expected.to eq([]) + end + end + end + + context 'user cannot see relations' do + context 'when user cannot see the referenced issue' do + let!(:issue_link) do + create(:issue_link, source: issue) + end + + it 'returns an empty list' do + is_expected.to eq([]) + end + end + + context 'when user cannot see the issue that referenced' do + let!(:issue_link) do + create(:issue_link, target: issue) + end + + it 'returns an empty list' do + is_expected.to eq([]) + end + end + end + + context 'remove relations' do + let!(:issue_link) do + create(:issue_link, source: issue, target: referenced_issue) + end + + context 'user can admin related issues just on target project' do + let(:user_role) { :guest } + let(:target_project) { create :project } + let(:referenced_issue) { create :issue, project: target_project } + + it 'returns no destroy relation path' do + target_project.add_developer(user) + + expect(subject.first[:relation_path]).to be_nil + end + end + + context 'user can admin related issues just on source project' do + let(:user_role) { :developer } + let(:target_project) { create :project } + let(:referenced_issue) { create :issue, project: target_project } + + it 'returns no destroy relation path' do + target_project.add_guest(user) + + expect(subject.first[:relation_path]).to be_nil + end + end + + context 'when user can admin related issues on both projects' do + let(:referenced_issue) { create :issue, project: project } + + it 'returns related issue destroy relation path' do + expect(subject.first[:relation_path]) + .to eq("/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link.id}") + end + end + end + end +end diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb index 78e030e6ac7..0b5bc3f32ef 100644 --- a/spec/services/issues/duplicate_service_spec.rb +++ b/spec/services/issues/duplicate_service_spec.rb @@ -83,6 +83,17 @@ RSpec.describe Issues::DuplicateService do expect(duplicate_issue.reload.duplicated_to).to eq(canonical_issue) end + + it 'relates the duplicate issues' do + canonical_project.add_reporter(user) + duplicate_project.add_reporter(user) + + subject.execute(duplicate_issue, canonical_issue) + + issue_link = IssueLink.last + expect(issue_link.source).to eq(duplicate_issue) + expect(issue_link.target).to eq(canonical_issue) + end end end end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 5f944d1213b..5d032b28399 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -223,6 +223,45 @@ RSpec.describe Issues::MoveService do end end + describe '#rewrite_related_issues' do + include_context 'user can move issue' + + let(:admin) { create(:admin) } + let(:authorized_project) { create(:project) } + let(:authorized_project2) { create(:project) } + let(:unauthorized_project) { create(:project) } + + let(:authorized_issue_b) { create(:issue, project: authorized_project) } + let(:authorized_issue_c) { create(:issue, project: authorized_project2) } + let(:authorized_issue_d) { create(:issue, project: authorized_project2) } + let(:unauthorized_issue) { create(:issue, project: unauthorized_project) } + + let!(:issue_link_a) { create(:issue_link, source: old_issue, target: authorized_issue_b) } + let!(:issue_link_b) { create(:issue_link, source: old_issue, target: unauthorized_issue) } + let!(:issue_link_c) { create(:issue_link, source: old_issue, target: authorized_issue_c) } + let!(:issue_link_d) { create(:issue_link, source: authorized_issue_d, target: old_issue) } + + before do + authorized_project.add_developer(user) + authorized_project2.add_developer(user) + end + + context 'multiple related issues' do + it 'moves all related issues and retains permissions' do + new_issue = move_service.execute(old_issue, new_project) + + expect(new_issue.related_issues(admin)) + .to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d, unauthorized_issue]) + + expect(new_issue.related_issues(user)) + .to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d]) + + expect(authorized_issue_d.related_issues(user)) + .to match_array([new_issue]) + end + end + end + context 'updating sent notifications' do let!(:old_issue_notification_1) { create(:sent_notification, project: old_issue.project, noteable: old_issue) } let!(:old_issue_notification_2) { create(:sent_notification, project: old_issue.project, noteable: old_issue) } diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index e9decd44730..794491fc50d 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -4,9 +4,9 @@ require 'spec_helper' RSpec.describe Notes::QuickActionsService do shared_context 'note on noteable' do - let(:project) { create(:project, :repository) } - let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } } - let(:assignee) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } } + let_it_be(:assignee) { create(:user) } before do project.add_maintainer(assignee) @@ -41,6 +41,36 @@ RSpec.describe Notes::QuickActionsService do end end + context '/relate' do + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:other_issue) { create(:issue, project: project) } + let(:note_text) { "/relate #{other_issue.to_reference}" } + let(:note) { create(:note_on_issue, noteable: issue, project: project, note: note_text) } + + context 'user cannot relate issues' do + before do + project.team.find_member(maintainer.id).destroy! + project.update!(visibility: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'does not create issue relation' do + expect do + _, update_params = service.execute(note) + service.apply_updates(update_params, note) + end.not_to change { IssueLink.count } + end + end + + context 'user is allowed to relate issues' do + it 'creates issue relation' do + expect do + _, update_params = service.execute(note) + service.apply_updates(update_params, note) + end.to change { IssueLink.count }.by(1) + end + end + end + describe '/reopen' do before do note.noteable.close! diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 57e32b1aea9..b970a48051f 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1644,6 +1644,103 @@ RSpec.describe QuickActions::InterpretService do end end end + + context 'relate command' do + let_it_be_with_refind(:group) { create(:group) } + + shared_examples 'relate command' do + it 'relates issues' do + service.execute(content, issue) + + expect(IssueLink.where(source: issue).map(&:target)).to match_array(issues_related) + end + end + + context 'user is member of group' do + before do + group.add_developer(developer) + end + + context 'relate a single issue' do + let(:other_issue) { create(:issue, project: project) } + let(:issues_related) { [other_issue] } + let(:content) { "/relate #{other_issue.to_reference}" } + + it_behaves_like 'relate command' + end + + context 'relate multiple issues at once' do + let(:second_issue) { create(:issue, project: project) } + let(:third_issue) { create(:issue, project: project) } + let(:issues_related) { [second_issue, third_issue] } + let(:content) { "/relate #{second_issue.to_reference} #{third_issue.to_reference}" } + + it_behaves_like 'relate command' + end + + context 'empty relate command' do + let(:issues_related) { [] } + let(:content) { '/relate' } + + it_behaves_like 'relate command' + end + + context 'already having related issues' do + let(:second_issue) { create(:issue, project: project) } + let(:third_issue) { create(:issue, project: project) } + let(:issues_related) { [second_issue, third_issue] } + let(:content) { "/relate #{third_issue.to_reference(project)}" } + + before do + create(:issue_link, source: issue, target: second_issue) + end + + it_behaves_like 'relate command' + end + + context 'cross project' do + let(:another_group) { create(:group, :public) } + let(:other_project) { create(:project, group: another_group) } + + before do + another_group.add_developer(developer) + end + + context 'relate a cross project issue' do + let(:other_issue) { create(:issue, project: other_project) } + let(:issues_related) { [other_issue] } + let(:content) { "/relate #{other_issue.to_reference(project)}" } + + it_behaves_like 'relate command' + end + + context 'relate multiple cross projects issues at once' do + let(:second_issue) { create(:issue, project: other_project) } + let(:third_issue) { create(:issue, project: other_project) } + let(:issues_related) { [second_issue, third_issue] } + let(:content) { "/relate #{second_issue.to_reference(project)} #{third_issue.to_reference(project)}" } + + it_behaves_like 'relate command' + end + + context 'relate a non-existing issue' do + let(:issues_related) { [] } + let(:content) { "/relate imaginary##{non_existing_record_iid}" } + + it_behaves_like 'relate command' + end + + context 'relate a private issue' do + let(:private_project) { create(:project, :private) } + let(:other_issue) { create(:issue, project: private_project) } + let(:issues_related) { [] } + let(:content) { "/relate #{other_issue.to_reference(project)}" } + + it_behaves_like 'relate command' + end + end + end + end end describe '#explain' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 969e5955609..0f5558738ac 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -86,6 +86,40 @@ RSpec.describe SystemNoteService do end end + describe '.relate_issue' do + let(:noteable_ref) { double } + let(:noteable) { double } + + before do + allow(noteable).to receive(:project).and_return(double) + end + + it 'calls IssuableService' do + expect_next_instance_of(::SystemNotes::IssuablesService) do |service| + expect(service).to receive(:relate_issue).with(noteable_ref) + end + + described_class.relate_issue(noteable, noteable_ref, double) + end + end + + describe '.unrelate_issue' do + let(:noteable_ref) { double } + let(:noteable) { double } + + before do + allow(noteable).to receive(:project).and_return(double) + end + + it 'calls IssuableService' do + expect_next_instance_of(::SystemNotes::IssuablesService) do |service| + expect(service).to receive(:unrelate_issue).with(noteable_ref) + end + + described_class.unrelate_issue(noteable, noteable_ref, double) + end + end + describe '.change_due_date' do let(:due_date) { double } diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb index 1b5b26d90da..536e9792534 100644 --- a/spec/services/system_notes/issuables_service_spec.rb +++ b/spec/services/system_notes/issuables_service_spec.rb @@ -13,6 +13,38 @@ RSpec.describe ::SystemNotes::IssuablesService do let(:service) { described_class.new(noteable: noteable, project: project, author: author) } + describe '#relate_issue' do + let(:noteable_ref) { create(:issue) } + + subject { service.relate_issue(noteable_ref) } + + it_behaves_like 'a system note' do + let(:action) { 'relate' } + end + + context 'when issue marks another as related' do + it 'sets the note text' do + expect(subject.note).to eq "marked this issue as related to #{noteable_ref.to_reference(project)}" + end + end + end + + describe '#unrelate_issue' do + let(:noteable_ref) { create(:issue) } + + subject { service.unrelate_issue(noteable_ref) } + + it_behaves_like 'a system note' do + let(:action) { 'unrelate' } + end + + context 'when issue relation is removed' do + it 'sets the note text' do + expect(subject.note).to eq "removed the relation with #{noteable_ref.to_reference(project)}" + end + end + end + describe '#change_assignee' do subject { service.change_assignee(assignee) } |