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

module Projects
  class UpdatePagesService < BaseService
    include Gitlab::Utils::StrongMemoize

    # old deployment can be cached by pages daemon
    # so we need to give pages daemon some time update cache
    # 10 minutes is enough, but 30 feels safer
    OLD_DEPLOYMENTS_DESTRUCTION_DELAY = 30.minutes

    attr_reader :build, :deployment_update

    def initialize(project, build)
      @project = project
      @build = build
      @deployment_update = ::Gitlab::Pages::DeploymentUpdate.new(project, build)
    end

    def execute
      register_attempt

      ::Ci::Pipelines::AddJobService.new(@build.pipeline).execute!(commit_status) do |job|
        job.enqueue!
        job.run!
      end

      return error(deployment_update.errors.first.full_message) unless deployment_update.valid?

      build.artifacts_file.use_file do |artifacts_path|
        deployment = create_pages_deployment(artifacts_path, build)

        break error('The uploaded artifact size does not match the expected value') unless deployment
        break error(deployment_update.errors.first.full_message) unless deployment_update.valid?

        deactive_old_deployments(deployment)
        success
      end
    rescue StandardError => e
      error(e.message)
      raise e
    end

    private

    def success
      commit_status.success
      publish_deployed_event
      super
    end

    def error(message)
      register_failure
      log_error("Projects::UpdatePagesService: #{message}")
      commit_status.allow_failure = !deployment_update.latest?
      commit_status.description = message
      commit_status.drop(:script_failure)
      super
    end

    # Create status notifying the deployment of pages
    def commit_status
      GenericCommitStatus.new(
        user: build.user,
        ci_stage: stage,
        name: 'pages:deploy',
        stage: 'deploy',
        stage_idx: stage.position
      )
    end
    strong_memoize_attr :commit_status

    # rubocop: disable Performance/ActiveRecordSubtransactionMethods
    def stage
      build.pipeline.stages.safe_find_or_create_by(name: 'deploy', pipeline_id: build.pipeline.id) do |stage|
        stage.position = GenericCommitStatus::EXTERNAL_STAGE_IDX
        stage.project = build.project
      end
    end
    strong_memoize_attr :commit_status
    # rubocop: enable Performance/ActiveRecordSubtransactionMethods

    def create_pages_deployment(artifacts_path, build)
      File.open(artifacts_path) do |file|
        attributes = pages_deployment_attributes(file, build)
        deployment = project.pages_deployments.build(**attributes)

        break if deployment.file.size != file.size

        deployment.tap(&:save!)
      end
    end

    # overridden on EE
    def pages_deployment_attributes(file, build)
      {
        file: file,
        file_count: deployment_update.entries_count,
        file_sha256: build.job_artifacts_archive.file_sha256,
        ci_build_id: build.id,
        root_directory: build.options[:publish],
        upload_ready: false
      }
    end

    def deactive_old_deployments(deployment)
      PagesDeployment.deactivate_deployments_older_than(
        deployment,
        time: OLD_DEPLOYMENTS_DESTRUCTION_DELAY.from_now)
    end

    def register_attempt
      pages_deployments_total_counter.increment
    end

    def register_failure
      pages_deployments_failed_total_counter.increment
    end

    def pages_deployments_total_counter
      Gitlab::Metrics.counter(:pages_deployments_total, "Counter of GitLab Pages deployments triggered")
    end
    strong_memoize_attr :pages_deployments_total_counter

    def pages_deployments_failed_total_counter
      Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed")
    end
    strong_memoize_attr :pages_deployments_failed_total_counter

    def publish_deployed_event
      event = ::Pages::PageDeployedEvent.new(data: {
        project_id: project.id,
        namespace_id: project.namespace_id,
        root_namespace_id: project.root_namespace.id
      })

      Gitlab::EventStore.publish(event)
    end
  end
end

::Projects::UpdatePagesService.prepend_mod

