destroy_service_spec.rb 10.4 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require 'spec_helper'

5
describe Users::DestroyService do
6
  describe "Deletes a user and all their personal projects" do
7 8
    let!(:user)      { create(:user) }
    let!(:admin)     { create(:admin) }
9
    let!(:namespace) { user.namespace }
10
    let!(:project)   { create(:project, namespace: namespace) }
11
    let(:service)    { described_class.new(admin) }
12
    let(:gitlab_shell) { Gitlab::Shell.new }
13 14 15

    context 'no options are given' do
      it 'deletes the user' do
16
        user_data = service.execute(user)
17

Igor Drozdov's avatar
Igor Drozdov committed
18
        expect(user_data['email']).to eq(user.email)
19
        expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
20
        expect { Namespace.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
21 22
      end

23 24 25 26 27 28
      it 'deletes user associations in batches' do
        expect(user).to receive(:destroy_dependent_associations_in_batches)

        service.execute(user)
      end

29 30 31 32 33 34
      it 'does not include snippets when deleting in batches' do
        expect(user).to receive(:destroy_dependent_associations_in_batches).with({ exclude: [:snippets] })

        service.execute(user)
      end

35
      it 'will delete the project' do
36
        expect_next_instance_of(Projects::DestroyService) do |destroy_service|
37
          expect(destroy_service).to receive(:execute).once.and_return(true)
38
        end
39

40
        service.execute(user)
41
      end
42 43 44

      it 'calls the bulk snippet destroy service for the user personal snippets' do
        repo1 = create(:personal_snippet, :repository, author: user).snippet_repository
George Thomas's avatar
George Thomas committed
45
        repo2 = create(:project_snippet, :repository, project: project, author: user).snippet_repository
46 47 48 49 50 51 52 53 54 55 56 57 58 59

        aggregate_failures do
          expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_truthy
          expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_truthy
        end

        # Call made when destroying user personal projects
        expect(Snippets::BulkDestroyService).to receive(:new)
          .with(admin, project.snippets).and_call_original

        # Call to remove user personal snippets and for
        # project snippets where projects are not user personal
        # ones
        expect(Snippets::BulkDestroyService).to receive(:new)
60
          .with(admin, user.snippets.only_personal_snippets).and_call_original
61 62 63 64 65 66 67 68 69

        service.execute(user)

        aggregate_failures do
          expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_falsey
          expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_falsey
        end
      end

70
      it 'does not delete project snippets that the user is the author of' do
George Thomas's avatar
George Thomas committed
71
        repo = create(:project_snippet, :repository, author: user).snippet_repository
72 73 74 75 76
        service.execute(user)
        expect(gitlab_shell.repository_exists?(repo2.shard_name, repo2.disk_path + '.git')).to be_truthy
        expect(User.ghost.snippets).to include(repo2.snippet)
      end

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
      context 'when an error is raised deleting snippets' do
        it 'does not delete user' do
          snippet = create(:personal_snippet, :repository, author: user)

          bulk_service = double
          allow(Snippets::BulkDestroyService).to receive(:new).and_call_original
          allow(Snippets::BulkDestroyService).to receive(:new).with(admin, user.snippets).and_return(bulk_service)
          allow(bulk_service).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))

          aggregate_failures do
            expect { service.execute(user) }
              .to raise_error(Users::DestroyService::DestroyError, 'foo' )
            expect(snippet.reload).not_to be_nil
            expect(gitlab_shell.repository_exists?(snippet.repository_storage, snippet.disk_path + '.git')).to be_truthy
          end
        end
      end
94 95
    end

96 97 98 99 100 101 102
    context 'projects in pending_delete' do
      before do
        project.pending_delete = true
        project.save
      end

      it 'destroys a project in pending_delete' do
103
        expect_next_instance_of(Projects::DestroyService) do |destroy_service|
104
          expect(destroy_service).to receive(:execute).once.and_return(true)
105
        end
106 107 108 109 110 111 112

        service.execute(user)

        expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound)
      end
    end

113
    context "a deleted user's issues" do
114
      let(:project) { create(:project) }
115 116 117 118 119

      before do
        project.add_developer(user)
      end

120
      context "for an issue the user was assigned to" do
Clement Ho's avatar
Clement Ho committed
121
        let!(:issue) { create(:issue, project: project, assignees: [user]) }
122 123 124 125 126

        before do
          service.execute(user)
        end

127
        it 'does not delete issues the user is assigned to' do
128 129 130
          expect(Issue.find_by_id(issue.id)).to be_present
        end

131
        it 'migrates the issue so that it is "Unassigned"' do
132 133
          migrated_issue = Issue.find_by_id(issue.id)

134
          expect(migrated_issue.assignees).to be_empty
135
        end
136 137
      end
    end
138

