Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class DestroyService < Members::BaseService
    include Gitlab::ExclusiveLeaseHelpers

    def execute(
      member,
      skip_authorization: false,
      skip_subresources: false,
      unassign_issuables: false,
      destroy_bot: false,
      skip_saml_identity: false
    )

      unless skip_authorization
        raise Gitlab::Access::AccessDeniedError unless authorized?(member, destroy_bot)

        raise Gitlab::Access::AccessDeniedError if destroying_member_with_owner_access_level?(member) &&
          cannot_revoke_owner_responsibilities_from_member_in_project?(member)
      end

      @skip_auth = skip_authorization

      if a_group_owner?(member)
        process_destroy_of_group_owner_member(member, skip_subresources, skip_saml_identity)
      else
        destroy_member(member)
        destroy_data_related_to_member(member, skip_subresources, skip_saml_identity)
      end

      enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables)

      member
    end

    # We use this to mark recursive calls made to this service from within the same service.
    # We do this so as to help us run some tasks that needs to be run only once per hierarchy, and not recursively.
    def mark_as_recursive_call
      @recursive_call = true
    end

    private

    # These actions need to be executed only once per hierarchy because the underlying services
    # apply these actions to the entire hierarchy anyway, so there is no need to execute them recursively.
    def enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables)
      return if recursive_call?

      enqueue_cleanup_jobs_once_per_heirarchy(member, unassign_issuables)
    end

    def enqueue_cleanup_jobs_once_per_heirarchy(member, unassign_issuables)
      enqueue_delete_todos(member)
      enqueue_unassign_issuables(member) if unassign_issuables
    end

    def recursive_call?
      @recursive_call == true
    end

    def process_destroy_of_group_owner_member(member, skip_subresources, skip_saml_identity)
      # Deleting 2 different group owners via the API in quick succession could lead to
      # wrong results for the `last_owner?` check due to race conditions. To prevent this
      # we wrap both the last_owner? check and the deletes of owners within a lock.
      last_group_owner = true

      in_lock("delete_members:#{member.source.class}:#{member.source.id}", sleep_sec: 0.1.seconds) do
        break if member.source.last_owner?(member.user)

        last_group_owner = false
        destroy_member(member)
      end

      # deletion of related data does not have to be within the lock.
      destroy_data_related_to_member(member, skip_subresources, skip_saml_identity) unless last_group_owner
    end

    def destroy_member(member)
      member.destroy
    end

    def destroy_data_related_to_member(member, skip_subresources, skip_saml_identity)
      member.user&.invalidate_cache_counts
      delete_member_associations(member, skip_subresources, skip_saml_identity)
    end

    def a_group_owner?(member)
      member.is_a?(GroupMember) && member.owner?
    end

    def delete_member_associations(member, skip_subresources, skip_saml_identity)
      if member.request? && member.user != current_user
        notification_service.decline_access_request(member)
      end

      delete_subresources(member) unless skip_subresources
      delete_project_invitations_by(member) unless skip_subresources
      resolve_access_request_todos(member)

      after_execute(member: member, skip_saml_identity: skip_saml_identity)
    end

    def authorized?(member, destroy_bot)
      return can_destroy_bot_member?(member) if destroy_bot

      if member.request?
        return can_destroy_member_access_request?(member) || can_withdraw_member_access_request?(member)
      end

      can_destroy_member?(member)
    end

    def delete_subresources(member)
      return unless member.is_a?(GroupMember) && member.user && member.group

      delete_project_members(member)
      delete_subgroup_members(member)
      delete_invited_members(member)
    end

    def delete_project_members(member)
      groups = member.group.self_and_descendants

      destroy_project_members(ProjectMember.in_namespaces(groups).with_user(member.user))
    end

    def delete_subgroup_members(member)
      groups = member.group.descendants

      destroy_group_members(GroupMember.of_groups(groups).with_user(member.user))
    end

    def delete_invited_members(member)
      groups = member.group.self_and_descendants

      destroy_group_members(GroupMember.of_groups(groups).not_accepted_invitations_by_user(member.user))

      destroy_project_members(ProjectMember.in_namespaces(groups).not_accepted_invitations_by_user(member.user))
    end

    def destroy_project_members(members)
      members.each do |project_member|
        service = self.class.new(current_user)
        service.mark_as_recursive_call
        service.execute(project_member, skip_authorization: @skip_auth)
      end
    end

    def destroy_group_members(members)
      members.each do |group_member|
        service = self.class.new(current_user)
        service.mark_as_recursive_call
        service.execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
      end
    end

    def delete_project_invitations_by(member)
      return unless member.is_a?(ProjectMember) && member.user && member.project

      members_to_delete = member.project.members.not_accepted_invitations_by_user(member.user)
      destroy_project_members(members_to_delete)
    end

    def can_destroy_member?(member)
      can?(current_user, destroy_member_permission(member), member)
    end

    def can_destroy_bot_member?(member)
      can?(current_user, destroy_bot_member_permission(member), member)
    end

    def can_destroy_member_access_request?(member)
      can?(current_user, :admin_member_access_request, member.source)
    end

    def can_withdraw_member_access_request?(member)
      can?(current_user, :withdraw_member_access_request, member)
    end

    def destroying_member_with_owner_access_level?(member)
      member.owner?
    end

    def destroy_member_permission(member)
      case member
      when GroupMember
        :destroy_group_member
      when ProjectMember
        :destroy_project_member
      else
        raise "Unknown member type: #{member}!"
      end
    end

    def destroy_bot_member_permission(member)
      raise "Unsupported bot member type: #{member}" unless member.is_a?(ProjectMember)

      :destroy_project_bot_member
    end

    def enqueue_unassign_issuables(member)
      source_type = member.is_a?(GroupMember) ? 'Group' : 'Project'

      member.run_after_commit_or_now do
        MembersDestroyer::UnassignIssuablesWorker.perform_async(member.user_id, member.source_id, source_type)
      end
    end
  end
end