```

Response:
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Projects::UpdatePagesService, feature_category: :pages do
  let_it_be(:project, refind: true) { create(:project, :repository) }

  let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
  let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }

  let(:options) { {} }
  let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD', options: options) }
  let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }

  let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
  let(:custom_root_file) { fixture_file_upload("spec/fixtures/pages_with_custom_root.zip") }
  let(:empty_file) { fixture_file_upload("spec/fixtures/pages_empty.zip") }
  let(:empty_metadata_filename) { "spec/fixtures/pages_empty.zip.meta" }
  let(:metadata_filename) { "spec/fixtures/pages.zip.meta" }
  let(:custom_root_file_metadata) { "spec/fixtures/pages_with_custom_root.zip.meta" }
  let(:metadata) { fixture_file_upload(metadata_filename) if File.exist?(metadata_filename) }

  subject(:service) { described_class.new(project, build) }

  RSpec.shared_examples 'old deployments' do
    it 'deactivates old deployments from the same project with the same path prefix', :freeze_time do
      other_project = create(:pages_deployment)
      same_project_other_path_prefix = create(:pages_deployment, project: project, path_prefix: 'other')
      same_project = create(:pages_deployment, project: project)

      expect { expect(service.execute[:status]).to eq(:success) }
        .to not_change { other_project.reload.deleted_at }
        .and not_change { same_project_other_path_prefix.reload.deleted_at }
        .and change { same_project.reload.deleted_at }
        .from(nil).to(described_class::OLD_DEPLOYMENTS_DESTRUCTION_DELAY.from_now)
    end
  end

  RSpec.shared_examples 'pages size limit is' do |size_limit|
    context "when size is below the limit" do
      before do
        allow(metadata).to receive(:total_size).and_return(size_limit - 1.megabyte)
        allow(metadata).to receive(:entries).and_return([])
      end

      it 'updates pages correctly' do
        subject.execute

        deploy_status = GenericCommitStatus.last
        expect(deploy_status.description).not_to be_present
        expect(project.pages_deployed?).to eq(true)
      end

      it_behaves_like 'old deployments'
    end

    context "when size is above the limit" do
      before do
        allow(metadata).to receive(:total_size).and_return(size_limit + 1.megabyte)
        allow(metadata).to receive(:entries).and_return([])
      end

      it 'limits the maximum size of gitlab pages' do
        subject.execute

        deploy_status = GenericCommitStatus.last
        expect(deploy_status.description).to match(/artifacts for pages are too large/)
        expect(deploy_status).to be_script_failure
      end
    end
  end

  context 'when a deploy stage already exists', :aggregate_failures do
    let!(:stage) { create(:ci_stage, name: 'deploy', pipeline: pipeline) }

    it 'assigns the deploy stage' do
      expect { service.execute }
        .to change(GenericCommitStatus, :count).by(1)
        .and change(Ci::Stage.where(name: 'deploy'), :count).by(0)

      status = GenericCommitStatus.last

      expect(status.ci_stage).to eq(stage)
      expect(status.ci_stage.name).to eq('deploy')
      expect(status.stage_name).to eq('deploy')
      expect(status.stage).to eq('deploy')
    end
  end

  context 'when a deploy stage does not exists' do
    it 'assigns the deploy stage' do
      expect { service.execute }
        .to change(GenericCommitStatus, :count).by(1)
        .and change(Ci::Stage.where(name: 'deploy'), :count).by(1)

      status = GenericCommitStatus.last

      expect(status.ci_stage.name).to eq('deploy')
      expect(status.stage_name).to eq('deploy')
      expect(status.stage).to eq('deploy')
    end
  end

  context 'for new artifacts' do
    context "for a valid job" do
      let!(:artifacts_archive) { create(:ci_job_artifact, :correct_checksum, file: file, job: build) }

      before do
        create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)

        build.reload
      end

      it_behaves_like 'old deployments'

      it "doesn't delete artifacts after deploying" do
        expect(service.execute[:status]).to eq(:success)

        expect(project.pages_deployed?).to eq(true)
        expect(build.artifacts?).to eq(true)
      end

      it 'succeeds' do
        expect { expect(service.execute[:status]).to eq(:success) }
          .to change { project.pages_deployed? }
          .from(false).to(true)
      end

      it 'publishes a PageDeployedEvent event with project id and namespace id' do
        expected_data = {
          project_id: project.id,
          namespace_id: project.namespace_id,
          root_namespace_id: project.root_namespace.id
        }

        expect { service.execute }.to publish_event(Pages::PageDeployedEvent).with(expected_data)
      end

      it 'creates pages_deployment' do
        expect { expect(service.execute[:status]).to eq(:success) }
          .to change { project.pages_deployments.count }
          .by(1)

        deployment = project.pages_deployments.last

        expect(deployment.size).to eq(file.size)
        expect(deployment.file).to be_present
        expect(deployment.file_count).to eq(3)
        expect(deployment.file_sha256).to eq(artifacts_archive.file_sha256)
        expect(deployment.ci_build_id).to eq(build.id)
        expect(deployment.root_directory).to be_nil
      end

      it 'does not fail if pages_metadata is absent' do
        project.pages_metadatum.destroy!
        project.reload

        expect { expect(service.execute[:status]).to eq(:success) }
          .to change { project.pages_deployments.count }
          .by(1)
      end

      context 'when archive does not have pages directory' do
        let(:file) { empty_file }
        let(:metadata_filename) { empty_metadata_filename }

        it 'returns an error' do
          expect(service.execute[:status]).not_to eq(:success)

          expect(GenericCommitStatus.last.description)
            .to eq(
              "Error: You need to either include a `public/` folder in your artifacts, " \
              "or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`")
        end
      end

      context 'when there is a custom root config' do
        let(:file) { custom_root_file }
        let(:metadata_filename) { custom_root_file_metadata }

        context 'when the directory specified with `publish` is included in the artifacts' do
          let(:options) { { publish: 'foo' } }

          it 'creates pages_deployment and saves it in the metadata' do
            expect(service.execute[:status]).to eq(:success)

            deployment = project.pages_deployments.last
            expect(deployment.root_directory).to eq(options[:publish])
          end
        end

        context 'when the directory specified with `publish` is not included in the artifacts' do
          let(:options) { { publish: 'bar' } }

          it 'returns an error' do
            expect(service.execute[:status]).not_to eq(:success)

            expect(GenericCommitStatus.last.description)
              .to eq(
                "Error: You need to either include a `public/` folder in your artifacts, " \
                "or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`")
          end
        end

        context 'when there is a folder named `public`, but `publish` specifies a different one' do
          let(:options) { { publish: 'foo' } }
          let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
          let(:metadata_filename) { "spec/fixtures/pages.zip.meta" }

          it 'returns an error' do
            expect(service.execute[:status]).not_to eq(:success)

            expect(GenericCommitStatus.last.description)
              .to eq(
                "Error: You need to either include a `public/` folder in your artifacts, " \
                "or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`")
          end
        end
      end

      it 'limits pages size' do
        stub_application_setting(max_pages_size: 1)
        expect(service.execute[:status]).not_to eq(:success)
      end

      it 'limits pages file count' do
        create(:plan_limits, :default_plan, pages_file_entries: 2)

        expect(service.execute[:status]).not_to eq(:success)

        expect(GenericCommitStatus.last.description)
          .to eq("pages site contains 3 file entries, while limit is set to 2")
      end

      context 'when timeout happens by DNS error' do
        before do
          allow_next_instance_of(described_class) do |instance|
            allow(instance).to receive(:create_pages_deployment).and_raise(SocketError)
          end
        end

        it 'raises an error' do
          expect { service.execute }.to raise_error(SocketError)

          build.reload

          deploy_status = GenericCommitStatus.last
          expect(deploy_status).to be_failed
          expect(project.pages_deployed?).to eq(false)
        end
      end

      context 'when missing artifacts metadata' do
        before do
          allow(build).to receive(:artifacts_metadata?).and_return(false)
        end

        it 'does not raise an error as failed job' do
          service.execute

          build.reload

          deploy_status = GenericCommitStatus.last
          expect(deploy_status).to be_failed
          expect(project.pages_deployed?).to eq(false)
        end
      end

      context 'with background jobs running', :sidekiq_inline do
        it 'succeeds' do
          expect(project.pages_deployed?).to be_falsey
          expect(service.execute[:status]).to eq(:success)
        end
      end

      context "when sha on branch was updated before deployment was uploaded" do
        before do
          expect(subject).to receive(:create_pages_deployment).and_wrap_original do |m, *args|
            build.update!(ref: 'feature')
            m.call(*args)
          end
        end

        it 'creates a new pages deployment' do
          expect { expect(service.execute[:status]).to eq(:success) }
            .to change { project.pages_deployments.count }.by(1)

          deployment = project.pages_deployments.last
          expect(deployment.ci_build_id).to eq(build.id)
        end

        it_behaves_like 'old deployments'

        context 'when newer deployment present' do
          it 'fails with outdated reference message' do
            new_pipeline = create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha)
            new_build = create(:ci_build, name: 'pages', pipeline: new_pipeline, ref: 'HEAD')
            create(:pages_deployment, project: project, ci_build: new_build)

            expect(service.execute[:status]).to eq(:error)

            deploy_status = GenericCommitStatus.last
            expect(deploy_status).to be_failed
            expect(deploy_status.description).to eq('build SHA is outdated for this ref')
          end
        end
      end

      it 'fails when uploaded deployment size is wrong' do
        allow_next_instance_of(PagesDeployment) do |deployment|
          allow(deployment)
            .to receive(:file)
            .and_return(instance_double(Pages::DeploymentUploader, size: file.size + 1))
        end

        expect(service.execute[:status]).not_to eq(:success)

        expect(GenericCommitStatus.last.description)
          .to eq('The uploaded artifact size does not match the expected value')
      end
    end
  end

  # this situation should never happen in real life because all new archives have sha256
  # and we only use new archives
  # this test is here just to clarify that this behavior is intentional
  context 'when artifacts archive does not have sha256' do
    let!(:artifacts_archive) { create(:ci_job_artifact, file: file, job: build) }

    before do
      create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)

      build.reload
    end

    it 'fails with exception raised' do
      expect { service.execute }
        .to raise_error("Validation failed: File sha256 can't be blank")
    end
  end

  it 'fails if no artifacts' do
    expect(service.execute[:status]).not_to eq(:success)
  end

  it 'fails for invalid archive' do
    create(:ci_job_artifact, :archive, file: invalid_file, job: build)
    expect(service.execute[:status]).not_to eq(:success)
  end

  describe 'maximum pages artifacts size' do
    let(:metadata) { spy('metadata') }

    before do
      file = fixture_file_upload('spec/fixtures/pages.zip')
      metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')

      create(:ci_job_artifact, :archive, :correct_checksum, file: file, job: build)
      create(:ci_job_artifact, :metadata, file: metafile, job: build)

      allow(build).to receive(:artifacts_metadata_entry)
        .and_return(metadata)
    end

    context 'when maximum pages size is set to zero' do
      before do
        stub_application_setting(max_pages_size: 0)
      end

      it_behaves_like 'pages size limit is', ::Gitlab::Pages::MAX_SIZE
    end

    context 'when size is limited on the instance level' do
      before do
        stub_application_setting(max_pages_size: 100)
      end

      it_behaves_like 'pages size limit is', 100.megabytes
    end
  end

  context 'when retrying the job' do
    let(:stage) { create(:ci_stage, position: 1_000_000, name: 'deploy', pipeline: pipeline) }
    let!(:older_deploy_job) do
      create(
        :generic_commit_status,
        :failed,
        pipeline: pipeline,
        ref: build.ref,
        ci_stage: stage,
        name: 'pages:deploy'
      )
    end

    before do
      create(:ci_job_artifact, :correct_checksum, file: file, job: build)
      create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)
      build.reload
    end

    it 'marks older pages:deploy jobs retried' do
      expect(service.execute[:status]).to eq(:success)

      expect(older_deploy_job.reload).to be_retried

      deploy_status = GenericCommitStatus.last
      expect(deploy_status.ci_stage).to eq(stage)
      expect(deploy_status.stage_idx).to eq(stage.position)
    end
  end
end


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

# NOTE: This service cannot be used directly because it is part of a
# a bigger process. Instead, use the service MoveAccessService which moves
# project memberships, project group links, authorizations and refreshes
# the authorizations if necessary
module Projects
  class MoveProjectMembersService < BaseMoveRelationsService
    def execute(source_project, remove_remaining_elements: true)
      return unless super

      Project.transaction do
        move_project_members
        remove_remaining_members if remove_remaining_elements

        success
      end
    end

    private

    def move_project_members
      non_existent_members.update_all(source_id: @project.id)
    end

    def remove_remaining_members
      # Remove remaining members and authorizations from source_project
      source_project.project_members.destroy_all # rubocop: disable Cop/DestroyAll
    end

    def project_members_in_target_project
      @project.project_members.select(:user_id)
    end

    # Look for members in source_project that are not in the target project
    # rubocop: disable CodeReuse/ActiveRecord
    def non_existent_members
      source_project.members
                    .select(:id)
                    .where.not(user_id: @project.project_members.select(:user_id))
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::MoveProjectMembersService, feature_category: :groups_and_projects do
  let!(:user) { create(:user) }
  let(:project_with_users) { create(:project, namespace: user.namespace) }
  let(:target_project) { create(:project, namespace: user.namespace) }
  let(:maintainer_user) { create(:user) }
  let(:reporter_user) { create(:user) }
  let(:developer_user) { create(:user) }

  subject { described_class.new(target_project, user) }

  describe '#execute' do
    before do
      project_with_users.add_maintainer(maintainer_user)
      project_with_users.add_developer(developer_user)
      project_with_users.add_reporter(reporter_user)
    end

    it 'moves the members from one project to another' do
      expect(project_with_users.project_members.count).to eq 4
      expect(target_project.project_members.count).to eq 1

      subject.execute(project_with_users)

      expect(project_with_users.project_members.count).to eq 0
      expect(target_project.project_members.count).to eq 4
    end

    it 'does not move existent members to the current project' do
      target_project.add_maintainer(developer_user)
      target_project.add_developer(reporter_user)

      expect(project_with_users.project_members.count).to eq 4
      expect(target_project.project_members.count).to eq 3

      subject.execute(project_with_users)

      expect(project_with_users.project_members.count).to eq 0
      expect(target_project.project_members.count).to eq 4
    end

    it 'rollbacks changes if transaction fails' do
      allow(subject).to receive(:success).and_raise(StandardError)

      expect { subject.execute(project_with_users) }.to raise_error(StandardError)

      expect(project_with_users.project_members.count).to eq 4
      expect(target_project.project_members.count).to eq 1
    end

    context 'when remove_remaining_elements is false' do
      let(:options) { { remove_remaining_elements: false } }

      it 'does not remove remaining project members' do
        target_project.add_maintainer(developer_user)
        target_project.add_developer(reporter_user)

        subject.execute(project_with_users, **options)

        expect(project_with_users.project_members.count).not_to eq 0
      end
    end
  end
