fork_service_spec.rb 12.4 KB
Newer Older
1 2
require 'spec_helper'

3
describe Projects::ForkService do
B
Bob Van Landuyt 已提交
4
  include ProjectForksHelper
5 6
  include Gitlab::ShellAdapter

7 8 9 10 11
  context 'when forking a new project' do
    describe 'fork by user' do
      before do
        @from_user = create(:user)
        @from_namespace = @from_user.namespace
12
        avatar = fixture_file_upload("spec/fixtures/dk.png", "image/png")
13 14 15 16 17 18 19 20 21 22 23
        @from_project = create(:project,
                               :repository,
                               creator_id: @from_user.id,
                               namespace: @from_namespace,
                               star_count: 107,
                               avatar: avatar,
                               description: 'wow such project')
        @to_user = create(:user)
        @to_namespace = @to_user.namespace
        @from_project.add_user(@to_user, :developer)
      end
24

25 26 27 28 29 30 31
      context 'fork project' do
        context 'when forker is a guest' do
          before do
            @guest = create(:user)
            @from_project.add_user(@guest, :guest)
          end
          subject { fork_project(@from_project, @guest) }
32

33 34
          it { is_expected.not_to be_persisted }
          it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) }
35 36 37 38

          it 'does not create a fork network' do
            expect { subject }.not_to change { @from_project.reload.fork_network }
          end
39 40
        end

41 42
        describe "successfully creates project in the user namespace" do
          let(:to_project) { fork_project(@from_project, @to_user, namespace: @to_user.namespace) }
43

44 45 46 47 48 49 50
          it { expect(to_project).to be_persisted }
          it { expect(to_project.errors).to be_empty }
          it { expect(to_project.owner).to eq(@to_user) }
          it { expect(to_project.namespace).to eq(@to_user.namespace) }
          it { expect(to_project.star_count).to be_zero }
          it { expect(to_project.description).to eq(@from_project.description) }
          it { expect(to_project.avatar.file).to be_exists }
51

52 53 54 55 56 57
          # This test is here because we had a bug where the from-project lost its
          # avatar after being forked.
          # https://gitlab.com/gitlab-org/gitlab-ce/issues/26158
          it "after forking the from-project still has its avatar" do
            # If we do not fork the project first we cannot detect the bug.
            expect(to_project).to be_persisted
58

59 60
            expect(@from_project.avatar.file).to be_exists
          end
61

62 63
          it 'flushes the forks count cache of the source project' do
            expect(@from_project.forks_count).to be_zero
64

65
            fork_project(@from_project, @to_user)
66

67 68
            expect(@from_project.forks_count).to eq(1)
          end
69

70 71 72
          it 'creates a fork network with the new project and the root project set' do
            to_project
            fork_network = @from_project.reload.fork_network
73

74 75 76 77
            expect(fork_network).not_to be_nil
            expect(fork_network.root_project).to eq(@from_project)
            expect(fork_network.projects).to contain_exactly(@from_project, to_project)
          end
78 79 80 81 82 83

          it 'imports the repository of the forked project' do
            to_project = fork_project(@from_project, @to_user, repository: true)

            expect(to_project.empty_repo?).to be_falsy
          end
84 85
        end

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
        context 'creating a fork of a fork' do
          let(:from_forked_project) { fork_project(@from_project, @to_user) }
          let(:other_namespace) do
            group = create(:group)
            group.add_owner(@to_user)
            group
          end
          let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) }

          it 'sets the root of the network to the root project' do
            expect(to_project.fork_network.root_project).to eq(@from_project)
          end

          it 'sets the forked_from_project on the membership' do
            expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project)
          end
102
        end
103
      end
104

105 106 107 108 109
      context 'project already exists' do
        it "fails due to validation, not transaction failure" do
          @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
          @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace)
          expect(@existing_project).to be_persisted
110

111 112 113 114
          expect(@to_project).not_to be_persisted
          expect(@to_project.errors[:name]).to eq(['has already been taken'])
          expect(@to_project.errors[:path]).to eq(['has already been taken'])
        end
115
      end
V
Valery Sizov 已提交
116

117 118
      context 'repository already exists' do
        let(:repository_storage) { 'default' }
119
        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
120

121
        before do
122
          gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
123
        end
124

125
        after do
126
          gitlab_shell.remove_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
127
        end
128

129 130
        it 'does not allow creation' do
          to_project = fork_project(@from_project, @to_user, namespace: @to_user.namespace)
131

132 133 134 135
          expect(to_project).not_to be_persisted
          expect(to_project.errors.messages).to have_key(:base)
          expect(to_project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
        end
136 137
      end

138 139 140 141 142 143
      context 'GitLab CI is enabled' do
        it "forks and enables CI for fork" do
          @from_project.enable_ci
          @to_project = fork_project(@from_project, @to_user)
          expect(@to_project.builds_enabled?).to be_truthy
        end
V
Valery Sizov 已提交
144
      end
145

146 147 148
      context "when project has restricted visibility level" do
        context "and only one visibility level is restricted" do
          before do
L
Lin Jen-Shin 已提交
149
            @from_project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
150 151 152
            stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
          end

153
          it "creates fork with lowest level" do
154 155
            forked_project = fork_project(@from_project, @to_user)

156
            expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
157
          end
158 159
        end

160 161 162 163 164 165 166
        context "and all visibility levels are restricted" do
          before do
            stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE])
          end

          it "creates fork with private visibility levels" do
            forked_project = fork_project(@from_project, @to_user)