Members::DestroyService.prepend_mod_with('Members::DestroyService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::DestroyService, feature_category: :groups_and_projects do
  let(:current_user) { create(:user) }
  let(:member_user) { create(:user) }
  let(:group) { create(:group, :public) }
  let(:group_project) { create(:project, :public, group: group) }
  let(:opts) { {} }

  shared_examples 'a service raising ActiveRecord::RecordNotFound' do
    it 'raises ActiveRecord::RecordNotFound' do
      expect { described_class.new(current_user).execute(member) }.to raise_error(ActiveRecord::RecordNotFound)
    end
  end

  shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
    it 'raises Gitlab::Access::AccessDeniedError' do
      expect { described_class.new(current_user).execute(member) }.to raise_error(Gitlab::Access::AccessDeniedError)
    end
  end

  shared_examples 'a service destroying a member' do
    before do
      type = member.is_a?(GroupMember) ? 'Group' : 'Project'
      expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
      expect(MembersDestroyer::UnassignIssuablesWorker).to receive(:perform_async).with(member.user_id, member.source_id, type) if opts[:unassign_issuables]
    end

    it 'destroys the member' do
      expect { described_class.new(current_user).execute(member, **opts) }.to change { member.source.members_and_requesters.count }.by(-1)
    end

    it 'destroys member notification_settings' do
      if member_user.notification_settings.any?
        expect { described_class.new(current_user).execute(member, **opts) }
          .to change { member_user.notification_settings.count }.by(-1)
      else
        expect { described_class.new(current_user).execute(member, **opts) }
          .not_to change { member_user.notification_settings.count }
      end
    end

    it 'resolves the access request todos for the owner' do
      expect_next_instance_of(described_class) do |instance|
        expect(instance).to receive(:resolve_access_request_todos).with(member)
      end

      described_class.new(current_user).execute(member, **opts)
    end
  end

  shared_examples 'a service destroying a member with access' do
    it_behaves_like 'a service destroying a member'

    it 'invalidates cached counts for assigned issues and merge requests', :aggregate_failures, :sidekiq_might_not_need_inline do
      create(:issue, project: group_project, assignees: [member_user])
      create(:merge_request, source_project: group_project, assignees: [member_user])
      create(:todo, :pending, project: group_project, user: member_user)
      create(:todo, :done, project: group_project, user: member_user)

      expect(member_user.assigned_open_merge_requests_count).to be(1)
      expect(member_user.assigned_open_issues_count).to be(1)
      expect(member_user.todos_pending_count).to be(1)
      expect(member_user.todos_done_count).to be(1)

      service = described_class.new(current_user)

      if opts[:unassign_issuables]
        expect(service).to receive(:enqueue_unassign_issuables).with(member)
      end

      service.execute(member, **opts)

      expect(member_user.assigned_open_merge_requests_count).to be(0)
      expect(member_user.assigned_open_issues_count).to be(0)
      expect(member_user.todos_pending_count).to be(0)
      expect(member_user.todos_done_count).to be(0)

      unless opts[:unassign_issuables]
        expect(member_user.assigned_merge_requests.opened.count).to be(1)
        expect(member_user.assigned_issues.opened.count).to be(1)
      end
    end
  end

  shared_examples 'a service destroying an access request of another user' do
    it_behaves_like 'a service destroying a member'

    it 'calls Member#after_decline_request' do
      expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member)

      described_class.new(current_user).execute(member, **opts)
    end
  end

  shared_examples 'a service destroying an access request of self' do
    it_behaves_like 'a service destroying a member'

    context 'when current user is the member' do
      it 'does not call Member#after_decline_request' do
        expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)

        described_class.new(current_user).execute(member, **opts)
      end
    end
  end

  context 'With ExclusiveLeaseHelpers' do
    include ExclusiveLeaseHelpers

    let(:lock_key) do
      "delete_members:#{member_to_delete.source.class}:#{member_to_delete.source.id}"
    end

    let(:timeout) { 1.minute }
    let(:service_object) { described_class.new(current_user) }

    subject(:destroy_member) { service_object.execute(member_to_delete, **opts) }

    context 'for group members' do
      before do
        group.add_owner(current_user)
      end

      context 'deleting group owners' do
        let!(:member_to_delete) { group.add_owner(member_user) }

        context 'locking to avoid race conditions' do
          it 'tries to perform the delete within a lock' do
            expect_to_obtain_exclusive_lease(lock_key, timeout: timeout)

            destroy_member
          end

          context 'based on status of the lock' do
            context 'when lock is obtained' do
              it 'destroys the membership' do
                expect_to_obtain_exclusive_lease(lock_key, timeout: timeout)

                expect { destroy_member }.to change { group.members.count }.by(-1)
              end
            end

            context 'when the lock cannot be obtained' do
              before do
                stub_exclusive_lease_taken(lock_key, timeout: timeout)
              end

              it 'raises error' do
                expect { destroy_member }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
              end
            end
          end
        end
      end

      context 'deleting group members that are not owners' do
        let!(:member_to_delete) { group.add_developer(member_user) }

        it 'does not try to perform the deletion of the member within a lock' do
          # We need to account for other places involved in the Member deletion process that
          # uses ExclusiveLease.

          # 1. `UpdateHighestRole` concern uses locks to peform work
          # whenever a Member is committed, so that needs to be accounted for.
          lock_key_for_update_highest_role = "update_highest_role:#{member_to_delete.user_id}"

          expect(Gitlab::ExclusiveLease)
            .to receive(:new).with(lock_key_for_update_highest_role, timeout: 10.minutes.to_i).and_call_original

          # 2. `Users::RefreshAuthorizedProjectsService` also uses locks to perform work,
          # whenever a user's authorizations has to be refreshed, so that needs to be accounted for as well.
          lock_key_for_authorizations_refresh = "refresh_authorized_projects:#{member_to_delete.user_id}"

          expect(Gitlab::ExclusiveLease)
            .to receive(:new).with(lock_key_for_authorizations_refresh, timeout: 1.minute.to_i).and_call_original

          # We do not use any locks for the member deletion process, from within this service.
          expect(Gitlab::ExclusiveLease)
            .not_to receive(:new).with(lock_key, timeout: timeout)

          destroy_member
        end

        it 'destroys the membership' do
          expect { destroy_member }.to change { group.members.count }.by(-1)
        end
      end
    end

    context 'for project members' do
      shared_examples_for 'deletes the project member without using a lock' do
        it 'does not try to perform the deletion of a project member within a lock' do
          # We need to account for other places involved in the Member deletion process that
          # uses ExclusiveLease.

          # 1. `UpdateHighestRole` concern uses locks to peform work
          # whenever a Member is committed, so that needs to be accounted for.
          lock_key_for_update_highest_role = "update_highest_role:#{member_to_delete.user_id}"

          expect(Gitlab::ExclusiveLease)
            .to receive(:new).with(lock_key_for_update_highest_role, timeout: 10.minutes.to_i).and_call_original

          # 2. `AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker` also uses locks to perform work,
          # whenever a user's authorizations has to be refreshed, so that needs to be accounted for as well.
          lock_key_for_authorizations_refresh =
            "authorized_project_update/project_recalculate_worker/projects/#{member_to_delete.project.id}"

          expect(Gitlab::ExclusiveLease)
            .to receive(:new).with(lock_key_for_authorizations_refresh, timeout: 10.seconds).and_call_original

          # We do not use any locks for the member deletion process, from within this service.
          expect(Gitlab::ExclusiveLease)
            .not_to receive(:new).with(lock_key, timeout: timeout)

          destroy_member
        end

        it 'destroys the membership' do
          expect { destroy_member }.to change { entity.members.count }.by(-1)
        end
      end

      before do
        group_project.add_owner(current_user)
      end

      context 'deleting project owners' do
        context 'deleting project owners' do
          let!(:member_to_delete) { entity.add_owner(member_user) }

          it_behaves_like 'deletes the project member without using a lock' do
            let(:entity) { group_project }
          end
        end
      end

      context 'deleting project members that are not owners' do
        let!(:member_to_delete) { group_project.add_developer(member_user) }

        it_behaves_like 'deletes the project member without using a lock' do
          let(:entity) { group_project }
        end
      end
    end
  end

  context 'with a member with access' do
    before do
      group_project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
      group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
    end

    context 'when current user cannot destroy the given member' do
      context 'with a project member' do
        let(:member) { group_project.members.find_by(user_id: member_user.id) }

        context 'when current user does not have any membership management permissions' do
          before do
            group_project.add_developer(member_user)
          end

          it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'

          context 'when skipping authorisation' do
            it_behaves_like 'a service destroying a member with access' do
              let(:opts) { { skip_authorization: true, unassign_issuables: true } }
            end
          end
        end

        context 'when a project maintainer tries to destroy a project owner' do
          before do
            group_project.add_owner(member_user)
          end

          it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'

          context 'when skipping authorisation' do
            it_behaves_like 'a service destroying a member with access' do
              let(:opts) { { skip_authorization: true, unassign_issuables: true } }
            end
          end
        end
      end
    end

    context 'with a group member' do
      let(:member) { group.members.find_by(user_id: member_user.id) }

      before do
        group.add_developer(member_user)
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'

      context 'when skipping authorisation' do
        it_behaves_like 'a service destroying a member with access' do
          let(:opts) { { skip_authorization: true, unassign_issuables: true } }
        end
      end
    end

    context 'when current user can destroy the given member' do
      before do
        group_project.add_maintainer(current_user)
        group.add_owner(current_user)
      end

      context 'with a project member' do
        let(:member) { group_project.members.find_by(user_id: member_user.id) }

        before do
          group_project.add_developer(member_user)
        end

        it_behaves_like 'a service destroying a member with access'

        context 'unassign issuables' do
          it_behaves_like 'a service destroying a member with access' do
            let(:opts) { { unassign_issuables: true } }
          end
        end
      end

      context 'with a project bot member' do
        let(:member) { group_project.members.find_by(user_id: member_user.id) }
        let(:member_user) { create(:user, :project_bot) }

        before do
          group_project.add_maintainer(member_user)
        end

        context 'when the destroy_bot flag is true' do
          it_behaves_like 'a service destroying a member with access' do
            let(:opts) { { destroy_bot: true } }
          end
        end

        context 'when the destroy_bot flag is not specified' do
          it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
        end
      end

      context 'with a group member' do
        let(:member) { group.members.find_by(user_id: member_user.id) }

        before do
          group.add_developer(member_user)
        end

        it_behaves_like 'a service destroying a member with access'

        context 'unassign issuables' do
          it_behaves_like 'a service destroying a member with access' do
            let(:opts) { { unassign_issuables: true } }
          end
        end
      end
    end
  end

  context 'with an access requester' do
    before do
      group_project.update!(request_access_enabled: true)
      group.update!(request_access_enabled: true)
      group_project.request_access(member_user)
      group.request_access(member_user)
    end

    context 'when current user cannot destroy the given access requester' do
      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
      end

      it_behaves_like 'a service destroying a member' do
        let(:opts) { { skip_authorization: true, skip_subresources: true } }
        let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:member) { group.requesters.find_by(user_id: member_user.id) }
      end

      it_behaves_like 'a service destroying a member' do
        let(:opts) { { skip_authorization: true, skip_subresources: true } }
        let(:member) { group.requesters.find_by(user_id: member_user.id) }
      end
    end

    context 'when current user can destroy the given access requester' do
      let(:opts) { { skip_subresources: true } }

      before do
        group_project.add_maintainer(current_user)
        group.add_owner(current_user)
      end

      it_behaves_like 'a service destroying an access request of another user' do
        let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
      end

      it_behaves_like 'a service destroying an access request of another user' do
        let(:member) { group.requesters.find_by(user_id: member_user.id) }
      end
    end

    context 'on withdrawing their own access request' do
      let(:opts) { { skip_subresources: true } }
      let(:current_user) { member_user }

      it_behaves_like 'a service destroying an access request of self' do
        let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
      end

      it_behaves_like 'a service destroying an access request of self' do
        let(:member) { group.requesters.find_by(user_id: member_user.id) }
      end
    end
  end

  context 'with an invited user' do
    let(:project_invited_member) { create(:project_member, :invited, project: group_project) }
    let(:group_invited_member) { create(:group_member, :invited, group: group) }

    context 'when current user cannot destroy the given invited user' do
      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:member) { project_invited_member }
      end

      it_behaves_like 'a service destroying a member' do
        let(:opts) { { skip_authorization: true } }
        let(:member) { project_invited_member }
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:member) { group_invited_member }
      end

      it_behaves_like 'a service destroying a member' do
        let(:opts) { { skip_authorization: true } }
        let(:member) { group_invited_member }
      end
    end

    context 'when current user can destroy the given invited user' do
      before do
        group_project.add_maintainer(current_user)
        group.add_owner(current_user)
      end

      # Regression spec for issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/32504
      it_behaves_like 'a service destroying a member' do
        let(:member) { project_invited_member }
      end

      it_behaves_like 'a service destroying a member' do
        let(:member) { group_invited_member }
      end
    end
  end

  context 'subresources' do
    let_it_be_with_reload(:user) { create(:user) }
    let_it_be_with_reload(:member_user) { create(:user) }

    let_it_be_with_reload(:group) { create(:group, :public) }
    let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
    let_it_be(:private_subgroup) { create(:group, :private, parent: group, name: 'private_subgroup') }
    let_it_be(:private_subgroup_with_direct_membership) { create(:group, :private, parent: group) }
    let_it_be_with_reload(:subsubgroup) { create(:group, parent: subgroup) }

    let_it_be_with_reload(:group_project) { create(:project, :public, group: group) }
    let_it_be_with_reload(:control_project) { create(:project, :private, group: subsubgroup) }
    let_it_be_with_reload(:subsubproject) { create(:project, :public, group: subsubgroup) }

    let_it_be(:private_subgroup_project) do
      create(:project, :private, group: private_subgroup, name: 'private_subgroup_project')
    end

    let_it_be(:private_subgroup_with_direct_membership_project) do
      create(:project, :private, group: private_subgroup_with_direct_membership, name: 'private_subgroup_project')
    end

    context 'with memberships' do
      before do
        subgroup.add_developer(member_user)
        subsubgroup.add_developer(member_user)
        subsubproject.add_developer(member_user)
        group_project.add_developer(member_user)
        control_project.add_maintainer(user)
        private_subgroup_with_direct_membership.add_developer(member_user)
        group.add_owner(user)

        @group_member = create(:group_member, :developer, group: group, user: member_user)
      end

      let_it_be(:todo_in_public_group_project) do
        create(:todo, :pending,
          project: group_project,
          user: member_user,
          target: create(:issue, project: group_project)
        )
      end

      let_it_be(:mr_in_public_group_project) do
        create(:merge_request, source_project: group_project, assignees: [member_user])
      end

      let_it_be(:todo_in_private_subgroup_project) do
        create(:todo, :pending,
          project: private_subgroup_project,
          user: member_user,
          target: create(:issue, project: private_subgroup_project)
        )
      end

      let_it_be(:mr_in_private_subgroup_project) do
        create(:merge_request, source_project: private_subgroup_project, assignees: [member_user])
      end

      let_it_be(:todo_in_public_subsubgroup_project) do
        create(:todo, :pending,
          project: subsubproject,
          user: member_user,
          target: create(:issue, project: subsubproject)
        )
      end

      let_it_be(:mr_in_public_subsubgroup_project) do
        create(:merge_request, source_project: subsubproject, assignees: [member_user])
      end

      let_it_be(:todo_in_private_subgroup_with_direct_membership_project) do
        create(:todo, :pending,
          project: private_subgroup_with_direct_membership_project,
          user: member_user,
          target: create(:issue, project: private_subgroup_with_direct_membership_project)
        )
      end

      let_it_be(:mr_in_private_subgroup_with_direct_membership_project) do
        create(:merge_request,
          source_project: private_subgroup_with_direct_membership_project,
          assignees: [member_user]
        )
      end

      context 'with skipping of subresources' do
        subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: true) }

        before do
          execute_service
        end

        it 'removes the group membership' do
          expect(group.members.map(&:user)).not_to include(member_user)
        end

        it 'does not remove the project membership' do
          expect(group_project.members.map(&:user)).to include(member_user)
        end

        it 'does not remove the subgroup membership' do
          expect(subgroup.members.map(&:user)).to include(member_user)
        end

        it 'does not remove the subsubgroup membership' do
          expect(subsubgroup.members.map(&:user)).to include(member_user)
        end

        it 'does not remove the subsubproject membership' do
          expect(subsubproject.members.map(&:user)).to include(member_user)
        end

        it 'does not remove the user from the control project' do
          expect(control_project.members.map(&:user)).to include(user)
        end

        context 'todos', :sidekiq_inline do
          it 'removes todos for which the user no longer has access' do
            expect(member_user.todos).to include(
              todo_in_public_group_project,
              todo_in_public_subsubgroup_project,
              todo_in_private_subgroup_with_direct_membership_project
            )

            expect(member_user.todos).not_to include(todo_in_private_subgroup_project)
          end
        end

        context 'issuables', :sidekiq_inline do
          subject(:execute_service) do
            described_class.new(user).execute(@group_member, skip_subresources: true, unassign_issuables: true)
          end

          it 'removes assigned issuables, even in subresources' do
            expect(member_user.assigned_merge_requests).to be_empty
          end
        end
      end

      context 'without skipping of subresources' do
        subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: false) }

        before do
          execute_service
        end

        it 'removes the project membership' do
          expect(group_project.members.map(&:user)).not_to include(member_user)
        end

        it 'removes the group membership' do
          expect(group.members.map(&:user)).not_to include(member_user)
        end

        it 'removes the subgroup membership' do
          expect(subgroup.members.map(&:user)).not_to include(member_user)
        end

        it 'removes the subsubgroup membership' do
          expect(subsubgroup.members.map(&:user)).not_to include(member_user)
        end

        it 'removes the subsubproject membership' do
          expect(subsubproject.members.map(&:user)).not_to include(member_user)
        end

        it 'does not remove the user from the control project' do
          expect(control_project.members.map(&:user)).to include(user)
        end

        context 'todos', :sidekiq_inline do
          it 'removes todos for which the user no longer has access' do
            expect(member_user.todos).to include(
              todo_in_public_group_project,
              todo_in_public_subsubgroup_project
            )

            expect(member_user.todos).not_to include(
              todo_in_private_subgroup_project,
              todo_in_private_subgroup_with_direct_membership_project
            )
          end
        end

        context 'issuables', :sidekiq_inline do
          subject(:execute_service) do
            described_class.new(user).execute(@group_member, skip_subresources: false, unassign_issuables: true)
          end

          it 'removes assigned issuables' do
            expect(member_user.assigned_merge_requests).to be_empty
          end
        end
      end
    end

    context 'with invites' do
      before do
        create(:group_member, :developer, group: subsubgroup, user: member_user)
        create(:project_member, :invited, project: group_project, created_by: member_user)
        create(:group_member, :invited, group: group, created_by: member_user)
        create(:project_member, :invited, project: subsubproject, created_by: member_user)
        create(:group_member, :invited, group: subgroup, created_by: member_user)

        subsubproject.add_developer(member_user)
        control_project.add_maintainer(user)
        group.add_owner(user)

        @group_member = create(:group_member, :developer, group: group, user: member_user)
      end

      context 'with skipping of subresources' do
        before do
          described_class.new(user).execute(@group_member, skip_subresources: true)
        end

        it 'does not remove group members invited by deleted user' do
          expect(group.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
        end

        it 'does not remove project members invited by deleted user' do
          expect(group_project.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
        end

        it 'does not remove subgroup members invited by deleted user' do
          expect(subgroup.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
        end

        it 'does not remove subproject members invited by deleted user' do
          expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
        end
      end

      context 'without skipping of subresources' do
        before do
          described_class.new(user).execute(@group_member, skip_subresources: false)
        end

        it 'removes group members invited by deleted user' do
          expect(group.members.not_accepted_invitations_by_user(member_user)).to be_empty
        end

        it 'removes project members invited by deleted user' do
          expect(group_project.members.not_accepted_invitations_by_user(member_user)).to be_empty
        end

        it 'removes subgroup members invited by deleted user' do
          expect(subgroup.members.not_accepted_invitations_by_user(member_user)).to be_empty
        end

        it 'removes subproject members invited by deleted user' do
          expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).to be_empty
        end
      end
    end
  end

  context 'deletion of invitations created by deleted project member' do
    let(:user) { project.first_owner }
    let(:member_user) { create(:user) }

    let(:project) { create(:project) }

    before do
      create(:project_member, :invited, project: project, created_by: member_user)

      project_member = create(:project_member, :maintainer, user: member_user, project: project)

      described_class.new(user).execute(project_member)
    end

    it 'removes project members invited by deleted user' do
      expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty
    end
  end

  describe '#mark_as_recursive_call' do
    it 'marks the instance as recursive' do
      service = described_class.new(current_user)
      service.mark_as_recursive_call

      expect(service.send(:recursive_call?)).to eq(true)
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class RequestAccessService < Members::BaseService
    def execute(source)
      raise Gitlab::Access::AccessDeniedError unless can_request_access?(source)

      source.members.create(
        access_level: Gitlab::Access::DEVELOPER,
        user: current_user,
        requested_at: Time.current.utc)
    end

    private

    def can_request_access?(source)
      can?(current_user, :request_access, source)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::RequestAccessService, feature_category: :groups_and_projects do
  let(:user) { create(:user) }

  shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
    it 'raises Gitlab::Access::AccessDeniedError' do
      expect { described_class.new(user).execute(source) }.to raise_error(Gitlab::Access::AccessDeniedError)
    end
  end

  shared_examples 'a service creating a access request' do
    it 'succeeds' do
      expect { described_class.new(user).execute(source) }.to change { source.requesters.count }.by(1)
    end

    it 'returns a <Source>Member' do
      member = described_class.new(user).execute(source)

      expect(member).to be_a "#{source.class}Member".constantize
      expect(member.requested_at).to be_present
    end
  end

  context 'when source is nil' do
    it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
      let(:source) { nil }
    end
  end

  context 'when current user cannot request access to the project' do
    %i[project group].each do |source_type|
      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:source) { create(source_type, :private) }
      end
    end
  end

  context 'when access requests are disabled' do
    %i[project group].each do |source_type|
      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:source) { create(source_type, :public, :request_access_disabled) }
      end
    end
  end

  context 'when current user can request access to the project' do
    %i[project group].each do |source_type|
      it_behaves_like 'a service creating a access request' do
        let(:source) { create(source_type, :public) }
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class UpdateService < Members::BaseService
    # @param members [Member, Array<Member>]
    # returns the updated member(s)
    def execute(members, permission: :update)
      members = Array.wrap(members)

      old_access_level_expiry_map = members.to_h do |member|
        [member.id, { human_access: member.human_access, expires_at: member.expires_at }]
      end

      updated_members = update_members(members, permission)
      Member.transaction do
        updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) }
      end

      prepare_response(members)
    end

    private

    def update_members(members, permission)
      # `filter_map` avoids the `post_update` call for the member that resulted in no change
      Member.transaction do
        members.filter_map { |member| update_member(member, permission) }
      end
    rescue ActiveRecord::RecordInvalid
      []
    end

    def update_member(member, permission)
      raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission)

      member.attributes = params
      return unless member.changed?

      member.expiry_notified_at = nil if member.expires_at_changed?
      member.tap(&:save!)
    end

    def post_update(member, permission, old_access_level_expiry_map)
      old_access_level = old_access_level_expiry_map[member.id][:human_access]
      old_expiry = old_access_level_expiry_map[member.id][:expires_at]

      after_execute(action: permission, old_access_level: old_access_level, old_expiry: old_expiry, member: member)
      enqueue_delete_todos(member) if downgrading_to_guest? # Deletes only confidential issues todos for guests
    end

    def prepare_response(members)
      errored_members = members.select { |member| member.errors.any? }
      if errored_members.present?
        error_message = errored_members.flat_map { |member| member.errors.full_messages }.uniq.to_sentence
        return error(error_message, pass_back: { members: errored_members })
      end

      success(members: members)
    end

    def has_update_permissions?(member, permission)
      can?(current_user, action_member_permission(permission, member), member) &&
        !prevent_upgrade_to_owner?(member) &&
        !prevent_downgrade_from_owner?(member)
    end

    def downgrading_to_guest?
      params[:access_level] == Gitlab::Access::GUEST
    end

    def upgrading_to_owner?
      params[:access_level] == Gitlab::Access::OWNER
    end

    def downgrading_from_owner?(member)
      member.owner?
    end

    def prevent_upgrade_to_owner?(member)
      upgrading_to_owner? && cannot_assign_owner_responsibilities_to_member_in_project?(member)
    end

    def prevent_downgrade_from_owner?(member)
      downgrading_from_owner?(member) && cannot_revoke_owner_responsibilities_from_member_in_project?(member)
    end
  end