end


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

# Service class for getting and caching the number of merge requests of several projects
# Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids
module Projects
  class BatchOpenMergeRequestsCountService < Projects::BatchCountService
    # rubocop: disable CodeReuse/ActiveRecord
    def global_count
      @global_count ||= count_service.query(project_ids).group(:project_id).count
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def count_service
      ::Projects::OpenMergeRequestsCountService
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::BatchOpenMergeRequestsCountService, feature_category: :code_review_workflow do
  subject { described_class.new([project_1, project_2]) }

  let_it_be(:project_1) { create(:project) }
  let_it_be(:project_2) { create(:project) }

  describe '#refresh_cache_and_retrieve_data', :use_clean_rails_memory_store_caching do
    before do
      create(:merge_request, source_project: project_1, target_project: project_1)
      create(:merge_request, source_project: project_2, target_project: project_2)
    end

    it 'refreshes cache keys correctly when cache is clean', :aggregate_failures do
      subject.refresh_cache_and_retrieve_data

      expect(Rails.cache.read(get_cache_key(subject, project_1))).to eq(1)
      expect(Rails.cache.read(get_cache_key(subject, project_2))).to eq(1)

      expect { subject.refresh_cache_and_retrieve_data }.not_to exceed_query_limit(0)
    end
  end

  def get_cache_key(subject, project)
    subject.count_service
      .new(project)
      .cache_key
  end
end


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

module Projects
  class SlackApplicationInstallService < BaseService
    include Gitlab::Routing

    # Endpoint to initiate the OAuth flow, redirects to Slack's authorization screen
    # https://api.slack.com/authentication/oauth-v2#asking
    SLACK_AUTHORIZE_URL = 'https://slack.com/oauth/v2/authorize'

    # Endpoint to exchange the temporary authorization code for an access token
    # https://api.slack.com/authentication/oauth-v2#exchanging
    SLACK_EXCHANGE_TOKEN_URL = 'https://slack.com/api/oauth.v2.access'

    def execute
      slack_data = exchange_slack_token

      return error("Slack: #{slack_data['error']}") unless slack_data['ok']

      integration = project.gitlab_slack_application_integration \
        || project.create_gitlab_slack_application_integration!

      installation = integration.slack_integration || integration.build_slack_integration

      installation.update!(
        bot_user_id: slack_data['bot_user_id'],
        bot_access_token: slack_data['access_token'],
        team_id: slack_data.dig('team', 'id'),
        team_name: slack_data.dig('team', 'name'),
        alias: project.full_path,
        user_id: slack_data.dig('authed_user', 'id'),
        authorized_scope_names: slack_data['scope']
      )

      update_legacy_installations!(installation)

      success
    end

    private

    def exchange_slack_token
      query = {
        client_id: Gitlab::CurrentSettings.slack_app_id,
        client_secret: Gitlab::CurrentSettings.slack_app_secret,
        code: params[:code],
        # NOTE: Needs to match the `redirect_uri` passed to the authorization endpoint,
        # otherwise we get a `bad_redirect_uri` error.
        redirect_uri: slack_auth_project_settings_slack_url(project)
      }

      Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
    end

    # Update any legacy SlackIntegration records for the Slack Workspace. Legacy SlackIntegration records
    # are any created before our Slack App was upgraded to use Granular Bot Permissions and issue a
    # bot_access_token. Any SlackIntegration records for the Slack Workspace will already have the same
    # bot_access_token.
    def update_legacy_installations!(installation)
      updatable_attributes = installation.attributes.slice(
        'user_id',
        'bot_user_id',
        'encrypted_bot_access_token',
        'encrypted_bot_access_token_iv',
        'updated_at'
      )

      SlackIntegration.by_team(installation.team_id).id_not_in(installation.id).each_batch do |batch|
        batch_ids = batch.pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
        batch.update_all(updatable_attributes)

        ::Integrations::SlackWorkspace::IntegrationApiScope.update_scopes(batch_ids, installation.slack_api_scopes)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::SlackApplicationInstallService, feature_category: :integrations do
  let_it_be(:user) { create(:user) }
  let_it_be_with_refind(:project) { create(:project) }

  let(:integration) { project.gitlab_slack_application_integration }
  let(:installation) { integration.slack_integration }

  let(:slack_app_id) { 'A12345' }
  let(:slack_app_secret) { 'secret' }
  let(:oauth_code) { 'code' }
  let(:params) { { code: oauth_code } }
  let(:exchange_url) { described_class::SLACK_EXCHANGE_TOKEN_URL }
  let(:redirect_url) { Gitlab::Routing.url_helpers.slack_auth_project_settings_slack_url(project) }

  subject(:service) { described_class.new(project, user, params) }

  before do
    stub_application_setting(slack_app_id: slack_app_id, slack_app_secret: slack_app_secret)

    query = {
      client_id: slack_app_id,
      client_secret: slack_app_secret,
      code: oauth_code,
      redirect_uri: redirect_url
    }

    stub_request(:get, exchange_url)
      .with(query: query)
      .to_return(body: response.to_json, headers: { 'Content-Type' => 'application/json' })
  end

  context 'when Slack responds with an error' do
    let(:response) do
      {
        ok: false,
        error: 'something is wrong'
      }
    end

    it 'returns error result' do
      result = service.execute

      expect(result).to eq(message: 'Slack: something is wrong', status: :error)
    end
  end

  context 'when Slack responds with an access token' do
    let_it_be(:team_id) { 'T11111' }
    let_it_be(:team_name) { 'Team name' }
    let_it_be(:user_id) { 'U11111' }
    let_it_be(:bot_user_id) { 'U99999' }
    let_it_be(:bot_access_token) { 'token-XXXXX' }

    let(:response) do
      {
        ok: true,
        app_id: 'A12345',
        authed_user: { id: user_id },
        token_type: 'bot',
        access_token: bot_access_token,
        bot_user_id: bot_user_id,
        team: { id: team_id, name: 'Team name' },
        enterprise: { is_enterprise_install: false },
        scope: 'chat:a,chat:b,chat:c'
      }
    end

    shared_examples 'success response' do
      it 'returns success result and creates all needed records' do
        result = service.execute

        expect(result).to eq(status: :success)
        expect(integration).to be_present
        expect(installation).to be_present
        expect(installation).to have_attributes(
          integration_id: integration.id,
          team_id: team_id,
          team_name: team_name,
          alias: project.full_path,
          user_id: user_id,
          bot_user_id: bot_user_id,
          bot_access_token: bot_access_token,
          authorized_scope_names: contain_exactly('chat:a', 'chat:b', 'chat:c')
        )
      end
    end

    it_behaves_like 'success response'

    context 'when integration record already exists' do
      before do
        project.create_gitlab_slack_application_integration!
      end

      it_behaves_like 'success response'

      context 'when installation record already exists' do
        before do
          integration.create_slack_integration!(
            team_id: 'old value',
            team_name: 'old value',
            alias: 'old value',
            user_id: 'old value',
            bot_user_id: 'old value',
            bot_access_token: 'old value'
          )
        end

        it_behaves_like 'success response'
      end
    end

    context 'when the team has other Slack installation records' do
      let_it_be_with_reload(:other_installation) { create(:slack_integration, team_id: team_id) }
      let_it_be_with_reload(:other_legacy_installation) { create(:slack_integration, :legacy, team_id: team_id) }
      let_it_be_with_reload(:legacy_installation_for_other_team) { create(:slack_integration, :legacy) }

      it_behaves_like 'success response'

      it 'updates related legacy records' do
        travel_to(1.minute.from_now) do
          expected_attributes = {
            'user_id' => user_id,
            'bot_user_id' => bot_user_id,
            'bot_access_token' => bot_access_token,
            'updated_at' => Time.current,
            'authorized_scope_names' => %w[chat:a chat:b chat:c]
          }

          service.execute

          expect(other_installation).to have_attributes(expected_attributes)
          expect(other_legacy_installation).to have_attributes(expected_attributes)
          expect(legacy_installation_for_other_team).not_to have_attributes(expected_attributes)
        end
      end
    end
  end
