summaryrefslogtreecommitdiff
path: root/spec/services/work_items/parent_links/create_service_spec.rb
blob: 2f2e830845a84e29ae4016bff45088fc0ffa1c73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfolio_management 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

      it 'creates notes', :aggregate_failures do
        subject

        work_item_notes = work_item.notes.last(2)
        expect(work_item_notes.first.note).to eq("added #{task1.to_reference} as child task")
        expect(work_item_notes.last.note).to eq("added #{task2.to_reference} as child task")
        expect(task1.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
        expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
      end

      context 'when task is already assigned' do
        let(:params) { { issuable_references: [task, task2] } }

        it 'creates links only for non related tasks', :aggregate_failures do
          expect { subject }.to change(parent_link_class, :count).by(1)

          expect(subject[:created_references].map(&:work_item_id)).to match_array([task2.id])
          expect(work_item.notes.last.note).to eq("added #{task2.to_reference} as child task")
          expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
          expect(task.notes).to be_empty
        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: is not allowed to add this type of parent. " \
            "#{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

        it 'creates notes for valid links' do
          subject

          expect(work_item.notes.last.note).to eq("added #{task1.to_reference} as child task")
          expect(task1.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
          expect(issue.notes).to be_empty
          expect(other_project_task.notes).to be_empty
        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: is not allowed to add this type of parent"

          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