end

Members::UpdateService.prepend_mod_with('Members::UpdateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::UpdateService, feature_category: :groups_and_projects do
  let_it_be(:project) { create(:project, :public) }
  let_it_be(:group) { create(:group, :public) }
  let_it_be(:current_user) { create(:user) }
  let_it_be(:member_user1) { create(:user) }
  let_it_be(:member_user2) { create(:user) }
  let_it_be(:member_users) { [member_user1, member_user2] }
  let_it_be(:permission) { :update }
  let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
  let(:members) { source.members_and_requesters.where(user_id: member_users).to_a }
  let(:update_service) { described_class.new(current_user, params) }
  let(:params) { { access_level: access_level } }
  let(:updated_members) { subject[:members] }

  before do
    member_users.first.tap do |member_user|
      expires_at = 10.days.from_now
      project.add_member(member_user, Gitlab::Access::DEVELOPER, expires_at: expires_at)
      group.add_member(member_user, Gitlab::Access::DEVELOPER, expires_at: expires_at)
    end

    member_users[1..].each do |member_user|
      project.add_developer(member_user)
      group.add_developer(member_user)
    end
  end

  shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
    it 'raises Gitlab::Access::AccessDeniedError' do
      expect { subject }
        .to raise_error(Gitlab::Access::AccessDeniedError)
    end
  end

  shared_examples 'current user cannot update the given members' do
    it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
      let_it_be(:source) { project }
    end

    it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
      let_it_be(:source) { group }
    end
  end

  shared_examples 'returns error status when params are invalid' do
    let_it_be(:params) { { expires_at: 2.days.ago } }

    specify do
      expect(subject[:status]).to eq(:error)
    end
  end

  shared_examples 'a service updating members' do
    it 'updates the members' do
      new_access_levels = updated_members.map(&:access_level)

      expect(updated_members).not_to be_empty
      expect(updated_members).to all(be_valid)
      expect(new_access_levels).to all(be access_level)
    end

    it 'returns success status' do
      expect(subject.fetch(:status)).to eq(:success)
    end

    it 'invokes after_execute with correct args' do
      members.each do |member|
        expect(update_service).to receive(:after_execute).with(
          action: permission,
          old_access_level: member.human_access,
          old_expiry: member.expires_at,
          member: member
        )
      end

      subject
    end

    it 'authorization update callback is triggered' do
      expect(members).to all(receive(:refresh_member_authorized_projects).once)

      subject
    end

    it 'does not enqueues todos for deletion' do
      members.each do |member|
        expect(TodosDestroyer::EntityLeaveWorker)
          .not_to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name)
      end

      subject
    end

    context 'when members are downgraded to guest' do
      shared_examples 'schedules to delete confidential todos' do
        it do
          members.each do |member|
            expect(TodosDestroyer::EntityLeaveWorker)
              .to receive(:perform_in)
                    .with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once
          end

          new_access_levels = updated_members.map(&:access_level)
          expect(updated_members).to all(be_valid)
          expect(new_access_levels).to all(be Gitlab::Access::GUEST)
        end
      end

      context 'with Gitlab::Access::GUEST level as a string' do
        let_it_be(:params) { { access_level: Gitlab::Access::GUEST.to_s } }

        it_behaves_like 'schedules to delete confidential todos'
      end

      context 'with Gitlab::Access::GUEST level as an integer' do
        let_it_be(:params) { { access_level: Gitlab::Access::GUEST } }

        it_behaves_like 'schedules to delete confidential todos'
      end
    end

    context 'when access_level is invalid' do
      let_it_be(:params) { { access_level: 'invalid' } }

      it 'raises an error' do
        expect { described_class.new(current_user, params) }
          .to raise_error(ArgumentError, 'invalid value for Integer(): "invalid"')
      end
    end

    context 'when members update results in no change' do
      let(:params) { { access_level: members.first.access_level } }

      it 'does not invoke update! and post_update' do
        expect(update_service).not_to receive(:save!)
        expect(update_service).not_to receive(:post_update)

        expect(subject[:status]).to eq(:success)
      end

      it 'authorization update callback is not triggered' do
        members.each { |member| expect(member).not_to receive(:refresh_member_authorized_projects) }

        subject
      end
    end
  end

  shared_examples 'updating a project' do
    let_it_be(:group_project) { create(:project, group: create(:group)) }
    let_it_be(:source) { group_project }

    before do
      member_users.each { |member_user| group_project.add_developer(member_user) }
    end

    context 'as a project maintainer' do
      before do
        group_project.add_maintainer(current_user)
      end

      it_behaves_like 'a service updating members'

      context 'when member update results in an error' do
        it_behaves_like 'a service returning an error'
      end

      context 'and updating members to OWNER' do
        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let_it_be(:access_level) { Gitlab::Access::OWNER }
        end
      end

      context 'and updating themselves to OWNER' do
        let(:members) { source.members_and_requesters.find_by!(user_id: current_user.id) }

        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let_it_be(:access_level) { Gitlab::Access::OWNER }
        end
      end

      context 'and downgrading members from OWNER' do
        before do
          member_users.each { |member_user| group_project.add_owner(member_user) }
        end

        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
        end
      end
    end

    context 'when current_user is considered an owner in the project via inheritance' do
      before do
        group_project.group.add_owner(current_user)
      end

      context 'and can update members to OWNER' do
        before do
          member_users.each { |member_user| group_project.add_developer(member_user) }
        end

        it_behaves_like 'a service updating members' do
          let_it_be(:access_level) { Gitlab::Access::OWNER }
        end
      end

      context 'and can downgrade members from OWNER' do
        before do
          member_users.each { |member_user| group_project.add_owner(member_user) }
        end

        it_behaves_like 'a service updating members' do
          let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
        end
      end
    end

    context 'when project members expiration date is updated with expiry_notified_at' do
      let_it_be(:params) { { expires_at: 20.days.from_now } }

      before do
        group_project.group.add_owner(current_user)
        members.each do |member|
          member.update!(expiry_notified_at: Date.today)
        end
      end

      it "clear expiry_notified_at" do
        subject

        members.each do |member|
          expect(member.reload.expiry_notified_at).to be_nil
        end
      end
    end
  end

  shared_examples 'updating a group' do
    let_it_be(:source) { group }

    before do
      group.add_owner(current_user)
    end

    it_behaves_like 'a service updating members'

    context 'when member update results in an error' do
      it_behaves_like 'a service returning an error'
    end

    context 'when group members expiration date is updated' do
      let_it_be(:params) { { expires_at: 20.days.from_now } }
      let(:notification_service) { instance_double(NotificationService) }

      before do
        allow(NotificationService).to receive(:new).and_return(notification_service)
      end

      it 'emails the users that their group membership expiry has changed' do
        members.each do |member|
          expect(notification_service).to receive(:updated_group_member_expiration).with(member)
        end

        subject
      end
    end

    context 'when group members expiration date is updated with expiry_notified_at' do
      let_it_be(:params) { { expires_at: 20.days.from_now } }

      before do
        members.each do |member|
          member.update!(expiry_notified_at: Date.today)
        end
      end

      it "clear expiry_notified_at" do
        subject

        members.each do |member|
          expect(member.reload.expiry_notified_at).to be_nil
        end
      end
    end
  end

  subject { update_service.execute(members, permission: permission) }

  shared_examples 'a service returning an error' do
    it_behaves_like 'returns error status when params are invalid'

    context 'when a member update results in invalid record' do
      let(:member2) { members.second }

      before do
        allow(member2).to receive(:save!) do
          member2.errors.add(:user_id)
          member2.errors.add(:access_level)
        end.and_raise(ActiveRecord::RecordInvalid)
      end

      it 'returns the error' do
        response = subject

        expect(response[:status]).to eq(:error)
        expect(response[:message]).to eq('User is invalid and Access level is invalid')
      end

      it 'rollbacks back the entire update' do
        old_access_levels = members.pluck(:access_level)

        subject

        expect(members.each(&:reset).pluck(:access_level)).to eq(old_access_levels)
      end
    end
  end

  it_behaves_like 'current user cannot update the given members'
  it_behaves_like 'updating a project'
  it_behaves_like 'updating a group'

  context 'with a single member' do
    let(:members) { create(:group_member, group: group) }

    before do
      group.add_owner(current_user)
    end

    it 'returns the correct response' do
      expect(subject[:members]).to contain_exactly(members)
    end
  end

  context 'when current user is an admin', :enable_admin_mode do
    let_it_be(:current_user) { create(:admin) }
    let_it_be(:source) { group }

    context 'when all owners are being downgraded' do
      before do
        member_users.each { |member_user| group.add_owner(member_user) }
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
    end

    context 'when all blocked owners are being downgraded' do
      before do
        member_users.each do |member_user|
          group.add_owner(member_user)
          member_user.block
        end
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class CreateService < Members::BaseService
    BlankInvitesError = Class.new(StandardError)
    TooManyInvitesError = Class.new(StandardError)
    MembershipLockedError = Class.new(StandardError)

    DEFAULT_INVITE_LIMIT = 100

    attr_reader :membership_locked

    def initialize(*args)
      super

      @errors = []
      @invites = invites_from_params
      @source = params[:source]
    end

    def execute
      raise Gitlab::Access::AccessDeniedError unless can?(current_user, create_member_permission(source), source)

      if adding_at_least_one_owner && cannot_assign_owner_responsibilities_to_member_in_project?
        raise Gitlab::Access::AccessDeniedError
      end

      validate_invite_source!
      validate_invitable!

      add_members
      after_add_hooks

      enqueue_onboarding_progress_action

      publish_event!

      result
    rescue BlankInvitesError, TooManyInvitesError, MembershipLockedError => e
      Gitlab::ErrorTracking.log_exception(e, class: self.class.to_s, user_id: current_user.id)
      error(e.message)
    end

    def single_member
      members.last
    end

    private

    attr_reader :source, :errors, :invites, :member_created_namespace_id, :members

    def adding_at_least_one_owner
      params[:access_level] == Gitlab::Access::OWNER
    end

    def cannot_assign_owner_responsibilities_to_member_in_project?
      source.is_a?(Project) && !current_user.can?(:manage_owners, source)
    end

    def invites_from_params
      # String, Nil, Array, Integer
      return params[:user_id] if params[:user_id].is_a?(Array)
      return [] unless params[:user_id]

      params[:user_id].to_s.split(',').uniq
    end

    def validate_invite_source!
      raise ArgumentError, s_('AddMember|No invite source provided.') unless invite_source.present?
    end

    def validate_invitable!
      raise BlankInvitesError, blank_invites_message if invites.blank?

      return unless user_limit && invites.size > user_limit

      message = format(s_("AddMember|Too many users specified (limit is %{user_limit})"), user_limit: user_limit)
      raise TooManyInvitesError, message
    end

    def blank_invites_message
      s_('AddMember|No users specified.')
    end

    def add_members
      @members = creator_service.add_members(
        source, invites, params[:access_level], **create_params
      )

      members.each { |member| process_result(member) }
    end

    def creator_service
      "Members::#{source.class.to_s.pluralize}::CreatorService".constantize
    end

    def create_params
      {
        expires_at: params[:expires_at],
        current_user: current_user
      }
    end

    def process_result(member)
      existing_errors = member.errors.full_messages

      # calling invalid? clears any errors that were added outside of the
      # rails validation process
      if member.invalid? || existing_errors.present?
        add_error_for_member(member, existing_errors)
      else
        after_execute(member: member)
        @member_created_namespace_id ||= member.namespace_id
      end
    end

    # overridden
    def add_error_for_member(member, existing_errors)
      prefix = "#{member.user.username}: " if member.user.present?

      errors << "#{prefix}#{all_member_errors(member, existing_errors).to_sentence}"
    end

    def all_member_errors(member, existing_errors)
      existing_errors.concat(member.errors.full_messages).uniq
    end

    def after_add_hooks
      # overridden in subclasses/ee
    end

    def after_execute(member:)
      super

      track_invite_source(member)
    end

    def track_invite_source(member)
      Gitlab::Tracking.event(
        self.class.name,
        'create_member',
        label: invite_source,
        property: tracking_property(member),
        user: current_user
      )
    end

    def invite_source
      params[:invite_source]
    end

    def tracking_property(member)
      # ideally invites go down the invite service class instead, but there is nothing that limits an invite
      # from being used in this class and if you send emails as a comma separated list to the api/members
      # endpoint, it will support invites
      member.invite? ? 'net_new_user' : 'existing_user'
    end

    def user_limit
      limit = params.fetch(:limit, DEFAULT_INVITE_LIMIT)

      limit && limit < 0 ? nil : limit
    end

    def enqueue_onboarding_progress_action
      return unless at_least_one_member_created?

      Onboarding::UserAddedWorker.perform_async(member_created_namespace_id)
    end

    def at_least_one_member_created?
      member_created_namespace_id.present?
    end

    def result
      if errors.any?
        error(formatted_errors)
      else
        success
      end
    end

    def formatted_errors
      errors.to_sentence
    end

    def publish_event!
      return unless at_least_one_member_created?

      Gitlab::EventStore.publish(
        Members::MembersAddedEvent.new(data: {
          source_id: source.id,
          source_type: source.class.name
        })
      )
    end

    def create_member_permission(source)
      case source
      when Group
        :admin_group_member
      when Project
        :admin_project_member
      else
        raise "Unknown source type: #{source.class}!"
      end
    end
  end