end


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

module Projects
  # Service class for performing operations that should take place after a
  # project has been renamed.
  #
  # Example usage:
  #
  #     project = Project.find(42)
  #
  #     project.update(...)
  #
  #     Projects::AfterRenameService.new(project).execute
  class AfterRenameService
    include BaseServiceUtility

    # @return [String] The Project being renamed.
    attr_reader :project

    # @return [String] The path slug the project was using, before the rename took place.
    attr_reader :path_before

    # @return [String] The full path of the namespace + project, before the rename took place.
    attr_reader :full_path_before

    # @return [String] The full path of the namespace + project, after the rename took place.
    attr_reader :full_path_after

    RenameFailedError = Class.new(StandardError)

    # @param [Project] project The Project being renamed.
    # @param [String] path_before The path slug the project was using, before the rename took place.
    def initialize(project, path_before:, full_path_before:)
      @project = project
      @path_before = path_before
      @full_path_before = full_path_before
      @full_path_after = project.full_path
    end

    def execute
      rename_base_repository_in_registry!
      expire_caches_before_rename
      rename_or_migrate_repository!
      send_move_instructions
      execute_system_hooks
      update_repository_configuration
      rename_transferred_documents
      log_completion
      publish_event
    end

    def rename_base_repository_in_registry!
      return unless project.has_container_registry_tags?

      ensure_registry_tags_can_be_handled

      result = ContainerRegistry::GitlabApiClient.rename_base_repository_path(
        full_path_before, name: project_path)

      return if result == :ok

      rename_failed!("Renaming the base repository in the registry failed with error #{result}.")
    end

    def ensure_registry_tags_can_be_handled
      return if Feature.enabled?(:renaming_project_with_tags, project) &&
        ContainerRegistry::GitlabApiClient.supports_gitlab_api?

      rename_failed!("Project #{full_path_before} cannot be renamed because images are " \
      "present in its container registry")
    end

    def expire_caches_before_rename
      project.expire_caches_before_rename(full_path_before)
    end

    def rename_or_migrate_repository!
      success =
        ::Projects::HashedStorage::MigrationService
          .new(project, full_path_before)
          .execute

      return if success

      rename_failed!("Repository #{full_path_before} could not be renamed to #{full_path_after}")
    end

    def send_move_instructions
      return unless send_move_instructions?

      project.send_move_instructions(full_path_before)
    end

    def execute_system_hooks
      project.old_path_with_namespace = full_path_before
      system_hook_service.execute_hooks_for(project, :rename)
    end

    def update_repository_configuration
      project.reload_repository!
      project.set_full_path
      project.track_project_repository
    end

    def rename_transferred_documents
      if rename_uploads?
        Gitlab::UploadsTransfer
          .new
          .rename_project(path_before, project_path, namespace_full_path)
      end
    end

    def log_completion
      log_info(
        "Project #{project.id} has been renamed from " \
          "#{full_path_before} to #{full_path_after}"
      )
    end

    def send_move_instructions?
      !project.import_started?
    end

    def rename_uploads?
      !project.hashed_storage?(:attachments)
    end

    def project_path
      project.path
    end

    def namespace_full_path
      project.namespace.full_path
    end

    def rename_failed!(error)
      log_error(error)

      raise RenameFailedError, error
    end

    def publish_event
      event = Projects::ProjectPathChangedEvent.new(data: {
        project_id: project.id,
        namespace_id: project.namespace_id,
        root_namespace_id: project.root_namespace.id,
        old_path: full_path_before,
        new_path: full_path_after
      })

      Gitlab::EventStore.publish(event)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::AfterRenameService, feature_category: :groups_and_projects do
  let(:legacy_storage) { Storage::LegacyProject.new(project) }
  let(:hashed_storage) { Storage::Hashed.new(project) }
  let!(:path_before_rename) { project.path }
  let!(:full_path_before_rename) { project.full_path }
  let!(:path_after_rename) { "#{project.path}-renamed" }
  let!(:full_path_after_rename) { "#{project.full_path}-renamed" }
  let!(:repo_before_rename) { project.repository.raw }
  let!(:wiki_repo_before_rename) { project.wiki.repository.raw }

  let(:repo_after_rename) do
    Gitlab::Git::Repository.new(project.repository_storage, "#{full_path_after_rename}.git", nil, nil)
  end

  let(:wiki_repo_after_rename) do
    Gitlab::Git::Repository.new(project.repository_storage, "#{full_path_after_rename}.wiki.git", nil, nil)
  end

  describe '#execute' do
    let(:project) { create(:project, :repository, skip_disk_validation: true) }
    let(:gitlab_shell) { Gitlab::Shell.new }
    let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) }
    let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) }
    let(:hashed_path) { File.join(hashed_prefix, hash) }
    let(:message) { "Repository #{full_path_before_rename} could not be renamed to #{full_path_after_rename}" }

    before do
      # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
      # call. This makes testing a bit easier.
      allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)

      stub_application_setting(hashed_storage_enabled: true)
    end

    shared_examples 'logging and raising a RenameFailedError' do
      it 'logs raises a RenameFailedError' do
        expect_any_instance_of(described_class).to receive(:log_error).with(message)

        expect { service_execute }
          .to raise_error(described_class::RenameFailedError)
      end
    end

    it 'renames a repository' do
      stub_container_registry_config(enabled: false)

      expect_any_instance_of(SystemHooksService)
        .to receive(:execute_hooks_for)
        .with(project, :rename)

      expect(project).to receive(:expire_caches_before_rename)

      service_execute
    end

    context 'when renaming or migrating fails' do
      before do
        allow_any_instance_of(::Projects::HashedStorage::MigrationService)
          .to receive(:execute).and_return(false)
      end

      it_behaves_like 'logging and raising a RenameFailedError'
    end

    context 'container registry with images' do
      let(:container_repository) { create(:container_repository) }
      let(:message) do
        "Project #{full_path_before_rename} cannot be renamed because images are " \
          "present in its container registry"
      end

      before do
        stub_container_registry_config(enabled: true)
        stub_container_registry_tags(repository: :any, tags: ['tag'])
        project.container_repositories << container_repository
      end

      context 'when feature renaming_project_with_tags is disabled' do
        before do
          stub_feature_flags(renaming_project_with_tags: false)
        end

        it_behaves_like 'logging and raising a RenameFailedError'
      end

      context 'when Gitlab API is not supported' do
        before do
          allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(false)
        end

        it_behaves_like 'logging and raising a RenameFailedError'
      end

      context 'when Gitlab API Client is supported' do
        before do
          allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true)
        end

        it 'renames the base repository in the registry' do
          expect(ContainerRegistry::GitlabApiClient).to receive(:rename_base_repository_path)
            .with(full_path_before_rename, name: path_after_rename).and_return(:ok)

          service_execute
        end

        context 'when the base repository rename in the registry fails' do
          before do
            allow(ContainerRegistry::GitlabApiClient)
              .to receive(:rename_base_repository_path).and_return(:bad_request)
          end

          let(:message) { 'Renaming the base repository in the registry failed with error bad_request.' }

          it_behaves_like 'logging and raising a RenameFailedError'
        end
      end
    end

    context 'attachments' do
      let(:uploader) { create(:upload, :issuable_upload, :with_file, model: project) }
      let(:file_uploader) { build(:file_uploader, project: project) }
      let(:legacy_storage_path) { File.join(file_uploader.root, legacy_storage.disk_path) }
      let(:hashed_storage_path) { File.join(file_uploader.root, hashed_storage.disk_path) }

      it 'keeps uploads folder location unchanged' do
        expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)

        service_execute
      end

      context 'when not rolled out' do
        let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }

        it 'moves attachments folder to hashed storage' do
          expect(File.directory?(legacy_storage_path)).to be_truthy
          expect(File.directory?(hashed_storage_path)).to be_falsey

          service_execute
          expect(project.reload.hashed_storage?(:attachments)).to be_truthy

          expect(File.directory?(legacy_storage_path)).to be_falsey
          expect(File.directory?(hashed_storage_path)).to be_truthy
        end
      end
    end

    it 'updates project full path in gitaly' do
      service_execute

      expect(project.repository.full_path).to eq(project.full_path)
    end

    it 'updates storage location' do
      service_execute

      expect(project.project_repository).to have_attributes(
        disk_path: project.disk_path,
        shard_name: project.repository_storage
      )
    end

    context 'EventStore' do
      let(:project) { create(:project, :repository, skip_disk_validation: true) }

      it 'publishes a ProjectPathChangedEvent' do
        expect { service_execute }
          .to publish_event(Projects::ProjectPathChangedEvent)
          .with(
            project_id: project.id,
            namespace_id: project.namespace_id,
            root_namespace_id: project.root_namespace.id,
            old_path: full_path_before_rename,
            new_path: full_path_after_rename
          )
      end
    end
  end

  def service_execute
    # AfterRenameService is called by UpdateService after a successful model.update
    # the initialization will include before and after paths values
    project.update!(path: path_after_rename)

    described_class.new(project, path_before: path_before_rename, full_path_before: full_path_before_rename).execute
  end