167

168 169
            expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
          end
170 171
        end
      end
172
    end
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
    describe 'fork to namespace' do
      before do
        @group_owner = create(:user)
        @developer   = create(:user)
        @project     = create(:project, :repository,
                              creator_id: @group_owner.id,
                              star_count: 777,
                              description: 'Wow, such a cool project!')
        @group = create(:group)
        @group.add_user(@group_owner, GroupMember::OWNER)
        @group.add_user(@developer,   GroupMember::DEVELOPER)
        @project.add_user(@developer,   :developer)
        @project.add_user(@group_owner, :developer)
        @opts = { namespace: @group }
      end

      context 'fork project for group' do
        it 'group owner successfully forks project into the group' do
          to_project = fork_project(@project, @group_owner, @opts)

          expect(to_project).to             be_persisted
          expect(to_project.errors).to      be_empty
          expect(to_project.owner).to       eq(@group)
          expect(to_project.namespace).to   eq(@group)
          expect(to_project.name).to        eq(@project.name)
          expect(to_project.path).to        eq(@project.path)
          expect(to_project.description).to eq(@project.description)
          expect(to_project.star_count).to  be_zero
202
        end
203
      end
204

205 206 207 208 209 210
      context 'fork project for group when user not owner' do
        it 'group developer fails to fork project into the group' do
          to_project = fork_project(@project, @developer, @opts)
          expect(to_project.errors[:namespace]).to eq(['is not valid'])
        end
      end
211

212 213 214 215 216 217 218 219 220
      context 'project already exists in group' do
        it 'fails due to validation, not transaction failure' do
          existing_project = create(:project, :repository,
                                    name: @project.name,
                                    namespace: @group)
          to_project = fork_project(@project, @group_owner, @opts)
          expect(existing_project.persisted?).to be_truthy
          expect(to_project.errors[:name]).to eq(['has already been taken'])
          expect(to_project.errors[:path]).to eq(['has already been taken'])
221 222
        end
      end
223 224 225 226 227 228 229 230 231 232 233 234 235

      context 'when the namespace has a lower visibility level than the project' do
        it 'creates the project with the lower visibility level' do
          public_project = create(:project, :public)
          private_group = create(:group, :private)
          group_owner = create(:user)
          private_group.add_owner(group_owner)

          forked_project = fork_project(public_project, group_owner, namespace: private_group)

          expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
        end
      end
236
    end
237 238
  end

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
  context 'when forking with object pools' do
    let(:fork_from_project) { create(:project, :public) }
    let(:forker) { create(:user) }

    before do
      stub_feature_flags(object_pools: true)
    end

    context 'when no pool exists' do
      it 'creates a new object pool' do
        forked_project = fork_project(fork_from_project, forker)

        expect(forked_project.pool_repository).to eq(fork_from_project.pool_repository)
      end
    end

    context 'when a pool already exists' do
      let!(:pool_repository) { create(:pool_repository, source_project: fork_from_project) }

      it 'joins the object pool' do
        forked_project = fork_project(fork_from_project, forker)

        expect(forked_project.pool_repository).to eq(fork_from_project.pool_repository)
      end
    end
  end

266 267 268 269 270 271 272 273 274
  context 'when linking fork to an existing project' do
    let(:fork_from_project) { create(:project, :public) }
    let(:fork_to_project) { create(:project, :public) }
    let(:user) { create(:user) }

    subject { described_class.new(fork_from_project, user) }

    def forked_from_project(project)
      project.fork_network_member&.forked_from_project
275 276
    end

277 278 279 280 281 282
    context 'if project is already forked' do
      it 'does not create fork relation' do
        allow(fork_to_project).to receive(:forked?).and_return(true)
        expect(forked_from_project(fork_to_project)).to be_nil
        expect(subject.execute(fork_to_project)).to be_nil
        expect(forked_from_project(fork_to_project)).to be_nil
283 284 285
      end
    end

286 287
    context 'if project is not forked' do
      it 'creates fork relation' do
288
        expect(fork_to_project.forked?).to be_falsy
289 290 291 292
        expect(forked_from_project(fork_to_project)).to be_nil

        subject.execute(fork_to_project)

293 294
        fork_to_project.reload

295 296 297
        expect(fork_to_project.forked?).to be true
        expect(forked_from_project(fork_to_project)).to eq fork_from_project
        expect(fork_to_project.forked_from_project).to eq fork_from_project
298 299
      end

300 301 302 303 304 305
      it 'flushes the forks count cache of the source project' do
        expect(fork_from_project.forks_count).to be_zero

        subject.execute(fork_to_project)

        expect(fork_from_project.forks_count).to eq(1)
306
      end
307 308 309 310 311 312 313 314

      it 'leaves no LFS objects dangling' do
        create(:lfs_objects_project, project: fork_to_project)

        expect { subject.execute(fork_to_project) }
          .to change { fork_to_project.lfs_objects_projects.count }
          .to(0)
      end
315 316 317 318 319 320 321 322 323 324 325

      context 'if the fork is not allowed' do
        let(:fork_from_project) { create(:project, :private) }

        it 'does not delete the LFS objects' do
          create(:lfs_objects_project, project: fork_to_project)

          expect { subject.execute(fork_to_project) }
            .not_to change { fork_to_project.lfs_objects_projects.size }
        end
      end
326 327
    end
  end
328
end