end

Members::CreateService.prepend_mod_with('Members::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, :sidekiq_inline,
  feature_category: :groups_and_projects do
  let_it_be(:source, reload: true) { create(:project) }
  let_it_be(:user) { create(:user) }
  let_it_be(:member) { create(:user) }
  let_it_be(:user_invited_by_id) { create(:user) }
  let_it_be(:user_id) { member.id.to_s }
  let_it_be(:access_level) { Gitlab::Access::GUEST }

  let(:additional_params) { { invite_source: '_invite_source_' } }
  let(:params) { { user_id: user_id, access_level: access_level }.merge(additional_params) }
  let(:current_user) { user }

  subject(:execute_service) { described_class.new(current_user, params.merge({ source: source })).execute }

  before do
    case source
    when Project
      source.add_maintainer(user)
      Onboarding::Progress.onboard(source.namespace)
    when Group
      source.add_owner(user)
      Onboarding::Progress.onboard(source)
    end
  end

  context 'when the current user does not have permission to create members' do
    let(:current_user) { create(:user) }

    it 'raises a Gitlab::Access::AccessDeniedError' do
      expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
    end

    context 'when a project maintainer attempts to add owners' do
      let(:access_level) { Gitlab::Access::OWNER }

      before do
        source.add_maintainer(current_user)
      end

      it 'raises a Gitlab::Access::AccessDeniedError' do
        expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
      end
    end
  end

  context 'when passing an invalid source' do
    let_it_be(:source) { Object.new }

    it 'raises a RuntimeError' do
      expect { execute_service }.to raise_error(RuntimeError, 'Unknown source type: Object!')
    end
  end

  context 'when passing valid parameters' do
    it 'adds a user to members' do
      expect(execute_service[:status]).to eq(:success)
      expect(source.users).to include member
      expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
    end

    context 'when user_id is passed as an integer' do
      let(:user_id) { member.id }

      it 'successfully creates member' do
        expect(execute_service[:status]).to eq(:success)
        expect(source.users).to include member
        expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
      end
    end

    context 'with user_id as an array of integers' do
      let(:user_id) { [member.id, user_invited_by_id.id] }

      it 'successfully creates members' do
        expect(execute_service[:status]).to eq(:success)
        expect(source.users).to include(member, user_invited_by_id)
        expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
      end
    end

    context 'with user_id as an array of strings' do
      let(:user_id) { [member.id.to_s, user_invited_by_id.id.to_s] }

      it 'successfully creates members' do
        expect(execute_service[:status]).to eq(:success)
        expect(source.users).to include(member, user_invited_by_id)
        expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
      end
    end

    context 'when executing on a group' do
      let_it_be(:source) { create(:group) }

      it 'adds a user to members' do
        expect(execute_service[:status]).to eq(:success)
        expect(source.users).to include member
        expect(Onboarding::Progress.completed?(source, :user_added)).to be(true)
      end

      it 'triggers a members added event' do
        expect(Gitlab::EventStore)
          .to receive(:publish)
          .with(an_instance_of(Members::MembersAddedEvent))
          .and_call_original

        expect(execute_service[:status]).to eq(:success)
      end
    end

    context 'when only one user fails validations' do
      let_it_be(:source) { create(:project, group: create(:group)) }
      let(:user_id) { [member.id, user_invited_by_id.id] }

      before do
        # validations will fail because we try to invite them to the project as a guest
        source.group.add_developer(member)
      end

      it 'triggers the members added and authorizations changed events' do
        expect(Gitlab::EventStore)
          .to receive(:publish)
                .with(an_instance_of(ProjectAuthorizations::AuthorizationsChangedEvent))
                .and_call_original

        expect(Gitlab::EventStore)
          .to receive(:publish)
          .with(an_instance_of(Members::MembersAddedEvent))
          .and_call_original

        expect(execute_service[:status]).to eq(:error)
        expect(execute_service[:message])
          .to include 'Access level should be greater than or equal to Developer inherited membership from group'
        expect(source.users).not_to include(member)
        expect(source.users).to include(user_invited_by_id)
      end
    end

    context 'when all users fail validations' do
      let_it_be(:source) { create(:project, group: create(:group)) }
      let(:user_id) { [member.id, user_invited_by_id.id] }

      before do
        # validations will fail because we try to invite them to the project as a guest
        source.group.add_developer(member)
        source.group.add_developer(user_invited_by_id)
      end

      it 'does not trigger the members added event' do
        expect(Gitlab::EventStore)
          .not_to receive(:publish)
          .with(an_instance_of(Members::MembersAddedEvent))

        expect(execute_service[:status]).to eq(:error)
        expect(execute_service[:message])
          .to include 'Access level should be greater than or equal to Developer inherited membership from group'
        expect(source.users).not_to include(member, user_invited_by_id)
      end
    end
  end

  context 'when passing no user ids' do
    let(:user_id) { '' }

    it 'does not add a member' do
      expect(Gitlab::ErrorTracking)
        .to receive(:log_exception)
              .with(an_instance_of(described_class::BlankInvitesError), class: described_class.to_s, user_id: user.id)
      expect(Gitlab::EventStore)
        .not_to receive(:publish)
        .with(an_instance_of(Members::MembersAddedEvent))

      expect(execute_service[:status]).to eq(:error)
      expect(execute_service[:message]).to eq(s_('AddMember|No users specified.'))
      expect(source.users).not_to include member
      expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(false)
    end
  end

  context 'when passing many user ids' do
    let(:user_id) { 1.upto(101).to_a.join(',') }

    it 'limits the number of users to 100' do
      expect(Gitlab::ErrorTracking)
        .to receive(:log_exception)
              .with(an_instance_of(described_class::TooManyInvitesError), class: described_class.to_s, user_id: user.id)

      expect(execute_service[:status]).to eq(:error)
      expect(execute_service[:message]).to be_present
      expect(source.users).not_to include member
      expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(false)
    end
  end

  context 'when passing an invalid access level' do
    let(:access_level) { -1 }

    it 'does not add a member' do
      expect(execute_service[:status]).to eq(:error)
      expect(execute_service[:message]).to include("#{member.username}: Access level is not included in the list")
      expect(source.users).not_to include member
      expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(false)
    end
  end

  context 'when passing an existing invite user id' do
    let(:invited_member) { create(:project_member, :guest, :invited, project: source) }
    let(:user_id) { invited_member.invite_email }
    let(:access_level) { ProjectMember::MAINTAINER }

    it 'allows already invited members to be re-invited by email and updates the member access' do
      expect(execute_service[:status]).to eq(:success)
      expect(invited_member.reset.access_level).to eq ProjectMember::MAINTAINER
      expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
    end
  end

  context 'when adding a project_bot' do
    let_it_be(:project_bot) { create(:user, :project_bot) }

    let(:user_id) { project_bot.id }

    context 'when project_bot is already a member' do
      before do
        source.add_developer(project_bot)
      end

      it 'does not update the member' do
        expect(execute_service[:status]).to eq(:error)
        expect(execute_service[:message]).to eq("#{project_bot.username}: not authorized to update member")
        expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(false)
      end
    end

    context 'when project_bot is not already a member' do
      it 'adds the member' do
        expect(execute_service[:status]).to eq(:success)
        expect(source.users).to include project_bot
        expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(true)
      end
    end
  end

  context 'when tracking the invite source', :snowplow do
    context 'when invite_source is not passed' do
      let(:additional_params) { {} }

      it 'raises an error' do
        expect { execute_service }.to raise_error(ArgumentError, 'No invite source provided.')

        expect_no_snowplow_event
      end
    end

    context 'when invite_source is passed' do
      it 'tracks the invite source from params' do
        execute_service

        expect_snowplow_event(
          category: described_class.name,
          action: 'create_member',
          label: '_invite_source_',
          property: 'existing_user',
          user: user
        )
      end

      context 'with an already existing member' do
        before do
          source.add_developer(member)
        end

        it 'tracks the invite source from params' do
          execute_service

          expect_snowplow_event(
            category: described_class.name,
            action: 'create_member',
            label: '_invite_source_',
            property: 'existing_user',
            user: user
          )
        end
      end
    end

    context 'when it is a net_new_user' do
      let(:additional_params) { { invite_source: '_invite_source_', user_id: 'email@example.org' } }

      it 'tracks the invite source from params' do
        execute_service

        expect_snowplow_event(
          category: described_class.name,
          action: 'create_member',
          label: '_invite_source_',
          property: 'net_new_user',
          user: user
        )
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class ImportProjectTeamService < BaseService
    ImportProjectTeamForbiddenError = Class.new(StandardError)

    def initialize(*args)
      super

      @errors = {}
    end

    def execute
      check_target_and_source_projects_exist!
      check_user_permissions!

      import_project_team
      process_import_result

      result
    rescue ArgumentError, ImportProjectTeamForbiddenError => e
      ServiceResponse.error(message: e.message, reason: :unprocessable_entity)
    end

    private

    attr_reader :members, :params, :current_user, :errors, :result

    def import_project_team
      @members = target_project.team.import(source_project, current_user)

      if members.is_a?(Array)
        members.each { |member| check_member_validity(member) }
      else
        @result = ServiceResponse.error(message: 'Import failed', reason: :unprocessable_entity)
      end
    end

    def check_target_and_source_projects_exist!
      if target_project.blank?
        raise ArgumentError, 'Target project does not exist'
      elsif source_project.blank?
        raise ArgumentError, 'Source project does not exist'
      end
    end

    def check_user_permissions!
      return if can?(current_user, :read_project_member, source_project) &&
        can?(current_user, :import_project_members_from_another_project, target_project)

      raise ImportProjectTeamForbiddenError, 'Forbidden'
    end

    def check_member_validity(member)
      return if member.valid?

      errors[member.user.username] = member.errors.full_messages.to_sentence
    end

    def process_import_result
      @result ||= if errors.any?
                    ServiceResponse.error(message: errors, payload: { total_members_count: members.size })
                  else
                    ServiceResponse.success(message: 'Successfully imported')
                  end
    end

    def target_project_id
      params[:id]
    end

    def source_project_id
      params[:project_id]
    end

    def target_project
      @target_project ||= Project.find_by_id(target_project_id)
    end

    def source_project
      @source_project ||= Project.find_by_id(source_project_id)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_projects do
  describe '#execute' do
    let_it_be(:source_project) { create(:project) }
    let_it_be(:target_project) { create(:project) }
    let_it_be(:user) { create(:user) }

    let(:source_project_id) { source_project.id }
    let(:target_project_id) { target_project.id }

    subject(:import) { described_class.new(user, { id: target_project_id, project_id: source_project_id }) }

    before_all do
      source_project.add_guest(user)
      target_project.add_maintainer(user)
    end

    context 'when project team members are imported successfully' do
      it 'returns a successful response' do
        result = import.execute

        expect(result).to be_a(ServiceResponse)
        expect(result.success?).to be(true)
        expect(result.message).to eq('Successfully imported')
      end
    end

    context 'when the project team import fails' do
      context 'when the target project cannot be found' do
        let(:target_project_id) { non_existing_record_id }

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Target project does not exist')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when the source project cannot be found' do
        let(:source_project_id) { non_existing_record_id }

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Source project does not exist')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when the user doing the import does not exist' do
        let(:user) { nil }

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Forbidden')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when the user does not have permission to read the source project members' do
        let(:source_project_id) { create(:project, :private).id }

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Forbidden')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when the user does not have permission to admin the target project' do
        let(:target_project_id) { create(:project).id }

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Forbidden')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when the source and target project are valid but the ProjectTeam#import command fails' do
        before do
          allow_next_instance_of(ProjectTeam) do |project_team|
            allow(project_team).to receive(:import).and_return(false)
          end
        end

        it 'returns unsuccessful response' do
          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          expect(result.message).to eq('Import failed')
          expect(result.reason).to eq(:unprocessable_entity)
        end
      end

      context 'when one of the imported project members is invalid' do
        it 'returns unsuccessful response' do
          project_bot = create(:user, :project_bot)
          source_project.add_developer(project_bot)

          result = import.execute

          expect(result).to be_a(ServiceResponse)
          expect(result.error?).to be(true)
          message = { project_bot.username => 'User project bots cannot be added to other groups / projects' }
          expect(result.message).to eq(message)
          expect(result.payload[:total_members_count]).to eq(2)
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  class ApproveAccessRequestService < Members::BaseService
    def execute(access_requester, skip_authorization: false, skip_log_audit_event: false)
      validate_access!(access_requester) unless skip_authorization

      access_requester.access_level = params[:access_level] if params[:access_level]
      access_requester.accept_request(current_user)

      after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event)

      access_requester
    end

    private

    def after_execute(member:, skip_log_audit_event:)
      super

      resolve_access_request_todos(member)
    end

    def validate_access!(access_requester)
      raise Gitlab::Access::AccessDeniedError unless can_approve_access_requester?(access_requester)

      if approving_member_with_owner_access_level?(access_requester) &&
        cannot_assign_owner_responsibilities_to_member_in_project?(access_requester)
        raise Gitlab::Access::AccessDeniedError
      end
    end

    def can_approve_access_requester?(access_requester)
      can?(current_user, :admin_member_access_request, access_requester.source)
    end

    def approving_member_with_owner_access_level?(access_requester)
      access_level_value = params[:access_level] || access_requester.access_level

      access_level_value == Gitlab::Access::OWNER
    end
  end