end


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

module Projects
  class MoveForksService < BaseMoveRelationsService
    def execute(source_project, remove_remaining_elements: true)
      return unless super && source_project.fork_network

      Project.transaction do
        move_fork_network_members
        update_root_project
        refresh_forks_count

        success
      end
    end

    private

    # rubocop: disable CodeReuse/ActiveRecord
    def move_fork_network_members
      ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
      ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # rubocop: disable CodeReuse/ActiveRecord
    def update_root_project
      # Update root network project
      ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def refresh_forks_count
      Projects::ForksCountService.new(@project).refresh_cache
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::MoveForksService, feature_category: :source_code_management do
  include ProjectForksHelper

  let!(:user) { create(:user) }
  let!(:project_with_forks) { create(:project, namespace: user.namespace) }
  let!(:target_project) { create(:project, namespace: user.namespace) }
  let!(:lvl1_forked_project_1) { fork_project(project_with_forks, user) }
  let!(:lvl1_forked_project_2) { fork_project(project_with_forks, user) }
  let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
  let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }

  subject { described_class.new(target_project, user) }

  describe '#execute' do
    context 'when moving a root forked project' do
      it 'moves the descendant forks' do
        expect(project_with_forks.forks.count).to eq 2
        expect(target_project.forks.count).to eq 0

        subject.execute(project_with_forks)

        expect(project_with_forks.forks.count).to eq 0
        expect(target_project.forks.count).to eq 2
        expect(lvl1_forked_project_1.forked_from_project).to eq target_project
        expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq target_project
        expect(lvl1_forked_project_2.forked_from_project).to eq target_project
        expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq target_project
      end

      it 'updates the fork network' do
        expect(project_with_forks.fork_network.root_project).to eq project_with_forks
        expect(project_with_forks.fork_network.fork_network_members.map(&:project)).to include project_with_forks

        subject.execute(project_with_forks)

        expect(target_project.reload.fork_network.root_project).to eq target_project
        expect(target_project.fork_network.fork_network_members.map(&:project)).not_to include project_with_forks
      end
    end

    context 'when moving a intermediate forked project' do
      it 'moves the descendant forks' do
        expect(lvl1_forked_project_1.forks.count).to eq 2
        expect(target_project.forks.count).to eq 0

        subject.execute(lvl1_forked_project_1)

        expect(lvl1_forked_project_1.forks.count).to eq 0
        expect(target_project.forks.count).to eq 2
        expect(lvl2_forked_project_1_1.forked_from_project).to eq target_project
        expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq target_project
        expect(lvl2_forked_project_1_2.forked_from_project).to eq target_project
        expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq target_project
      end

      it 'moves the ascendant fork' do
        subject.execute(lvl1_forked_project_1)

        expect(target_project.forked_from_project).to eq project_with_forks
        expect(target_project.fork_network_member.forked_from_project).to eq project_with_forks
      end

      it 'does not update fork network' do
        subject.execute(lvl1_forked_project_1)

        expect(target_project.reload.fork_network.root_project).to eq project_with_forks
      end
    end

    context 'when moving a leaf forked project' do
      it 'moves the ascendant fork' do
        subject.execute(lvl2_forked_project_1_1)

        expect(target_project.forked_from_project).to eq lvl1_forked_project_1
        expect(target_project.fork_network_member.forked_from_project).to eq lvl1_forked_project_1
      end

      it 'does not update fork network' do
        subject.execute(lvl2_forked_project_1_1)

        expect(target_project.reload.fork_network.root_project).to eq project_with_forks
      end
    end

    it 'rollbacks changes if transaction fails' do
      allow(subject).to receive(:success).and_raise(StandardError)

      expect { subject.execute(project_with_forks) }.to raise_error(StandardError)

      expect(project_with_forks.forks.count).to eq 2
      expect(target_project.forks.count).to eq 0
    end
  end
end


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

module Projects
  class MoveUsersStarProjectsService < BaseMoveRelationsService
    def execute(source_project, remove_remaining_elements: true)
      return unless super

      user_stars = source_project.users_star_projects

      return unless user_stars.any?

      Project.transaction do
        user_stars.update_all(project_id: @project.id)

        @project.update(star_count: @project.starrers.with_state(:active).size)
        source_project.update(star_count: source_project.starrers.with_state(:active).size)

        success
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::MoveUsersStarProjectsService, feature_category: :groups_and_projects do
  let!(:user) { create(:user) }
  let!(:project_with_stars) { create(:project, namespace: user.namespace) }
  let!(:target_project) { create(:project, namespace: user.namespace) }

  subject { described_class.new(target_project, user) }

  describe '#execute' do
    before do
      create_list(:users_star_project, 2, project: project_with_stars)
    end

    it 'moves the user\'s stars from one project to another' do
      project_with_stars.reload
      target_project.reload

      expect(project_with_stars.users_star_projects.count).to eq 2
      expect(project_with_stars.star_count).to eq 2
      expect(target_project.users_star_projects.count).to eq 0
      expect(target_project.star_count).to eq 0

      subject.execute(project_with_stars)
      project_with_stars.reload
      target_project.reload

      expect(project_with_stars.users_star_projects.count).to eq 0
      expect(project_with_stars.star_count).to eq 0
      expect(target_project.users_star_projects.count).to eq 2
      expect(target_project.star_count).to eq 2
    end

    it 'rollbacks changes if transaction fails' do
      allow(subject).to receive(:success).and_raise(StandardError)

      expect { subject.execute(project_with_stars) }.to raise_error(StandardError)
      project_with_stars.reload
      target_project.reload

      expect(project_with_stars.users_star_projects.count).to eq 2
      expect(project_with_stars.star_count).to eq 2
      expect(target_project.users_star_projects.count).to eq 0
      expect(target_project.star_count).to eq 0
    end
  end
end


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

module Projects
  class AutocompleteService < BaseService
    include LabelsAsHash
    def issues
      IssuesFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
    end

    def milestones
      finder_params = {
        project_ids: [@project.id],
        state: :active,
        order: { due_date: :asc, title: :asc }
      }

      finder_params[:group_ids] = @project.group.self_and_ancestors.select(:id) if @project.group

      MilestonesFinder.new(finder_params).execute.select([:iid, :title, :due_date])
    end

    def merge_requests
      MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
    end

    def commands(noteable)
      return [] unless noteable && current_user

      QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
    end

    def snippets
      SnippetsFinder.new(current_user, project: project).execute.select([:id, :title])
    end

    def contacts(target)
      available_contacts = Crm::ContactsFinder.new(current_user, group: project.group).execute
        .select([:id, :email, :first_name, :last_name, :state])

      contact_hashes = available_contacts.as_json

      return contact_hashes unless target.is_a?(Issue)

      ids = target.customer_relations_contacts.ids # rubocop:disable CodeReuse/ActiveRecord

      contact_hashes.each do |hash|
        hash[:set] = ids.include?(hash['id'])
      end

      contact_hashes
    end

    def labels_as_hash(target)
      super(target, project_id: project.id, include_ancestor_groups: true)
    end
  end
end