139
    context "a deleted user's merge_requests" do
140
      let(:project) { create(:project, :repository) }
141 142 143

      before do
        project.add_developer(user)
144 145
      end

146
      context "for an merge request the user was assigned to" do
147
        let!(:merge_request) { create(:merge_request, source_project: project, assignees: [user]) }
148 149 150 151 152

        before do
          service.execute(user)
        end

153 154
        it 'does not delete merge requests the user is assigned to' do
          expect(MergeRequest.find_by_id(merge_request.id)).to be_present
155 156
        end

157 158
        it 'migrates the merge request so that it is "Unassigned"' do
          migrated_merge_request = MergeRequest.find_by_id(merge_request.id)
159

160
          expect(migrated_merge_request.assignees).to be_empty
161 162 163 164
        end
      end
    end

165 166 167 168 169 170 171
    context "solo owned groups present" do
      let(:solo_owned)  { create(:group) }
      let(:member)      { create(:group_member) }
      let(:user)        { member.user }

      before do
        solo_owned.group_members = [member]
172 173 174 175 176 177 178
      end

      it 'returns the user with attached errors' do
        expect(service.execute(user)).to be(user)
        expect(user.errors.full_messages).to eq([
          'You must transfer ownership or delete groups before you can remove user'
        ])
179 180 181
      end

      it 'does not delete the user' do
182
        service.execute(user)
183 184 185 186 187 188 189 190 191 192 193
        expect(User.find(user.id)).to eq user
      end
    end

    context "deletions with solo owned groups" do
      let(:solo_owned)      { create(:group) }
      let(:member)          { create(:group_member) }
      let(:user)            { member.user }

      before do
        solo_owned.group_members = [member]
194
        service.execute(user, delete_solo_owned_groups: true)
195 196 197 198 199 200 201 202 203 204
      end

      it 'deletes solo owned groups' do
        expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound)
      end

      it 'deletes the user' do
        expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
      end
    end
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

    context "deletion permission checks" do
      it 'does not delete the user when user is not an admin' do
        other_user = create(:user)

        expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
        expect(User.exists?(user.id)).to be(true)
      end

      it 'allows admins to delete anyone' do
        described_class.new(admin).execute(user)

        expect(User.exists?(user.id)).to be(false)
      end

      it 'allows users to delete their own account' do
        described_class.new(user).execute(user)

        expect(User.exists?(user.id)).to be(false)
      end
    end
226

227
    context "migrating associated records" do
228 229
      let!(:issue)     { create(:issue, author: user) }

230
      it 'delegates to the `MigrateToGhostUser` service to move associated records to the ghost user' do
231
        expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once.and_call_original
232

233
        service.execute(user)
234 235

        expect(issue.reload.author).to be_ghost
236
      end
237 238 239 240 241

      it 'does not run `MigrateToGhostUser` if hard_delete option is given' do
        expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute)

        service.execute(user, hard_delete: true)
242 243

        expect(Issue.exists?(issue.id)).to be_falsy
244
      end
245
    end
246 247

    describe "user personal's repository removal" do
248 249 250 251 252 253 254 255 256
      context 'storages' do
        before do
          perform_enqueued_jobs { service.execute(user) }
        end

        context 'legacy storage' do
          let!(:project) { create(:project, :empty_repo, :legacy_storage, namespace: user.namespace) }

          it 'removes repository' do
257
            expect(gitlab_shell.repository_exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey
258 259
          end
        end
260

261 262
        context 'hashed storage' do
          let!(:project) { create(:project, :empty_repo, namespace: user.namespace) }
263

264
          it 'removes repository' do
265
            expect(gitlab_shell.repository_exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey
266
          end
267 268 269
        end
      end

270 271 272 273 274
      context 'repository removal status is taken into account' do
        it 'raises exception' do
          expect_next_instance_of(::Projects::DestroyService) do |destroy_service|
            expect(destroy_service).to receive(:execute).and_return(false)
          end
275

276 277
          expect { service.execute(user) }
            .to raise_error(Users::DestroyService::DestroyError, "Project #{project.id} can't be deleted" )
278 279 280
        end
      end
    end
281 282 283

    describe "calls the before/after callbacks" do
      it 'of project_members' do
Jasper Maes's avatar
Jasper Maes committed
284 285
        expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:find).once
        expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:initialize).once
286 287 288 289 290 291 292 293 294
        expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:destroy).once

        service.execute(user)
      end

      it 'of group_members' do
        group_member = create(:group_member)
        group_member.group.group_members.create(user: user, access_level: 40)

Jasper Maes's avatar
Jasper Maes committed
295 296
        expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:find).once
        expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:initialize).once
297 298 299 300 301
        expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:destroy).once

        service.execute(user)
      end
    end
302 303
  end
end