diff options
Diffstat (limited to 'spec/services/work_items/parent_links/create_service_spec.rb')
-rw-r--r-- | spec/services/work_items/parent_links/create_service_spec.rb | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/spec/services/work_items/parent_links/create_service_spec.rb b/spec/services/work_items/parent_links/create_service_spec.rb new file mode 100644 index 00000000000..85b0ee040cd --- /dev/null +++ b/spec/services/work_items/parent_links/create_service_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::ParentLinks::CreateService do + describe '#execute' do + let_it_be(:user) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:work_item) { create(:work_item, project: project) } + let_it_be(:task) { create(:work_item, :task, project: project) } + let_it_be(:task1) { create(:work_item, :task, project: project) } + let_it_be(:task2) { create(:work_item, :task, project: project) } + let_it_be(:guest_task) { create(:work_item, :task) } + let_it_be(:invalid_task) { build_stubbed(:work_item, :task, id: non_existing_record_id)} + let_it_be(:another_project) { (create :project) } + let_it_be(:other_project_task) { create(:work_item, :task, iid: 100, project: another_project) } + let_it_be(:existing_parent_link) { create(:parent_link, work_item: task, work_item_parent: work_item)} + + let(:parent_link_class) { WorkItems::ParentLink } + let(:issuable_type) { :task } + let(:params) { {} } + + before do + project.add_reporter(user) + project.add_guest(guest) + guest_task.project.add_guest(user) + another_project.add_reporter(user) + end + + shared_examples 'returns not found error' do + it 'returns error' do + error = "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} ID." + + is_expected.to eq(service_error(error)) + end + + it 'no relationship is created' do + expect { subject }.not_to change(parent_link_class, :count) + end + end + + subject { described_class.new(work_item, user, params).execute } + + context 'when the reference list is empty' do + let(:params) { { issuable_references: [] } } + + it_behaves_like 'returns not found error' + end + + context 'when work item not found' do + let(:params) { { issuable_references: [invalid_task] } } + + it_behaves_like 'returns not found error' + end + + context 'when user has no permission to link work items' do + let(:params) { { issuable_references: [guest_task] } } + + it_behaves_like 'returns not found error' + end + + context 'child and parent are the same work item' do + let(:params) { { issuable_references: [work_item] } } + + it 'no relationship is created' do + expect { subject }.not_to change(parent_link_class, :count) + end + end + + context 'when there are tasks to relate' do + let(:params) { { issuable_references: [task1, task2] } } + + it 'creates relationships', :aggregate_failures do + expect { subject }.to change(parent_link_class, :count).by(2) + + tasks_parent = parent_link_class.where(work_item: [task1, task2]).map(&:work_item_parent).uniq + expect(tasks_parent).to match_array([work_item]) + end + + it 'returns success status and created links', :aggregate_failures do + expect(subject.keys).to match_array([:status, :created_references]) + expect(subject[:status]).to eq(:success) + expect(subject[:created_references].map(&:work_item_id)).to match_array([task1.id, task2.id]) + end + + context 'when task is already assigned' do + let(:params) { { issuable_references: [task, task2] } } + + it 'creates links only for non related tasks' do + expect { subject }.to change(parent_link_class, :count).by(1) + + expect(subject[:created_references].map(&:work_item_id)).to match_array([task2.id]) + end + end + + context 'when there are invalid children' do + let_it_be(:issue) { create(:work_item, project: project) } + + let(:params) { { issuable_references: [task1, issue, other_project_task] } } + + it 'creates links only for valid children' do + expect { subject }.to change { parent_link_class.count }.by(1) + end + + it 'returns error status' do + error = "#{issue.to_reference} cannot be added: only Task can be assigned as a child in hierarchy.. " \ + "#{other_project_task.to_reference} cannot be added: parent must be in the same project as child." + + is_expected.to eq(service_error(error, http_status: 422)) + end + end + + context 'when parent type is invalid' do + let(:work_item) { create :work_item, :task, project: project } + + let(:params) { { target_issuable: task1 } } + + it 'returns error status' do + error = "#{task1.to_reference} cannot be added: only Issue and Incident can be parent of Task." + + is_expected.to eq(service_error(error, http_status: 422)) + end + end + + context 'when max depth is reached' do + let(:params) { { issuable_references: [task2] } } + + before do + stub_const("#{parent_link_class}::MAX_CHILDREN", 1) + end + + it 'returns error status' do + error = "#{task2.to_reference} cannot be added: parent already has maximum number of children." + + is_expected.to eq(service_error(error, http_status: 422)) + end + end + + context 'when params include invalid ids' do + let(:params) { { issuable_references: [task1, invalid_task] } } + + it 'creates links only for valid IDs' do + expect { subject }.to change(parent_link_class, :count).by(1) + end + end + + context 'when user is a guest' do + let(:user) { guest } + + it_behaves_like 'returns not found error' + end + + context 'when user is a guest assigned to the work item' do + let(:user) { guest } + + before do + work_item.assignees = [guest] + end + + it_behaves_like 'returns not found error' + end + end + end + + def service_error(message, http_status: 404) + { + message: message, + status: :error, + http_status: http_status + } + end +end |