Projects::AutocompleteService.prepend_mod_with('Projects::AutocompleteService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_projects do
  describe '#issues' do
    describe 'confidential issues' do
      let(:author) { create(:user) }
      let(:assignee) { create(:user) }
      let(:non_member) { create(:user) }
      let(:member) { create(:user) }
      let(:admin) { create(:admin) }
      let(:project) { create(:project, :public) }
      let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
      let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
      let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) }

      it 'does not list project confidential issues for guests' do
        autocomplete = described_class.new(project, nil)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).not_to include security_issue_1.iid
        expect(issues).not_to include security_issue_2.iid
        expect(issues.count).to eq 1
      end

      it 'does not list project confidential issues for non project members' do
        autocomplete = described_class.new(project, non_member)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).not_to include security_issue_1.iid
        expect(issues).not_to include security_issue_2.iid
        expect(issues.count).to eq 1
      end

      it 'does not list project confidential issues for project members with guest role' do
        project.add_guest(member)

        autocomplete = described_class.new(project, non_member)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).not_to include security_issue_1.iid
        expect(issues).not_to include security_issue_2.iid
        expect(issues.count).to eq 1
      end

      it 'lists project confidential issues for author' do
        autocomplete = described_class.new(project, author)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).to include security_issue_1.iid
        expect(issues).not_to include security_issue_2.iid
        expect(issues.count).to eq 2
      end

      it 'lists project confidential issues for assignee' do
        autocomplete = described_class.new(project, assignee)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).not_to include security_issue_1.iid
        expect(issues).to include security_issue_2.iid
        expect(issues.count).to eq 2
      end

      it 'lists project confidential issues for project members' do
        project.add_developer(member)

        autocomplete = described_class.new(project, member)
        issues = autocomplete.issues.map(&:iid)

        expect(issues).to include issue.iid
        expect(issues).to include security_issue_1.iid
        expect(issues).to include security_issue_2.iid
        expect(issues.count).to eq 3
      end

      context 'when admin mode is enabled', :enable_admin_mode do
        it 'lists all project issues for admin', :enable_admin_mode do
          autocomplete = described_class.new(project, admin)
          issues = autocomplete.issues.map(&:iid)

          expect(issues).to include issue.iid
          expect(issues).to include security_issue_1.iid
          expect(issues).to include security_issue_2.iid
          expect(issues.count).to eq 3
        end
      end

      context 'when admin mode is disabled' do
        it 'does not list project confidential issues for admin' do
          autocomplete = described_class.new(project, admin)
          issues = autocomplete.issues.map(&:iid)

          expect(issues).to include issue.iid
          expect(issues).not_to include security_issue_1.iid
          expect(issues).not_to include security_issue_2.iid
          expect(issues.count).to eq 1
        end
      end
    end
  end

  describe '#milestones' do
    let(:user) { create(:user) }
    let(:group) { create(:group) }
    let(:project) { create(:project, group: group) }
    let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
    let!(:group_milestone2) { create(:milestone, group: group, due_date: '2017-01-01', title: 'First Title') }
    let!(:project_milestone) { create(:milestone, project: project, due_date: '2016-01-01') }

    let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }

    it 'includes project and group milestones and sorts them correctly' do
      expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title, group_milestone1.title])
    end

    it 'does not include closed milestones' do
      group_milestone1.close

      expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title])
    end

    it 'does not include milestones from other projects in the group' do
      other_project = create(:project, group: group)
      project_milestone.update!(project: other_project)

      expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
    end

    context 'with nested groups' do
      let(:subgroup) { create(:group, :public, parent: group) }
      let!(:subgroup_milestone) { create(:milestone, group: subgroup) }

      before do
        project.update!(namespace: subgroup)
      end

      it 'includes project milestones and all acestors milestones' do
        expect(milestone_titles).to match_array(
          [project_milestone.title, group_milestone2.title, group_milestone1.title, subgroup_milestone.title]
        )
      end
    end
  end

  describe '#contacts' do
    let_it_be(:user) { create(:user) }
    let_it_be(:group) { create(:group, :crm_enabled) }
    let_it_be(:project) { create(:project, group: group) }
    let_it_be(:contact_1) { create(:contact, group: group) }
    let_it_be(:contact_2) { create(:contact, group: group) }
    let_it_be(:contact_3) { create(:contact, :inactive, group: group) }

    let(:issue) { nil }

    subject { described_class.new(project, user).contacts(issue).as_json }

    before do
      group.add_developer(user)
    end

    it 'returns CRM contacts from group' do
      expected_contacts = [
        { 'id' => contact_1.id, 'email' => contact_1.email,
          'first_name' => contact_1.first_name, 'last_name' => contact_1.last_name, 'state' => contact_1.state },
        { 'id' => contact_2.id, 'email' => contact_2.email,
          'first_name' => contact_2.first_name, 'last_name' => contact_2.last_name, 'state' => contact_2.state },
        { 'id' => contact_3.id, 'email' => contact_3.email,
          'first_name' => contact_3.first_name, 'last_name' => contact_3.last_name, 'state' => contact_3.state }
      ]

      expect(subject).to match_array(expected_contacts)
    end

    context 'some contacts are already assigned to the issue' do
      let(:issue) { create(:issue, project: project) }

      before do
        issue.customer_relations_contacts << [contact_2, contact_3]
      end

      it 'marks already assigned contacts as set' do
        expected_contacts = [
          { 'id' => contact_1.id, 'email' => contact_1.email,
            'first_name' => contact_1.first_name, 'last_name' => contact_1.last_name, 'state' => contact_1.state, 'set' => false },
          { 'id' => contact_2.id, 'email' => contact_2.email,
            'first_name' => contact_2.first_name, 'last_name' => contact_2.last_name, 'state' => contact_2.state, 'set' => true },
          { 'id' => contact_3.id, 'email' => contact_3.email,
            'first_name' => contact_3.first_name, 'last_name' => contact_3.last_name, 'state' => contact_3.state, 'set' => true }
        ]

        expect(subject).to match_array(expected_contacts)
      end
    end
  end

  describe '#labels_as_hash' do
    def expect_labels_to_equal(labels, expected_labels)
      expect(labels.size).to eq(expected_labels.size)
      extract_title = lambda { |label| label['title'] }
      expect(labels.map(&extract_title)).to match_array(expected_labels.map(&extract_title))
    end

    let(:user) { create(:user) }
    let(:group) { create(:group, :nested) }
    let!(:sub_group) { create(:group, parent: group) }
    let(:project) { create(:project, :public, group: group) }
    let(:issue) { create(:issue, project: project) }

    let!(:label1) { create(:label, project: project) }
    let!(:label2) { create(:label, project: project) }
    let!(:sub_group_label) { create(:group_label, group: sub_group) }
    let!(:parent_group_label) { create(:group_label, group: group.parent, group_id: group.id) }

    before do
      create(:group_member, group: group, user: user)
    end

    it 'returns labels from project and ancestor groups' do
      service = described_class.new(project, user)
      results = service.labels_as_hash(nil)
      expected_labels = [label1, label2, parent_group_label]

      expect_labels_to_equal(results, expected_labels)
    end

    context 'some labels are already assigned' do
      before do
        issue.labels << label1
      end

      it 'marks already assigned as set' do
        service = described_class.new(project, user)
        results = service.labels_as_hash(issue)
        expected_labels = [label1, label2, parent_group_label]

        expect_labels_to_equal(results, expected_labels)

        assigned_label_titles = issue.labels.map(&:title)
        results.each do |hash|
          if assigned_label_titles.include?(hash['title'])
            expect(hash[:set]).to eq(true)
          else
            expect(hash.key?(:set)).to eq(false)
          end
        end
      end
    end
  end
end


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