end

Members::ApproveAccessRequestService.prepend_mod_with('Members::ApproveAccessRequestService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::ApproveAccessRequestService, feature_category: :groups_and_projects do
  let(:project) { create(:project, :public) }
  let(:group) { create(:group, :public) }
  let(:current_user) { create(:user) }
  let(:access_requester_user) { create(:user) }
  let(:access_requester) { source.requesters.find_by!(user_id: access_requester_user.id) }
  let(:opts) { {} }
  let(:params) { {} }
  let(:custom_access_level) { Gitlab::Access::MAINTAINER }

  shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
    it 'raises Gitlab::Access::AccessDeniedError' do
      expect do
        described_class.new(current_user, params).execute(access_requester, **opts)
      end.to raise_error(Gitlab::Access::AccessDeniedError)
    end
  end

  shared_examples 'a service approving an access request' do
    it 'succeeds' do
      expect do
        described_class.new(current_user, params).execute(access_requester, **opts)
      end.to change { source.requesters.count }.by(-1)
    end

    it 'returns a <Source>Member' do
      member = described_class.new(current_user, params).execute(access_requester, **opts)

      expect(member).to be_a "#{source.class}Member".constantize
      expect(member.requested_at).to be_nil
    end

    it 'calls the method to resolve access request for the approver' do
      expect_next_instance_of(described_class) do |instance|
        expect(instance).to receive(:resolve_access_request_todos).with(access_requester)
      end

      described_class.new(current_user, params).execute(access_requester, **opts)
    end

    it 'resolves the todos for the access requests' do
      expect_next_instance_of(TodoService) do |instance|
        expect(instance).to receive(:resolve_access_request_todos).with(access_requester)
      end

      described_class.new(current_user, params).execute(access_requester, **opts)
    end

    context 'with a custom access level' do
      let(:params) { { access_level: custom_access_level } }

      it 'returns a ProjectMember with the custom access level' do
        member = described_class.new(current_user, params).execute(access_requester, **opts)

        expect(member.access_level).to eq(custom_access_level)
      end
    end
  end

  context 'when an access requester is found' do
    before do
      project.request_access(access_requester_user)
      group.request_access(access_requester_user)
    end

    context 'when current user is nil' do
      let(:user) { nil }

      context 'and :ldap option is not given' do
        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let(:source) { project }
        end

        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let(:source) { group }
        end
      end

      context 'and :skip_authorization option is false' do
        let(:opts) { { skip_authorization: false } }

        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let(:source) { project }
        end

        it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
          let(:source) { group }
        end
      end

      context 'and :skip_authorization option is true' do
        let(:opts) { { skip_authorization: true } }

        it_behaves_like 'a service approving an access request' do
          let(:source) { project }
        end

        it_behaves_like 'a service approving an access request' do
          let(:source) { group }
        end
      end
    end

    context 'when current user cannot approve access request to the project' do
      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:source) { project }
      end

      it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
        let(:source) { group }
      end
    end

    context 'when current user can approve access request to the project' do
      before do
        project.add_maintainer(current_user)
        group.add_owner(current_user)
      end

      it_behaves_like 'a service approving an access request' do
        let(:source) { project }
      end

      it_behaves_like 'a service approving an access request' do
        let(:source) { group }
      end
    end

    context 'in a project' do
      let_it_be(:group_project) { create(:project, :public, group: create(:group, :public)) }

      let(:source) { group_project }
      let(:custom_access_level) { Gitlab::Access::OWNER }
      let(:params) { { access_level: custom_access_level } }

      before do
        group_project.request_access(access_requester_user)
      end

      context 'maintainers' do
        before do
          group_project.add_maintainer(current_user)
        end

        context 'cannot approve the access request of a requester to give them OWNER permissions' do
          it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
        end
      end

      context 'owners' do
        before do
          # so that `current_user` is considered an `OWNER` in the project via inheritance.
          group_project.group.add_owner(current_user)
        end

        context 'can approve the access request of a requester to give them OWNER permissions' do
          it_behaves_like 'a service approving an access request'
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  module Projects
    class CreatorService < Members::CreatorService
      class << self
        def cannot_manage_owners?(source, current_user)
          !Ability.allowed?(current_user, :manage_owners, source)
        end
      end

      private

      def can_create_new_member?
        return false if assigning_project_member_with_owner_access_level? &&
          cannot_assign_owner_responsibilities_to_member_in_project?

        # This access check(`admin_project_member`) will write to safe request store cache for the user being added.
        # This means any operations inside the same request will need to purge that safe request
        # store cache if operations are needed to be done inside the same request that checks max member access again on
        # that user.
        current_user.can?(:admin_project_member, member.project) || adding_the_creator_as_owner_in_a_personal_project?
      end

      def can_update_existing_member?
        # rubocop:disable Layout/EmptyLineAfterGuardClause
        raise ::Gitlab::Access::AccessDeniedError if assigning_project_member_with_owner_access_level? &&
          cannot_assign_owner_responsibilities_to_member_in_project?
        # rubocop:enable Layout/EmptyLineAfterGuardClause

        current_user.can?(:update_project_member, member)
      end

      def adding_the_creator_as_owner_in_a_personal_project?
        # this condition is reached during testing setup a lot due to use of `.add_member`
        member.project.personal_namespace_holder?(member.user)
      end

      def assigning_project_member_with_owner_access_level?
        return true if member && member.owner?

        access_level == Gitlab::Access::OWNER
      end

      def cannot_assign_owner_responsibilities_to_member_in_project?
        member.is_a?(ProjectMember) && !current_user.can?(:manage_owners, member.source)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::Projects::CreatorService, feature_category: :groups_and_projects do
  let_it_be(:source, reload: true) { create(:project, :public) }
  let_it_be(:source2, reload: true) { create(:project, :public) }
  let_it_be(:user) { create(:user) }

  describe '.access_levels' do
    it 'returns Gitlab::Access.sym_options_with_owner' do
      expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
    end
  end

  it_behaves_like 'owner management'

  describe '.add_members' do
    it_behaves_like 'bulk member creation' do
      let_it_be(:source_type) { Project }
      let_it_be(:member_type) { ProjectMember }
    end
  end

  describe '.add_member' do
    it_behaves_like 'member creation' do
      let_it_be(:member_type) { ProjectMember }
    end

    context 'authorized projects update' do
      it 'schedules a single project authorization update job when called multiple times' do
        stub_feature_flags(do_not_run_safety_net_auth_refresh_jobs: false)

        expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to receive(:bulk_perform_in).once

        1.upto(3) do
          described_class.add_member(source, user, :maintainer)
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Members
  module Groups
    class CreatorService < Members::CreatorService
      class << self
        def cannot_manage_owners?(source, current_user)
          source.max_member_access_for_user(current_user) < Gitlab::Access::OWNER
        end
      end

      private

      def can_create_new_member?
        current_user.can?(:admin_group_member, member.group)
      end

      def can_update_existing_member?
        current_user.can?(:update_group_member, member)
      end
    end
  end
