summaryrefslogtreecommitdiff
path: root/spec/services/projects/unlink_fork_service_spec.rb
blob: 2a8965e62cee6a2c64639b79563c71d3b3390f63 (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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
  include ProjectForksHelper

  subject { described_class.new(forked_project, user) }

  let(:project) { create(:project, :public) }
  let!(:forked_project) { fork_project(project, user) }
  let(:user) { create(:user) }

  context 'with opened merge request on the source project' do
    let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: forked_project.forked_from_project) }
    let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) }
    let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }

    let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }

    before do
      allow(MergeRequests::CloseService).to receive(:new)
        .with(forked_project, user)
        .and_return(mr_close_service)
    end

    it 'close all pending merge requests' do
      expect(mr_close_service).to receive(:execute).with(merge_request)
      expect(mr_close_service).to receive(:execute).with(merge_request2)

      subject.execute
    end

    it 'does not close merge requests for the project being unlinked' do
      expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)
    end
  end

  it 'removes the link to the fork network' do
    expect(forked_project.fork_network_member).to be_present
    expect(forked_project.fork_network).to be_present

    subject.execute
    forked_project.reload

    expect(forked_project.fork_network_member).to be_nil
    expect(forked_project.reload.fork_network).to be_nil
  end

  it 'refreshes the forks count cache of the source project' do
    source = forked_project.forked_from_project

    expect(source.forks_count).to eq(1)

    subject.execute
    BatchLoader::Executor.clear_current

    expect(source.forks_count).to be_zero
  end

  context 'when the original project was deleted' do
    it 'does not fail when the original project is deleted' do
      source = forked_project.forked_from_project
      source.destroy!
      forked_project.reload

      expect { subject.execute }.not_to raise_error
    end
  end

  context 'when given project is a source of forks' do
    let!(:forked_project_2) { fork_project(project, user) }
    let!(:fork_of_fork) { fork_project(forked_project, user) }

    subject { described_class.new(project, user) }

    context 'with opened merge requests from fork back to root project' do
      let!(:merge_request) { create(:merge_request, source_project: project, target_project: forked_project) }
      let!(:merge_request2) { create(:merge_request, source_project: project, target_project: fork_project(project)) }
      let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }

      let(:mr_close_service) { MergeRequests::CloseService.new(project, user) }

      before do
        allow(MergeRequests::CloseService).to receive(:new)
                                                .with(project, user)
                                                .and_return(mr_close_service)
      end

      it 'closes all pending merge requests' do
        expect(mr_close_service).to receive(:execute).with(merge_request)
        expect(mr_close_service).to receive(:execute).with(merge_request2)

        subject.execute
      end

      it 'does not close merge requests that do not come from the project being unlinked' do
        expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)

        subject.execute
      end
    end

    it 'removes its link to the fork network and updates direct network members' do
      expect(project.fork_network_member).to be_present
      expect(project.fork_network).to be_present
      expect(project.forked_to_members.count).to eq(2)
      expect(forked_project.forked_to_members.count).to eq(1)
      expect(fork_of_fork.forked_to_members.count).to eq(0)

      subject.execute

      project.reload
      forked_project.reload
      fork_of_fork.reload

      expect(project.fork_network_member).to be_nil
      expect(project.fork_network).to be_nil
      expect(forked_project.fork_network).to have_attributes(root_project_id: nil,
                                                             deleted_root_project_name: project.full_name)
      expect(project.forked_to_members.count).to eq(0)
      expect(forked_project.forked_to_members.count).to eq(1)
      expect(fork_of_fork.forked_to_members.count).to eq(0)
    end

    it 'refreshes the forks count cache of the given project' do
      expect(project.forks_count).to eq(2)

      subject.execute
      BatchLoader::Executor.clear_current

      expect(project.forks_count).to be_zero
    end

    context 'and is node with a parent' do
      subject { described_class.new(forked_project, user) }

      context 'with opened merge requests from and to given project' do
        let!(:mr_from_parent) { create(:merge_request, source_project: project, target_project: forked_project) }
        let!(:mr_to_parent) { create(:merge_request, source_project: forked_project, target_project: project) }
        let!(:mr_to_child) { create(:merge_request, source_project: forked_project, target_project: fork_of_fork) }
        let!(:mr_from_child) { create(:merge_request, source_project: fork_of_fork, target_project: forked_project) }
        let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }

        let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }

        before do
          allow(MergeRequests::CloseService).to receive(:new)
            .with(forked_project, user)
            .and_return(mr_close_service)
        end

        it 'close all pending merge requests' do
          merge_requests = [mr_from_parent, mr_to_parent, mr_from_child, mr_to_child]

          merge_requests.each do |mr|
            expect(mr_close_service).to receive(:execute).with(mr).and_call_original
          end

          subject.execute

          merge_requests = MergeRequest.where(id: merge_requests)

          expect(merge_requests).to all(have_attributes(state: 'closed'))
        end

        it 'does not close merge requests which do not come from the project being unlinked' do
          expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)

          subject.execute
        end
      end

      it 'refreshes the forks count cache of the parent and the given project' do
        expect(project.forks_count).to eq(2)
        expect(forked_project.forks_count).to eq(1)

        subject.execute
        BatchLoader::Executor.clear_current

        expect(project.forks_count).to eq(1)
        expect(forked_project.forks_count).to eq(0)
      end

      it 'removes its link to the fork network and updates direct network members' do
        expect(project.fork_network).to be_present
        expect(forked_project.fork_network).to be_present
        expect(fork_of_fork.fork_network).to be_present

        expect(project.forked_to_members.count).to eq(2)
        expect(forked_project.forked_to_members.count).to eq(1)
        expect(fork_of_fork.forked_to_members.count).to eq(0)

        subject.execute
        project.reload
        forked_project.reload
        fork_of_fork.reload

        expect(project.fork_network).to be_present
        expect(forked_project.fork_network).to be_nil
        expect(fork_of_fork.fork_network).to be_present

        expect(project.forked_to_members.count).to eq(1) # 1 child is gone
        expect(forked_project.forked_to_members.count).to eq(0)
        expect(fork_of_fork.forked_to_members.count).to eq(0)
      end
    end
  end

  context 'when given project is not part of a fork network' do
    let!(:project_without_forks) { create(:project, :public) }

    subject { described_class.new(project_without_forks, user) }

    it 'does not raise errors' do
      expect { subject.execute }.not_to raise_error
    end
  end
end