# Projects::BranchesByModeService uses Gitaly page-token pagination
# in order to optimally fetch branches.
# The drawback of the page-token pagination is that it doesn't provide
# an option of going to the previous page of the collection.
# That's why we need to fall back to offset pagination when previous page
# is requested.
class Projects::BranchesByModeService
  include Gitlab::Routing

  attr_reader :project, :params

  def initialize(project, params = {})
    @project = project
    @params = params
  end

  def execute
    return fetch_branches_via_gitaly_pagination if use_gitaly_pagination?

    fetch_branches_via_offset_pagination
  end

  private

  def mode
    params[:mode]
  end

  def by_mode(branches)
    return branches unless %w[active stale].include?(mode)

    branches.select { |b| b.state.to_s == mode }
  end

  def use_gitaly_pagination?
    return false if params[:page].present? || params[:search].present?

    Feature.enabled?(:branch_list_keyset_pagination, project)
  end

  def fetch_branches_via_offset_pagination
    branches = BranchesFinder.new(project.repository, params).execute
    branches = Kaminari.paginate_array(by_mode(branches)).page(params[:page])

    branches_with_links(branches, last_page: branches.last_page?)
  end

  def fetch_branches_via_gitaly_pagination
    per_page = Kaminari.config.default_per_page
    options = params.merge(per_page: per_page + 1, page_token: params[:page_token])

    branches = BranchesFinder.new(project.repository, options).execute(gitaly_pagination: true)

    # Branch is stale if it hasn't been updated for 3 months
    # This logic is specified in Gitlab Rails and isn't specified in Gitaly
    # To display stale branches we fetch branches sorted as most-stale-at-the-top
    # If the result contains active branches we filter them out and define that no more stale branches left
    # Same logic applies to fetching active branches
    branches = by_mode(branches)
    last_page = branches.size <= per_page

    branches = branches.take(per_page) # rubocop:disable CodeReuse/ActiveRecord

    branches_with_links(branches, last_page: last_page)
  end

  def branches_with_links(branches, last_page:)
    # To fall back to offset pagination we need to track current page via offset param
    # And increase it whenever we go to the next page
    previous_offset = params[:offset].to_i

    previous_path = nil
    next_path = nil

    return [branches, previous_path, next_path] if branches.blank?

    unless last_page
      next_path = project_branches_filtered_path(project, state: mode, page_token: branches.last.name, sort: params[:sort], offset: previous_offset + 1)
    end

    if previous_offset > 0
      previous_path = project_branches_filtered_path(project, state: mode, sort: params[:sort], page: previous_offset, offset: previous_offset - 1)
    end

    [branches, previous_path, next_path]
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::BranchesByModeService, feature_category: :source_code_management do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project, :repository) }

  let(:finder) { described_class.new(project, params) }
  let(:params) { { mode: 'all' } }

  subject { finder.execute }

  describe '#execute' do
    context 'page is passed' do
      let(:page) { (TestEnv::BRANCH_SHA.length.to_f / Kaminari.config.default_per_page).ceil }
      let(:params) { { page: page, mode: 'all', offset: page - 1 } }

      it 'uses offset pagination' do
        expect(finder).to receive(:fetch_branches_via_offset_pagination).and_call_original

        branches, prev_page, next_page = subject
        remaining = TestEnv::BRANCH_SHA.length % Kaminari.config.default_per_page

        expect(branches.size).to eq(remaining > 0 ? remaining : 20)
        expect(next_page).to be_nil
        expect(prev_page).to eq("/#{project.full_path}/-/branches/all?offset=#{page - 2}&page=#{page - 1}")
      end

      context 'but the page does not contain any branches' do
        let(:params) { { page: 100, mode: 'all' } }

        it 'uses offset pagination' do
          expect(finder).to receive(:fetch_branches_via_offset_pagination).and_call_original

          branches, prev_page, next_page = subject

          expect(branches).to eq([])
          expect(next_page).to be_nil
          expect(prev_page).to be_nil
        end
      end
    end

    context 'search is passed' do
      let(:params) { { search: 'feature' } }

      it 'uses offset pagination' do
        expect(finder).to receive(:fetch_branches_via_offset_pagination).and_call_original

        branches, prev_page, next_page = subject

        expect(branches.map(&:name)).to match_array(%w[feature feature_conflict])
        expect(next_page).to be_nil
        expect(prev_page).to be_nil
      end
    end

    context 'branch_list_keyset_pagination is disabled' do
      it 'uses offset pagination' do
        stub_feature_flags(branch_list_keyset_pagination: false)

        expect(finder).to receive(:fetch_branches_via_offset_pagination).and_call_original

        branches, prev_page, next_page = subject
        expected_page_token = ERB::Util.url_encode(TestEnv::BRANCH_SHA.sort[19][0])

        expect(branches.size).to eq(20)
        expect(next_page).to eq("/#{project.full_path}/-/branches/all?offset=1&page_token=#{expected_page_token}")
        expect(prev_page).to be_nil
      end
    end

    context 'uses gitaly pagination' do
      before do
        expect(finder).to receive(:fetch_branches_via_gitaly_pagination).and_call_original
      end

      it 'returns branches for the first page' do
        branches, prev_page, next_page = subject
        expected_page_token = ERB::Util.url_encode(TestEnv::BRANCH_SHA.sort[19][0])

        expect(branches.size).to eq(20)
        expect(next_page).to eq("/#{project.full_path}/-/branches/all?offset=1&page_token=#{expected_page_token}")
        expect(prev_page).to be_nil
      end

      context 'when second page is requested' do
        let(:page_token) { 'conflict-resolvable' }
        let(:params) { { page_token: page_token, mode: 'all', sort: 'name_asc', offset: 1 } }

        it 'returns branches for the first page' do
          branches, prev_page, next_page = subject
          branch_index = TestEnv::BRANCH_SHA.sort.find_index { |a| a[0] == page_token }
          expected_page_token = ERB::Util.url_encode(TestEnv::BRANCH_SHA.sort[20 + branch_index][0])

          expect(branches.size).to eq(20)
          expect(next_page).to eq("/#{project.full_path}/-/branches/all?offset=2&page_token=#{expected_page_token}&sort=name_asc")
          expect(prev_page).to eq("/#{project.full_path}/-/branches/all?offset=0&page=1&sort=name_asc")
        end
      end

      context 'when last page is requested' do
        let(:page_token) { TestEnv::BRANCH_SHA.sort[-16][0] }
        let(:params) { { page_token: page_token, mode: 'all', sort: 'name_asc', offset: 4 } }

        it 'returns branches after the specified branch' do
          branches, prev_page, next_page = subject

          expect(branches.size).to eq(15)
          expect(next_page).to be_nil
          expect(prev_page).to eq("/#{project.full_path}/-/branches/all?offset=3&page=4&sort=name_asc")
        end
      end
    end

    context 'filter by mode' do
      let(:stale) { double(state: 'stale') }
      let(:active) { double(state: 'active') }

      before do
        allow_next_instance_of(BranchesFinder) do |instance|
          allow(instance).to receive(:execute).and_return([stale, active])
        end
      end

      context 'stale' do
        let(:params) { { mode: 'stale' } }

        it 'returns stale branches' do
          is_expected.to eq([[stale], nil, nil])
        end
      end

      context 'active' do
        let(:params) { { mode: 'active' } }

        it 'returns active branches' do
          is_expected.to eq([[active], nil, nil])
        end
      end
    end
  end
end


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

# This service is an adapter used to for the GitLab Import feature, and
# creating a project from a template.
# The latter will under the hood just import an archive supplied by GitLab.
module Projects
  class GitlabProjectsImportService
    include Gitlab::Utils::StrongMemoize
    include Gitlab::TemplateHelper

    attr_reader :current_user, :params

    def initialize(user, import_params, override_params = nil)
      @current_user = user
      @params = import_params.dup
      @override_params = override_params
    end

    def execute
      prepare_template_environment(template_file)

      prepare_import_params

      ::Projects::CreateService.new(current_user, params).execute
    end

    private

    def overwrite_project?
      overwrite? && project_with_same_full_path?
    end

    def project_with_same_full_path?
      Project.find_by_full_path(project_path).present?
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def current_namespace
      strong_memoize(:current_namespace) do
        Namespace.find_by(id: params[:namespace_id]) || current_user.namespace
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def project_path
      "#{current_namespace.full_path}/#{params[:path]}"
    end

    def overwrite?
      strong_memoize(:overwrite) do
        params.delete(:overwrite)
      end
    end

    def template_file
      strong_memoize(:template_file) do
        params.delete(:file)
      end
    end

    def prepare_import_params
      data = {}
      data[:override_params] = @override_params if @override_params

      if overwrite_project?
        data[:original_path] = params[:path]
        params[:path] += "-#{tmp_filename}"
      end

      if template_file
        data[:sample_data] = params.delete(:sample_data) if params.key?(:sample_data)
        params[:import_type] = 'gitlab_project'
      end

      params[:import_data] = { data: data } if data.present?
    end
  end
end

Projects::GitlabProjectsImportService.prepend_mod_with('Projects::GitlabProjectsImportService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::GitlabProjectsImportService, feature_category: :importers do
  let_it_be(:namespace) { create(:namespace) }

  let(:path) { 'test-path' }
  let(:file) { fixture_file_upload('spec/fixtures/project_export.tar.gz') }
  let(:overwrite) { false }
  let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }

  subject { described_class.new(namespace.owner, import_params) }

  describe '#execute' do
    it_behaves_like 'gitlab projects import validations'
  end
end


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

module Projects
  # Service class for counting and caching the number of all merge requests of
  # a project.
  class AllMergeRequestsCountService < Projects::CountService
    def relation_for_count
      @project.merge_requests
    end

    def cache_key_name
      'all_merge_requests_count'
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::AllMergeRequestsCountService, :use_clean_rails_memory_store_caching, feature_category: :groups_and_projects do
  let_it_be(:project) { create(:project) }

  subject { described_class.new(project) }

  it_behaves_like 'a counter caching service'

  describe '#count' do
    it 'returns the number of all merge requests' do
      create(:merge_request, :opened, source_project: project, target_project: project)
      create(:merge_request, :closed, source_project: project, target_project: project)
      create(:merge_request, :merged, source_project: project, target_project: project)

      expect(subject.count).to eq(3)
    end
  end
end


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

module Projects
  # Service class for counting and caching the number of all issues of a
  # project.
  class AllIssuesCountService < Projects::CountService
    def relation_for_count
      @project.issues
    end

    def cache_key_name
      'all_issues_count'
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::AllIssuesCountService, :use_clean_rails_memory_store_caching, feature_category: :groups_and_projects do
  let_it_be(:group) { create(:group, :public) }
  let_it_be(:project) { create(:project, :public, namespace: group) }
  let_it_be(:banned_user) { create(:user, :banned) }

  subject { described_class.new(project) }

  it_behaves_like 'a counter caching service'

  describe '#count' do
    it 'returns the number of all issues' do
      create(:issue, :opened, project: project)
      create(:issue, :opened, confidential: true, project: project)
      create(:issue, :opened, author: banned_user, project: project)
      create(:issue, :closed, project: project)

      expect(subject.count).to eq(4)
    end
  end
end


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

