File: hierarchy_spec.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (233 lines) | stat: -rw-r--r-- 8,626 bytes parent folder | download
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::Callbacks::Hierarchy, feature_category: :portfolio_management do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project) }

  let_it_be(:work_item) { create(:work_item, project: project) }
  let_it_be(:parent_work_item) { create(:work_item, project: project) }
  let_it_be(:child_work_item) { create(:work_item, :task, project: project) }
  let_it_be(:existing_link) { create(:parent_link, work_item: child_work_item, work_item_parent: work_item) }

  let(:callback) { described_class.new(issuable: work_item, current_user: user, params: params) }
  let(:not_found_error) { 'No matching work item found. Make sure that you are adding a valid work item ID.' }

  shared_examples 'raises a callback error' do |message_arg|
    it { expect { subject }.to raise_error(::Issuable::Callbacks::Base::Error, message_arg || message) }
  end

  describe '#after_create' do
    subject { callback.after_create }

    context 'when invalid params are present' do
      let(:params) { { other_parent: 'parent_work_item' } }

      it_behaves_like 'raises a callback error', 'One or more arguments are invalid: other_parent.'
    end
  end

  describe '#after_update' do
    subject(:after_update_callback) { callback.after_update }

    context 'when invalid params are present' do
      let(:params) { { other_parent: 'parent_work_item' } }

      it_behaves_like 'raises a callback error', 'One or more arguments are invalid: other_parent.'
    end

    context 'when multiple params are present' do
      it_behaves_like 'raises a callback error', 'One and only one of children, parent or remove_child is required' do
        let(:params) { { parent: parent_work_item, children: [child_work_item] } }
      end

      it_behaves_like 'raises a callback error', 'One and only one of children, parent or remove_child is required' do
        let(:params) { { parent: parent_work_item, remove_child: child_work_item } }
      end

      it_behaves_like 'raises a callback error', 'One and only one of children, parent or remove_child is required' do
        let(:params) { { remove_child: child_work_item, children: [child_work_item] } }
      end
    end

    context 'when relative position params are incomplete' do
      context 'when only adjacent_work_item is present' do
        let(:params) do
          { parent: parent_work_item, adjacent_work_item: child_work_item }
        end

        it_behaves_like 'raises a callback error', described_class::INVALID_RELATIVE_POSITION_ERROR
      end

      context 'when only relative_position is present' do
        let(:params) do
          { parent: parent_work_item, relative_position: 'AFTER' }
        end

        it_behaves_like 'raises a callback error', described_class::INVALID_RELATIVE_POSITION_ERROR
      end
    end

    context 'when updating children' do
      let_it_be(:child_work_item2) { create(:work_item, :task, project: project) }
      let_it_be(:child_work_item3) { create(:work_item, :task, project: project) }
      let_it_be(:child_work_item4) { create(:work_item, :task, project: project) }

      context 'when user has insufficient permissions to link work items' do
        let(:params) { { children: [child_work_item4] } }

        it_behaves_like 'raises a callback error' do
          let(:message) { not_found_error }
        end
      end

      context 'when user has sufficient permissions to link work item' do
        before_all do
          project.add_developer(user)
        end

        context 'with valid children params' do
          let(:params) { { children: [child_work_item2, child_work_item3] } }

          it 'correctly sets work item parent' do
            after_update_callback

            expect(work_item.reload.work_item_children)
              .to contain_exactly(child_work_item, child_work_item2, child_work_item3)
          end

          context 'when relative_position and adjacent_work_item are given' do
            context 'with BEFORE value' do
              let(:params) do
                { children: [child_work_item3], relative_position: 'BEFORE', adjacent_work_item: child_work_item }
              end

              it_behaves_like 'raises a callback error', described_class::CHILDREN_REORDERING_ERROR
            end

            context 'with AFTER value' do
              let(:params) do
                { children: [child_work_item2], relative_position: 'AFTER', adjacent_work_item: child_work_item }
              end

              it_behaves_like 'raises a callback error', described_class::CHILDREN_REORDERING_ERROR
            end
          end
        end

        context 'with remove_child param' do
          let(:params) { { remove_child: [child_work_item] } }

          it 'correctly removes the work item child' do
            expect { after_update_callback }.to change { WorkItems::ParentLink.count }.by(-1)

            expect(work_item.reload.work_item_children).to be_empty
          end
        end

        context 'when child is already assigned' do
          let(:params) { { children: [child_work_item] } }

          it_behaves_like 'raises a callback error', 'Work item(s) already assigned'
        end

        context 'when child type is invalid' do
          let_it_be(:child_issue) { create(:work_item, project: project) }

          let(:params) { { children: [child_issue] } }

          it_behaves_like 'raises a callback error' do
            let(:message) do
              "#{child_issue.to_reference} cannot be added: it's not allowed to add this type of parent item"
            end
          end
        end
      end
    end

    context 'when updating parent' do
      let_it_be(:work_item) { create(:work_item, :task, project: project) }

      let(:params) { { parent: parent_work_item } }

      context 'when user has insufficient permissions to link work items' do
        it_behaves_like 'raises a callback error' do
          let(:message) { not_found_error }
        end
      end

      context 'when user has sufficient permissions to link work item' do
        before_all do
          project.add_developer(user)
        end

        it 'correctly sets new parent' do
          after_update_callback

          expect(work_item.work_item_parent).to eq(parent_work_item)
        end

        context 'when parent is nil' do
          let(:params) { { parent: nil } }

          it 'removes the work item parent if present' do
            work_item.update!(work_item_parent: parent_work_item)

            expect do
              after_update_callback
              work_item.reload
            end.to change { work_item.work_item_parent }.from(parent_work_item).to(nil)
          end

          it 'does not raise an error if parent not present', :aggregate_failures do
            work_item.update!(work_item_parent: nil)

            expect { after_update_callback }.not_to raise_error

            expect(work_item.reload.work_item_parent).to be_nil
          end
        end

        context 'when type is invalid' do
          let_it_be(:parent_task) { create(:work_item, :task, project: project) }

          let(:params) { { parent: parent_task } }

          it_behaves_like 'raises a callback error' do
            let(:message) do
              "#{parent_task.to_reference} cannot be added: it's not allowed to add this type of parent item"
            end
          end
        end

        context 'with positioning arguments' do
          let_it_be_with_reload(:adjacent) { create(:work_item, :task, project: project) }

          let_it_be_with_reload(:adjacent_link) do
            create(:parent_link, work_item: adjacent, work_item_parent: parent_work_item)
          end

          let(:params) { { parent: parent_work_item, adjacent_work_item: adjacent, relative_position: 'AFTER' } }

          it 'correctly sets new parent and position' do
            after_update_callback

            expect(work_item.work_item_parent).to eq(parent_work_item)
            expect(work_item.parent_link.relative_position).to be > adjacent_link.relative_position
          end

          context 'when other hierarchy adjacent is provided' do
            let_it_be(:other_hierarchy_adjacent) { create(:parent_link).work_item }

            let(:params) do
              { parent: parent_work_item, adjacent_work_item: other_hierarchy_adjacent, relative_position: 'AFTER' }
            end

            it_behaves_like 'raises a callback error', described_class::UNRELATED_ADJACENT_HIERARCHY_ERROR
          end
        end
      end
    end
  end
end