end

Members::Groups::CreatorService.prepend_mod_with('Members::Groups::CreatorService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Members::Groups::CreatorService, feature_category: :groups_and_projects do
  let_it_be(:source, reload: true) { create(:group, :public) }
  let_it_be(:source2, reload: true) { create(:group, :public) }
  let_it_be(:user) { create(:user) }

  describe '.access_levels' do
    it 'returns Gitlab::Access.options_with_owner' do
      expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
    end
  end

  it_behaves_like 'owner management'

  describe '.add_members' do
    it_behaves_like 'bulk member creation' do
      let_it_be(:source_type) { Group }
      let_it_be(:member_type) { GroupMember }
    end
  end

  describe '.add_member' do
    it_behaves_like 'member creation' do
      let_it_be(:member_type) { GroupMember }
    end

    context 'authorized projects update' do
      it 'schedules a single project authorization update job when called multiple times' do
        # this is inline with the overridden behaviour in stubbed_member.rb
        worker_instance = AuthorizedProjectsWorker.new
        expect(AuthorizedProjectsWorker).to receive(:new).once.and_return(worker_instance)
        expect(worker_instance).to receive(:perform).with(user.id)

        1.upto(3) do
          described_class.add_member(source, user, :maintainer)
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module AlertManagement
  class ProcessPrometheusAlertService
    extend ::Gitlab::Utils::Override
    include ::AlertManagement::AlertProcessing
    include ::AlertManagement::Responses

    def initialize(project, payload, integration: nil)
      @project = project
      @payload = payload
      @integration = integration
    end

    def execute
      return bad_request unless incoming_payload.has_required_attributes?

      process_alert
      return bad_request unless alert.persisted?

      complete_post_processing_tasks

      success(alert)
    end

    private

    attr_reader :project, :payload, :integration

    override :incoming_payload
    def incoming_payload
      strong_memoize(:incoming_payload) do
        Gitlab::AlertManagement::Payload.parse(
          project,
          payload,
          integration: integration,
          monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe AlertManagement::ProcessPrometheusAlertService, feature_category: :incident_management do
  let_it_be(:project, reload: true) { create(:project, :repository) }

  let(:service) { described_class.new(project, payload) }

  describe '#execute' do
    include_context 'incident management settings enabled'

    subject(:execute) { service.execute }

    before do
      stub_licensed_features(oncall_schedules: false, generic_alert_fingerprinting: false)
    end

    context 'when alert payload is valid' do
      let_it_be(:starts_at) { '2020-04-27T10:10:22.265949279Z' }
      let_it_be(:title) { 'Alert title' }
      let_it_be(:fingerprint) { [starts_at, title, 'vector(1)'].join('/') }
      let_it_be(:source) { 'Prometheus' }

      let(:prometheus_status) { 'firing' }
      let(:payload) do
        {
          'status' => prometheus_status,
          'labels' => {
            'alertname' => 'GitalyFileServerDown',
            'channel' => 'gitaly',
            'pager' => 'pagerduty',
            'severity' => 's1'
          },
          'annotations' => {
            'description' => 'Alert description',
            'runbook' => 'troubleshooting/gitaly-down.md',
            'title' => title
          },
          'startsAt' => starts_at,
          'endsAt' => '2020-04-27T10:20:22.265949279Z',
          'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
        }
      end

      it_behaves_like 'processes new firing alert'
      include_examples 'handles race condition in alert creation'

      context 'with resolving payload' do
        let(:prometheus_status) { 'resolved' }

        it_behaves_like 'processes recovery alert'
      end

      context 'environment given' do
        let(:environment) { create(:environment, project: project) }
        let(:alert) { project.alert_management_alerts.last }

        before do
          payload['labels']['gitlab_environment_name'] = environment.name
        end

        it 'sets the environment' do
          execute

          expect(alert.environment).to eq(environment)
        end
      end
    end

    context 'when alert payload is invalid' do
      let(:payload) { {} }

      it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module AlertManagement
  class CreateAlertIssueService
    include Gitlab::Utils::StrongMemoize

    DEFAULT_ALERT_TITLE = ::Gitlab::AlertManagement::Payload::Generic::DEFAULT_TITLE
    DEFAULT_INCIDENT_TITLE = 'New: Incident'

    # @param alert [AlertManagement::Alert]
    # @param user [User]
    def initialize(alert, user)
      @alert = alert
      @user = user
    end

    def execute
      return error_no_permissions unless allowed?
      return error_issue_already_exists if alert.issue

      result = create_incident
      return result unless result.success?

      issue = result[:issue]
      perform_after_create_tasks(issue)

      result
    end

    private

    attr_reader :alert, :user

    delegate :project, to: :alert

    def allowed?
      user.can?(:create_issue, project)
    end

    def create_incident
      ::IncidentManagement::Incidents::CreateService.new(
        project,
        user,
        title: alert_presenter.title,
        description: alert_presenter.issue_description,
        severity: alert.severity,
        alert: alert
      ).execute
    end

    def update_title_for(issue)
      return unless issue.title == DEFAULT_ALERT_TITLE

      issue.update!(title: "#{DEFAULT_INCIDENT_TITLE} #{issue.iid}")
    end

    def perform_after_create_tasks(issue)
      update_title_for(issue)

      SystemNoteService.new_alert_issue(alert, issue, user)
    end

    def error(message, issue = nil)
      ServiceResponse.error(payload: { issue: issue }, message: message)
    end

    def error_issue_already_exists
      error(_('An issue already exists'))
    end

    def error_no_permissions
      error(_('You have no permissions'))
    end

    def alert_presenter
      strong_memoize(:alert_presenter) do
        alert.present
      end
    end
  end
end

AlertManagement::CreateAlertIssueService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe AlertManagement::CreateAlertIssueService, feature_category: :incident_management do
  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }
  let_it_be(:project) { create(:project, group: group) }
  let_it_be(:payload) do
    {
      'startsAt' => '2020-04-27T10:10:22.265949279Z',
      'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
    }
  end

  let_it_be(:generic_alert, reload: true) { create(:alert_management_alert, :triggered, project: project, payload: payload) }
  let_it_be(:prometheus_alert, reload: true) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }

  let(:alert) { generic_alert }
  let(:alert_presenter) { alert.present }
  let(:created_issue) { Issue.last! }

  describe '#execute' do
    subject(:execute) { described_class.new(alert, user).execute }

    before do
      allow(user).to receive(:can?).and_call_original
      allow(user).to receive(:can?)
        .with(:create_issue, project)
        .and_return(can_create)
    end

    shared_examples 'creating an alert issue' do
      it 'creates an issue' do
        expect { execute }.to change { project.issues.count }.by(1)
      end

      it 'returns a created issue' do
        expect(execute.payload).to eq(issue: created_issue)
      end

      it 'has a successful status' do
        expect(execute).to be_success
      end

      it 'sets alert.issue_id in the same ActiveRecord query execution' do
        execute

        expect(alert.issue_id).to eq(created_issue.id)
      end

      it 'creates a system note' do
        expect { execute }.to change { alert.reload.notes.count }.by(1)
      end
    end

    shared_examples 'setting an issue attributes' do
      before do
        execute
      end

      it 'sets issue author to the current user' do
        expect(created_issue.author).to eq(user)
      end

      it 'sets the issue title' do
        expect(created_issue.title).to eq(alert.title)
      end

      it 'sets the issue description' do
        expect(created_issue.description).to include(alert_presenter.send(:issue_summary_markdown).strip)
      end
    end

    context 'when a user is allowed to create an issue' do
      let(:can_create) { true }

      before do
        project.add_developer(user)
      end

      it 'checks permissions' do
        execute
        expect(user).to have_received(:can?).with(:create_issue, project).exactly(2).times
      end

      context 'with alert severity' do
        using RSpec::Parameterized::TableSyntax

        where(:alert_severity, :incident_severity) do
          'critical' | 'critical'
          'high'     | 'high'
          'medium'   | 'medium'
          'low'      | 'low'
          'info'     | 'unknown'
          'unknown'  | 'unknown'
        end

        with_them do
          before do
            alert.update!(severity: alert_severity)
            execute
          end

          it 'sets the correct severity level' do
            expect(created_issue.severity).to eq(incident_severity)
          end
        end
      end

      context 'when the alert is prometheus alert' do
        let(:alert) { prometheus_alert }
        let(:issue) { subject.payload[:issue] }

        it_behaves_like 'creating an alert issue'
        it_behaves_like 'setting an issue attributes'
      end

      context 'when the alert is generic' do
        let(:alert) { generic_alert }
        let(:issue) { subject.payload[:issue] }
        let(:default_alert_title) { described_class::DEFAULT_ALERT_TITLE }

        it_behaves_like 'creating an alert issue'
        it_behaves_like 'setting an issue attributes'

        context 'when alert title matches the default title exactly' do
          before do
            generic_alert.update!(title: default_alert_title)
          end

          it 'updates issue title with the IID' do
            execute

            expect(created_issue.title).to eq("New: Incident #{created_issue.iid}")
          end
        end

        context 'when the alert title contains the default title' do
          let(:non_default_alert_title) { "Not #{default_alert_title}" }

          before do
            generic_alert.update!(title: non_default_alert_title)
          end

          it 'does not change issue title' do
            execute

            expect(created_issue.title).to eq(non_default_alert_title)
          end
        end
      end

      context 'when issue cannot be created' do
        let(:alert) { generic_alert }

        before do
          # Invalid alert
          alert.update_columns(title: '')
        end

        it 'has an unsuccessful status' do
          expect(execute).to be_error
          expect(execute.errors).to contain_exactly("Title can't be blank")
        end
      end

      context 'when alert cannot be updated' do
        let(:alert) { create(:alert_management_alert, :with_validation_errors, :triggered, project: project, payload: payload) }

        it 'responds with error' do
          expect(execute).to be_error
          expect(execute.errors).to contain_exactly('Hosts hosts array is over 255 chars')
        end
      end

      context 'when alert already has an attached issue' do
        let!(:issue) { create(:issue, project: project) }

        before do
          alert.update!(issue_id: issue.id)
        end

        it 'does not create yet another issue' do
          expect { execute }.not_to change(Issue, :count)
        end

        it 'responds with error' do
          expect(execute).to be_error
          expect(execute.message).to eq(_('An issue already exists'))
        end
      end
    end

    context 'when a user is not allowed to create an issue' do
      let(:can_create) { false }

      it 'checks permissions' do
        execute
        expect(user).to have_received(:can?).with(:create_issue, project)
      end

      it 'responds with error' do
        expect(execute).to be_error
        expect(execute.message).to eq(_('You have no permissions'))
      end
    end
  end
end