module Projects
  class ParticipantsService < BaseService
    include Users::ParticipableService

    def execute(noteable)
      @noteable = noteable

      participants =
        noteable_owner +
        participants_in_noteable +
        all_members +
        project_members +
        groups

      render_participants_as_hash(participants.uniq)
    end

    def project_members
      @project_members ||= sorted(project.authorized_users)
    end

    def all_members
      return [] if Feature.enabled?(:disable_all_mention)

      [{ username: "all", name: "All Project and Group Members", count: project_members.count }]
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ParticipantsService, feature_category: :groups_and_projects do
  describe '#execute' do
    let_it_be(:user) { create(:user) }
    let_it_be(:project) { create(:project, :public) }
    let_it_be(:noteable) { create(:issue, project: project) }

    before_all do
      project.add_developer(user)

      stub_feature_flags(disable_all_mention: false)
    end

    def run_service
      described_class.new(project, user).execute(noteable)
    end

    it 'returns results in correct order' do
      group = create(:group).tap { |g| g.add_owner(user) }

      expect(run_service.pluck(:username)).to eq([
        noteable.author.username, 'all', user.username, group.full_path
      ])
    end

    it 'includes `All Project and Group Members`' do
      expect(run_service).to include(a_hash_including({ username: "all", name: "All Project and Group Members" }))
    end

    context 'N+1 checks' do
      before do
        run_service # warmup, runs table cache queries and create queries
        BatchLoader::Executor.clear_current
      end

      it 'avoids N+1 UserDetail queries' do
        project.add_developer(create(:user))

        control_count = ActiveRecord::QueryRecorder.new { run_service.to_a }.count

        BatchLoader::Executor.clear_current

        project.add_developer(create(:user, status: build(:user_status, availability: :busy)))

        expect { run_service.to_a }.not_to exceed_query_limit(control_count)
      end

      it 'avoids N+1 groups queries' do
        group_1 = create(:group)
        group_1.add_owner(user)

        control_count = ActiveRecord::QueryRecorder.new { run_service }.count

        BatchLoader::Executor.clear_current

        group_2 = create(:group)
        group_2.add_owner(user)

        expect { run_service }.not_to exceed_query_limit(control_count)
      end
    end

    it 'does not return duplicate author' do
      participants = run_service

      expect(participants.count { |p| p[:username] == noteable.author.username }).to eq 1
    end

    describe 'group items' do
      subject(:group_items) { run_service.select { |hash| hash[:type].eql?('Group') } }

      describe 'group user counts' do
        let(:group_1) { create(:group) }
        let(:group_2) { create(:group) }

        before do
          group_1.add_owner(user)
          group_1.add_owner(create(:user))

          group_2.add_owner(user)
          create(:group_member, :access_request, group: group_2, user: create(:user))
        end

        it 'returns correct user counts for groups' do
          expect(group_items).to contain_exactly(
            a_hash_including(name: group_1.full_name, count: 2),
            a_hash_including(name: group_2.full_name, count: 1)
          )
        end
      end

      describe 'avatar_url' do
        let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }

        before do
          group.add_owner(user)
        end

        it 'returns an url for the avatar' do
          expect(group_items.size).to eq 1
          expect(group_items.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
        end

        it 'returns an url for the avatar with relative url' do
          stub_config_setting(relative_url_root: '/gitlab')
          stub_config_setting(url: Settings.send(:build_gitlab_url))

          expect(group_items.size).to eq 1
          expect(group_items.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
        end
      end

      context 'with subgroups' do
        let(:group_1) { create(:group, path: 'bb') }
        let(:group_2) { create(:group, path: 'zz') }
        let(:subgroup) { create(:group, path: 'aa', parent: group_1) }

        before do
          group_1.add_owner(user)
          group_2.add_owner(user)
          subgroup.add_owner(user)
        end

        it 'returns results ordered by full path' do
          expect(group_items.pluck(:username)).to eq([
            group_1.full_path, subgroup.full_path, group_2.full_path
          ])
        end
      end
    end

    context 'when `disable_all_mention` FF is enabled' do
      before do
        stub_feature_flags(disable_all_mention: true)
      end

      it 'does not include `All Project and Group Members`' do
        expect(run_service).not_to include(a_hash_including({ username: "all", name: "All Project and Group Members" }))
      end
    end
  end

  describe '#project_members' do
    subject(:usernames) { service.project_members.map { |member| member[:username] } }

    context 'when there is a project in group namespace' do
      let_it_be(:public_group) { create(:group, :public) }
      let_it_be(:public_project, reload: true) { create(:project, :public, namespace: public_group) }

      let_it_be(:public_group_owner) { create(:user) }

      let(:service) { described_class.new(public_project, create(:user)) }

      before do
        public_group.add_owner(public_group_owner)
      end

      it 'returns members of a group' do
        expect(usernames).to include(public_group_owner.username)
      end
    end

    context 'when there is a private group and a public project' do
      let_it_be(:public_group) { create(:group, :public) }
      let_it_be(:private_group) { create(:group, :private, :nested) }
      let_it_be(:public_project, reload: true) { create(:project, :public, namespace: public_group) }

      let_it_be(:project_issue) { create(:issue, project: public_project) }

      let_it_be(:public_group_owner) { create(:user) }
      let_it_be(:private_group_member) { create(:user) }
      let_it_be(:public_project_maintainer) { create(:user) }
      let_it_be(:private_group_owner) { create(:user) }

      let_it_be(:group_ancestor_owner) { create(:user) }

      before_all do
        public_group.add_owner public_group_owner
        private_group.add_developer private_group_member
        public_project.add_maintainer public_project_maintainer

        private_group.add_owner private_group_owner
        private_group.parent.add_owner group_ancestor_owner
      end

      context 'when the private group is invited to the public project' do
        before_all do
          create(:project_group_link, group: private_group, project: public_project)
        end

        let(:service) { described_class.new(public_project, create(:user)) }

        it 'does not return the private group' do
          expect(usernames).not_to include(private_group.name)
        end

        it 'returns private group members' do
          expect(usernames).to include(private_group_member.username)
        end

        it 'returns the project maintainer' do
          expect(usernames).to include(public_project_maintainer.username)
        end

        it 'returns project members from an invited public group' do
          invited_public_group = create(:group, :public)
          invited_public_group.add_owner create(:user)

          create(:project_group_link, group: invited_public_group, project: public_project)

          expect(usernames).to include(invited_public_group.users.first.username)
        end

        it 'returns members of the ancestral groups of the private group' do
          expect(usernames).to include(group_ancestor_owner.username)
        end

        it 'returns invited group members of the private group' do
          invited_group = create(:group, :public)
          create(:group_group_link, shared_group: private_group, shared_with_group: invited_group)

          other_user = create(:user)
          invited_group.add_guest(other_user)

          expect(usernames).to include(other_user.username)
        end
      end
    end
  end
end


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

module Projects
  class MoveNotificationSettingsService < BaseMoveRelationsService
    def execute(source_project, remove_remaining_elements: true)
      return unless super

      Project.transaction do
        move_notification_settings
        remove_remaining_notification_settings if remove_remaining_elements

        success
      end
    end

    private

    def move_notification_settings
      non_existent_notifications.update_all(source_id: @project.id)
    end

    # Remove remaining notification settings from source_project
    def remove_remaining_notification_settings
      source_project.notification_settings.destroy_all # rubocop: disable Cop/DestroyAll
    end

    # Get users of current notification_settings
    def users_in_target_project
      @project.notification_settings.select(:user_id)
    end

    # Look for notification_settings in source_project that are not in the target project
    # rubocop: disable CodeReuse/ActiveRecord
    def non_existent_notifications
      source_project.notification_settings
        .select(:id)
        .where.not(user_id: users_in_target_project)
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::MoveNotificationSettingsService, feature_category: :groups_and_projects do
  let(:user) { create(:user) }
  let(:project_with_notifications) { create(:project, namespace: user.namespace) }
  let(:target_project) { create(:project, namespace: user.namespace) }

  subject { described_class.new(target_project, user) }

  describe '#execute' do
    context 'with notification settings' do
      before do
        create_list(:notification_setting, 2, source: project_with_notifications)
      end

      it 'moves the user\'s notification settings from one project to another' do
        expect(project_with_notifications.notification_settings.count).to eq 3
        expect(target_project.notification_settings.count).to eq 1

        subject.execute(project_with_notifications)

        expect(project_with_notifications.notification_settings.count).to eq 0
        expect(target_project.notification_settings.count).to eq 3
      end

      it 'rollbacks changes if transaction fails' do
        allow(subject).to receive(:success).and_raise(StandardError)

        expect { subject.execute(project_with_notifications) }.to raise_error(StandardError)

        expect(project_with_notifications.notification_settings.count).to eq 3
        expect(target_project.notification_settings.count).to eq 1
      end
    end

    it 'does not move existent notification settings in the current project' do
      expect(project_with_notifications.notification_settings.count).to eq 1
      expect(target_project.notification_settings.count).to eq 1
      expect(user.notification_settings.count).to eq 2

      subject.execute(project_with_notifications)

      expect(user.notification_settings.count).to eq 1
    end

    context 'when remove_remaining_elements is false' do
      let(:options) { { remove_remaining_elements: false } }

      it 'does not remove remaining notification settings' do
        subject.execute(project_with_notifications, **options)

        expect(project_with_notifications.notification_settings.count).not_to eq 0
      end
    end
  end
end