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

module Environments
  class StopService < BaseService
    attr_reader :ref

    def execute(environment)
      unless can?(current_user, :stop_environment, environment)
        return ServiceResponse.error(
          message: 'Unauthorized to stop the environment',
          payload: { environment: environment }
        )
      end

      if params[:force]
        environment.stop_complete!
      else
        environment.stop_with_actions!(current_user)
      end

      unless environment.saved_change_to_attribute?(:state)
        return ServiceResponse.error(
          message: 'Attemped to stop the environment but failed to change the status',
          payload: { environment: environment }
        )
      end

      ServiceResponse.success(payload: { environment: environment })
    end

    def execute_for_branch(branch_name)
      @ref = branch_name

      return unless @ref.present?

      environments.each { |environment| execute(environment) }
    end

    def execute_for_merge_request_pipeline(merge_request)
      return unless merge_request.actual_head_pipeline&.merge_request?

      created_environments = merge_request.created_environments

      if created_environments.any?
        # This log message can be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/372965
        Gitlab::AppJsonLogger.info(message: 'Running new dynamic environment stop logic', project_id: project.id)
        created_environments.each { |env| execute(env) }
      else
        environments_in_head_pipeline = merge_request.environments_in_head_pipeline(deployment_status: :success)
        environments_in_head_pipeline.each { |env| execute(env) }

        if environments_in_head_pipeline.any?
          # If we don't see a message often, we'd be able to remove this path. (or likely in GitLab 16.0)
          # See https://gitlab.com/gitlab-org/gitlab/-/issues/372965
          Gitlab::AppJsonLogger.info(message: 'Running legacy dynamic environment stop logic', project_id: project.id)
        end
      end
    end

    private

    def environments
      @environments ||= Environments::EnvironmentsByDeploymentsFinder
        .new(project, current_user, ref: @ref, recently_updated: true)
        .execute
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::StopService, feature_category: :continuous_delivery do
  include CreateEnvironmentsHelpers

  let(:project) { create(:project, :private, :repository) }
  let(:user) { create(:user) }

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

  describe '#execute' do
    subject { service.execute(environment) }

    let_it_be(:project) { create(:project, :private, :repository) }
    let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
    let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }

    let(:user) { developer }

    context 'with a deployment' do
      let!(:environment) { review_job.persisted_environment }
      let!(:pipeline) { create(:ci_pipeline, project: project) }
      let!(:review_job) { create(:ci_build, :with_deployment, :start_review_app, pipeline: pipeline, project: project) }
      let!(:stop_review_job) { create(:ci_build, :with_deployment, :stop_review_app, :manual, pipeline: pipeline, project: project) }

      before do
        review_job.success!
      end

      context 'without stop action' do
        let!(:environment) { create(:environment, :available, project: project) }

        it 'stops the environment' do
          expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
        end
      end

      it 'plays the stop action' do
        expect { subject }.to change { stop_review_job.reload.status }.from('manual').to('pending')
      end

      context 'force option' do
        let(:service) { described_class.new(project, user, { force: true }) }

        it 'does not play the stop action when forced' do
          expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
          expect(stop_review_job.reload.status).to eq('manual')
        end
      end

      context 'when an environment has already been stopped' do
        let!(:environment) { create(:environment, :stopped, project: project) }

        it 'does not play the stop action' do
          expect { subject }.not_to change { stop_review_job.reload.status }
        end
      end
    end

    context 'without a deployment' do
      let!(:environment) { create(:environment, project: project) }

      it 'stops the environment' do
        expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
      end

      context 'when the actor is a reporter' do
        let(:user) { reporter }

        it 'does not stop the environment' do
          expect { subject }.not_to change { environment.reload.state }
        end
      end
    end
  end

  describe '#execute_for_branch' do
    context 'when environment with review app exists' do
      context 'when user has permission to stop environment' do
        before do
          project.add_developer(user)
        end

        context 'when environment is associated with removed branch' do
          it 'stops environment' do
            expect_environment_stopping_on('feature', feature_environment)
          end
        end

        context 'when environment is associated with different branch' do
          it 'does not stop environment' do
            expect_environment_not_stopped_on('master', feature_environment)
          end
        end

        context 'when specified branch does not exist' do
          it 'does not stop environment' do
            expect_environment_not_stopped_on('non/existent/branch', feature_environment)
          end
        end

        context 'when no branch not specified' do
          it 'does not stop environment' do
            expect_environment_not_stopped_on(nil, feature_environment)
          end
        end

        context 'when environment is not stopped' do
          before do
            allow_next_found_instance_of(Environment) do |environment|
              allow(environment).to receive(:state).and_return(:stopped)
            end
          end

          it 'does not stop environment' do
            expect_environment_not_stopped_on('feature', feature_environment)
          end
        end
      end

      context 'when user does not have permission to stop environment' do
        context 'when user has no access to manage deployments' do
          before do
            project.add_guest(user)
          end

          it 'does not stop environment' do
            expect_environment_not_stopped_on('master', feature_environment)
          end
        end
      end

      context 'when branch for stop action is protected' do
        before do
          project.add_developer(user)
          create(:protected_branch, :no_one_can_push, name: 'master', project: project)
        end

        it 'does not stop environment' do
          expect_environment_not_stopped_on('master', feature_environment)
        end
      end
    end

    context 'when there is no environment associated with review app' do
      before do
        create(:environment, project: project)
      end

      context 'when user has permission to stop environments' do
        before do
          project.add_maintainer(user)
        end

        it 'does not stop environment' do
          expect_environment_not_stopped_on('master', feature_environment)
        end
      end
    end

    context 'when environment does not exist' do
      it 'does not raise error' do
        expect { service.execute_for_branch('master') }
          .not_to raise_error
      end
    end
  end

  describe '#execute_for_merge_request_pipeline' do
    subject { service.execute_for_merge_request_pipeline(merge_request) }

    let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
    let(:project) { merge_request.project }
    let(:user) { create(:user) }

    let(:pipeline) do
      create(:ci_pipeline,
        source: :merge_request_event,
        merge_request: merge_request,
        project: project,
        sha: merge_request.diff_head_sha,
        merge_requests_as_head_pipeline: [merge_request])
    end

    let!(:review_job) { create(:ci_build, :with_deployment, :start_review_app, :success, pipeline: pipeline, project: project) }
    let!(:stop_review_job) { create(:ci_build, :with_deployment, :stop_review_app, :manual, pipeline: pipeline, project: project) }

    before do
      review_job.deployment.success!
    end

    it 'has active environment at first' do
      expect(pipeline.environments_in_self_and_project_descendants.first).to be_available
    end

    context 'when user is a developer' do
      before do
        project.add_developer(user)
      end

      context 'and merge request has associated created_environments' do
        let!(:environment1) { create(:environment, project: project, merge_request: merge_request) }
        let!(:environment2) { create(:environment, project: project, merge_request: merge_request) }
        let!(:environment3) { create(:environment, project: project) }
        let!(:environment3_deployment) { create(:deployment, environment: environment3, sha: pipeline.sha) }

        before do
          subject
        end

        it 'stops the associated created_environments' do
          expect(environment1.reload).to be_stopped
          expect(environment2.reload).to be_stopped
        end

        it 'does not affect environments that are not associated to the merge request' do
          expect(environment3.reload).to be_available
        end
      end

      it 'stops the active environment' do
        subject
        expect(pipeline.environments_in_self_and_project_descendants.first).to be_stopping
      end

      context 'when pipeline is a branch pipeline for merge request' do
        let(:pipeline) do
          create(:ci_pipeline,
            source: :push,
            project: project,
            sha: merge_request.diff_head_sha,
            merge_requests_as_head_pipeline: [merge_request])
        end

        it 'does not stop the active environment' do
          subject

          expect(pipeline.environments_in_self_and_project_descendants.first).to be_available
        end
      end

      context 'with environment related jobs ' do
        let!(:environment) { create(:environment, :available, name: 'staging', project: project) }
        let!(:prepare_staging_job) { create(:ci_build, :prepare_staging, pipeline: pipeline, project: project) }
        let!(:start_staging_job) { create(:ci_build, :start_staging, :with_deployment, :manual, pipeline: pipeline, project: project) }
        let!(:stop_staging_job) { create(:ci_build, :stop_staging, :manual, pipeline: pipeline, project: project) }

        it 'does not stop environments that was not started by the merge request' do
          subject

          expect(prepare_staging_job.persisted_environment.state).to eq('available')
        end
      end
    end

    context 'when user is a reporter' do
      before do
        project.add_reporter(user)
      end

      it 'does not stop the active environment' do
        subject

        expect(pipeline.environments_in_self_and_project_descendants.first).to be_available
      end
    end

    context 'when pipeline is not associated with environments' do
      let!(:job) { create(:ci_build, pipeline: pipeline, project: project) }

      it 'does not raise exception' do
        expect { subject }.not_to raise_exception
      end
    end

    context 'when pipeline is not a pipeline for merge request' do
      let(:pipeline) do
        create(:ci_pipeline,
          project: project,
          ref: 'feature',
          sha: merge_request.diff_head_sha,
          merge_requests_as_head_pipeline: [merge_request])
      end

      it 'does not stop the active environment' do
        subject

        expect(pipeline.environments_in_self_and_project_descendants.first).to be_available
      end
    end
  end

  def expect_environment_stopped_on(branch, environment)
    expect { service.execute_for_branch(branch) }
      .to change { environment.reload.state }.from('available').to('stopped')
  end

  def expect_environment_stopping_on(branch, environment)
    expect { service.execute_for_branch(branch) }
      .to change { environment.reload.state }.from('available').to('stopping')
  end

  def expect_environment_not_stopped_on(branch, environment)
    expect { service.execute_for_branch(branch) }
      .not_to change { environment.reload.state }.from('available')
  end

  def feature_environment
    create(:environment, :with_review_app, project: project, ref: 'feature')
  end
end


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

module Environments
  class AutoRecoverService
    include ::Gitlab::ExclusiveLeaseHelpers
    include ::Gitlab::LoopHelpers

    BATCH_SIZE = 100
    LOOP_TIMEOUT = 45.minutes
    LOOP_LIMIT = 1000
    EXCLUSIVE_LOCK_KEY = 'environments:auto_recover:lock'
    LOCK_TIMEOUT = 50.minutes

    ##
    # Recover environments that are stuck stopping on a GitLab instance
    #
    # This auto stop process cannot run for more than 45 minutes. This is for
    # preventing multiple `AutoStopCronWorker` CRON jobs run concurrently,
    # which is scheduled at every hour.
    def execute
      in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
        loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
          recover_in_batch
        end
      end
    end

    private

    def recover_in_batch
      environments = Environment.preload_project.select(:id, :project_id).long_stopping.limit(BATCH_SIZE)

      return false if environments.empty?

      Environments::AutoRecoverWorker.bulk_perform_async_with_contexts(
        environments,
        arguments_proc: ->(environment) { environment.id },
        context_proc: ->(environment) { { project: environment.project } }
      )

      true
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::AutoRecoverService, :clean_gitlab_redis_shared_state, :sidekiq_inline,
  feature_category: :continuous_delivery do
  include CreateEnvironmentsHelpers
  include ExclusiveLeaseHelpers

  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:user) { create(:user) }

  let(:service) { described_class.new }

  before_all do
    project.add_developer(user)
  end

  describe '#execute' do
    subject { service.execute }

    let_it_be(:project) { create(:project, :repository) }
    let_it_be(:user) { create(:user) }

    let(:environments) { Environment.all }

    before_all do
      project.add_developer(user)
      project.repository.add_branch(user, 'review/feature-1', 'master')
      project.repository.add_branch(user, 'review/feature-2', 'master')
    end

    before do
      create_review_app(user, project, 'review/feature-1')
      create_review_app(user, project, 'review/feature-2')

      Environment.all.map do |e|
        e.stop_actions.map(&:drop)
        e.stop!
        e.update!(updated_at: (Environment::LONG_STOP + 1.day).ago)
        e.reload
      end
    end

    it 'stops environments that have been stuck stopping too long' do
      expect { subject }
        .to change { Environment.all.map(&:state).uniq }
        .from(['stopping']).to(['available'])
    end

    it 'schedules stop processes in bulk' do
      args = [[Environment.find_by_name('review/feature-1').id], [Environment.find_by_name('review/feature-2').id]]

      expect(Environments::AutoRecoverWorker)
        .to receive(:bulk_perform_async).with(args).once.and_call_original

      subject
    end

    context 'when the other sidekiq worker has already been running' do
      before do
        stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY)
      end

      it 'does not execute recover_in_batch' do
        expect_next_instance_of(described_class) do |service|
          expect(service).not_to receive(:recover_in_batch)
        end

        expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
      end
    end

    context 'when loop reached timeout' do
      before do
        stub_const("#{described_class}::LOOP_TIMEOUT", 0.seconds)
        stub_const("#{described_class}::LOOP_LIMIT", 100_000)
        allow_next_instance_of(described_class) do |service|
          allow(service).to receive(:recover_in_batch).and_return(true)
        end
      end

      it 'returns false and does not continue the process' do
        is_expected.to eq(false)
      end
    end

    context 'when loop reached loop limit' do
      before do
        stub_const("#{described_class}::LOOP_LIMIT", 1)
        stub_const("#{described_class}::BATCH_SIZE", 1)
      end

      it 'stops only one available environment' do
        expect { subject }.to change { Environment.long_stopping.count }.by(-1)
      end
    end
  end
end


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

module Environments
  class DestroyService < BaseService
    def execute(environment)
      unless can?(current_user, :destroy_environment, environment)
        return ServiceResponse.error(
          message: 'Unauthorized to delete the environment'
        )
      end

      environment.destroy

      unless environment.destroyed?
        return ServiceResponse.error(
          message: 'Attemped to destroy the environment but failed'
        )
      end

      ServiceResponse.success
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::DestroyService, feature_category: :continuous_delivery do
  include CreateEnvironmentsHelpers

  let_it_be(:project) { create(:project, :private, :repository) }
  let_it_be(:user) { create(:user) }

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

  describe '#execute' do
    subject { service.execute(environment) }

    let_it_be(:project) { create(:project, :private, :repository) }
    let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
    let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }

    let(:user) { developer }

    let!(:environment) { create(:environment, project: project, state: :stopped) }

    context "when destroy is authorized" do
      it 'destroys the environment' do
        expect { subject }.to change { environment.destroyed? }.from(false).to(true)
      end
    end

    context "when destroy is not authorized" do
      let(:user) { reporter }

      it 'does not destroy the environment' do
        expect { subject }.not_to change { environment.destroyed? }
      end
    end

    context "when destroy fails" do
      before do
        allow(environment)
          .to receive(:destroy)
          .and_return(false)
      end

      it 'returns errors' do
        expect(subject.message).to include("Attemped to destroy the environment but failed")
      end
    end
  end
end


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

module Environments
  class UpdateService < BaseService
    ALLOWED_ATTRIBUTES = %i[external_url tier cluster_agent kubernetes_namespace flux_resource_path].freeze

    def execute(environment)
      unless can?(current_user, :update_environment, environment)
        return ServiceResponse.error(
          message: _('Unauthorized to update the environment'),
          payload: { environment: environment }
        )
      end

      if unauthorized_cluster_agent?
        return ServiceResponse.error(
          message: _('Unauthorized to access the cluster agent in this project'),
          payload: { environment: environment })
      end

      if environment.update(**params.slice(*ALLOWED_ATTRIBUTES))
        ServiceResponse.success(payload: { environment: environment })
      else
        ServiceResponse.error(
          message: environment.errors.full_messages,
          payload: { environment: environment }
        )
      end
    end

    private

    def unauthorized_cluster_agent?
      return false unless params[:cluster_agent]

      ::Clusters::Agents::Authorizations::UserAccess::Finder
        .new(current_user, agent: params[:cluster_agent], project: project)
        .execute
        .empty?
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::UpdateService, feature_category: :environment_management do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
  let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
  let_it_be(:environment) { create(:environment, project: project) }

  let(:service) { described_class.new(project, current_user, params) }
  let(:current_user) { developer }
  let(:params) { {} }

  describe '#execute' do
    subject { service.execute(environment) }

    let(:params) { { external_url: 'https://gitlab.com/' } }

    it 'updates the external URL' do
      expect { subject }.to change { environment.reload.external_url }.to('https://gitlab.com/')
    end

    it 'returns successful response' do
      response = subject

      expect(response).to be_success
      expect(response.payload[:environment]).to eq(environment)
    end

    context 'when setting a kubernetes namespace to the environment' do
      let(:params) { { kubernetes_namespace: 'default' } }

      it 'updates the kubernetes namespace' do
        expect { subject }.to change { environment.reload.kubernetes_namespace }.to('default')
      end

      it 'returns successful response' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment]).to eq(environment)
      end
    end

    context 'when setting a flux resource path to the environment' do
      let(:params) { { flux_resource_path: 'path/to/flux/resource' } }

      it 'updates the flux resource path' do
        expect { subject }.to change { environment.reload.flux_resource_path }.to('path/to/flux/resource')
      end

      it 'returns successful response' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment]).to eq(environment)
      end
    end

    context 'when setting a cluster agent to the environment' do
      let_it_be(:agent_management_project) { create(:project) }
      let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) }

      let!(:authorization) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) }
      let(:params) { { cluster_agent: cluster_agent } }

      it 'returns successful response' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment].cluster_agent).to eq(cluster_agent)
      end

      context 'when user does not have permission to read the agent' do
        let!(:authorization) { nil }

        it 'returns an error' do
          response = subject

          expect(response).to be_error
          expect(response.message).to eq('Unauthorized to access the cluster agent in this project')
          expect(response.payload[:environment]).to eq(environment)
        end
      end
    end

    context 'when unsetting a cluster agent of the environment' do
      let_it_be(:cluster_agent) { create(:cluster_agent, project: project) }

      let(:params) { { cluster_agent: nil } }

      before do
        environment.update!(cluster_agent: cluster_agent)
      end

      it 'returns successful response' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment].cluster_agent).to be_nil
      end
    end

    context 'when params contain invalid value' do
      let(:params) { { external_url: 'http://${URL}' } }

      it 'returns an error' do
        response = subject

        expect(response).to be_error
        expect(response.message).to match_array("External url URI is invalid")
        expect(response.payload[:environment]).to eq(environment)
      end
    end

    context 'when disallowed parameter is passed' do
      let(:params) { { external_url: 'https://gitlab.com/', slug: 'prod' } }

      it 'ignores the parameter' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment].external_url).to eq('https://gitlab.com/')
        expect(response.payload[:environment].slug).not_to eq('prod')
      end
    end

    context 'when user is reporter' do
      let(:current_user) { reporter }

      it 'returns an error' do
        response = subject

        expect(response).to be_error
        expect(response.message).to eq('Unauthorized to update the environment')
        expect(response.payload[:environment]).to eq(environment)
      end
    end
  end
end


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

module Environments
  class CreateService < BaseService
    ALLOWED_ATTRIBUTES = %i[name external_url tier cluster_agent kubernetes_namespace flux_resource_path].freeze

    def execute
      unless can?(current_user, :create_environment, project)
        return ServiceResponse.error(
          message: _('Unauthorized to create an environment'),
          payload: { environment: nil }
        )
      end

      if unauthorized_cluster_agent?
        return ServiceResponse.error(
          message: _('Unauthorized to access the cluster agent in this project'),
          payload: { environment: nil })
      end

      environment = project.environments.create(**params.slice(*ALLOWED_ATTRIBUTES))

      if environment.persisted?
        ServiceResponse.success(payload: { environment: environment })
      else
        ServiceResponse.error(
          message: environment.errors.full_messages,
          payload: { environment: nil }
        )
      end
    end

    private

    def unauthorized_cluster_agent?
      return false unless params[:cluster_agent]

      ::Clusters::Agents::Authorizations::UserAccess::Finder
        .new(current_user, agent: params[:cluster_agent], project: project)
        .execute
        .empty?
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::CreateService, feature_category: :environment_management do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
  let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }

  let(:service) { described_class.new(project, current_user, params) }
  let(:current_user) { developer }
  let(:params) { {} }

  describe '#execute' do
    subject { service.execute }

    let(:params) { { name: 'production', external_url: 'https://gitlab.com', tier: :production, kubernetes_namespace: 'default', flux_resource_path: 'path/to/flux/resource' } }

    it 'creates an environment' do
      expect { subject }.to change { ::Environment.count }.by(1)
    end

    it 'returns successful response' do
      response = subject

      expect(response).to be_success
      expect(response.payload[:environment].name).to eq('production')
      expect(response.payload[:environment].external_url).to eq('https://gitlab.com')
      expect(response.payload[:environment].tier).to eq('production')
      expect(response.payload[:environment].kubernetes_namespace).to eq('default')
      expect(response.payload[:environment].flux_resource_path).to eq('path/to/flux/resource')
    end

    context 'with a cluster agent' do
      let_it_be(:agent_management_project) { create(:project) }
      let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) }

      let!(:authorization) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) }
      let(:params) { { name: 'production', cluster_agent: cluster_agent } }

      it 'returns successful response' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment].cluster_agent).to eq(cluster_agent)
      end

      context 'when user does not have permission to read the agent' do
        let!(:authorization) { nil }

        it 'returns an error' do
          response = subject

          expect(response).to be_error
          expect(response.message).to eq('Unauthorized to access the cluster agent in this project')
          expect(response.payload[:environment]).to be_nil
        end
      end
    end

    context 'when params contain invalid value' do
      let(:params) { { name: 'production', external_url: 'http://${URL}' } }

      it 'does not create an environment' do
        expect { subject }.not_to change { ::Environment.count }
      end

      it 'returns an error' do
        response = subject

        expect(response).to be_error
        expect(response.message).to match_array("External url URI is invalid")
        expect(response.payload[:environment]).to be_nil
      end
    end

    context 'when disallowed parameter is passed' do
      let(:params) { { name: 'production', slug: 'prod' } }

      it 'ignores the parameter' do
        response = subject

        expect(response).to be_success
        expect(response.payload[:environment].name).to eq('production')
        expect(response.payload[:environment].slug).not_to eq('prod')
      end
    end

    context 'when user is reporter' do
      let(:current_user) { reporter }

      it 'does not create an environment' do
        expect { subject }.not_to change { ::Environment.count }
      end

      it 'returns an error' do
        response = subject

        expect(response).to be_error
        expect(response.message).to eq('Unauthorized to create an environment')
        expect(response.payload[:environment]).to be_nil
      end
    end
  end
end


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

module Environments
  class StopStaleService < BaseService
    def execute
      return ServiceResponse.error(message: 'Before date must be provided') unless params[:before].present?

      return ServiceResponse.error(message: 'Unauthorized') unless can?(current_user, :stop_environment, project)

      Environment.available
      .deployed_and_updated_before(project.id, params[:before])
      .without_protected(project)
      .in_batches(of: 100) do |env_batch| # rubocop:disable Cop/InBatches
        Environments::AutoStopWorker.bulk_perform_async_with_contexts(
          env_batch,
          arguments_proc: ->(environment) { environment.id },
          context_proc: ->(environment) { { project: project } }
        )
      end

      ServiceResponse.success(message: 'Successfully requested stop for all stale environments')
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::StopStaleService,
  :clean_gitlab_redis_shared_state,
  :sidekiq_inline,
  feature_category: :continuous_delivery do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:user) { create(:user) }

  let(:params) { { after: nil } }
  let(:service) { described_class.new(project, user, params) }

  describe '#execute' do
    subject { service.execute }

    let_it_be(:project) { create(:project, :repository) }
    let_it_be(:user) { create(:user) }
    let_it_be(:stale_environment) { create(:environment, project: project, updated_at: 2.weeks.ago) }
    let_it_be(:stale_environment2) { create(:environment, project: project, updated_at: 2.weeks.ago) }
    let_it_be(:recent_environment) { create(:environment, project: project, updated_at: Date.today) }

    let_it_be(:params) { { before: 1.week.ago } }

    before do
      allow(service).to receive(:can?).with(user, :stop_environment, project).and_return(true)
    end

    it 'only stops stale environments' do
      spy_service = Environments::AutoStopWorker.new

      allow(Environments::AutoStopWorker).to receive(:new) { spy_service }

      expect(spy_service).to receive(:perform).with(stale_environment.id).and_call_original
      expect(spy_service).to receive(:perform).with(stale_environment2.id).and_call_original
      expect(spy_service).not_to receive(:perform).with(recent_environment.id)

      expect(Environment).to receive(:deployed_and_updated_before).with(project.id, params[:before]).and_call_original
      expect(Environment).to receive(:without_protected).with(project).and_call_original

      expect(subject.success?).to be_truthy

      expect(stale_environment.reload).to be_stopped
      expect(stale_environment2.reload).to be_stopped
      expect(recent_environment.reload).to be_available
    end
  end
end


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

module Environments
  module CanaryIngress
    class UpdateService < ::BaseService
      def execute_async(environment)
        result = validate(environment)

        return result unless result[:status] == :success

        Environments::CanaryIngress::UpdateWorker.perform_async(environment.id, params)

        success
      end

      # This method actually executes the PATCH request to Kubernetes,
      # that is used by internal processes i.e. sidekiq worker.
      # You should always use `execute_async` to properly validate user's requests.
      def execute(environment)
        canary_ingress = environment.ingresses&.find(&:canary?)

        unless canary_ingress.present?
          return error(_('Canary Ingress does not exist in the environment.'))
        end

        if environment.patch_ingress(canary_ingress, patch_data)
          environment.clear_all_caches
          success
        else
          error(_('Failed to update the Canary Ingress.'), :bad_request)
        end
      end

      private

      def validate(environment)
        unless can?(current_user, :update_environment, environment)
          return error(_('You do not have permission to update the environment.'))
        end

        unless params[:weight].is_a?(Integer) && (0..100).cover?(params[:weight])
          return error(_('Canary weight must be specified and valid range (0..100).'))
        end

        if environment.has_running_deployments?
          return error(_('There are running deployments on the environment. Please retry later.'))
        end

        if ::Gitlab::ApplicationRateLimiter.throttled?(:update_environment_canary_ingress, scope: [environment])
          return error(_("This environment's canary ingress has been updated recently. Please retry later."))
        end

        success
      end

      def patch_data
        {
          metadata: {
            annotations: {
              Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
            }
          }
        }
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_cache,
  feature_category: :continuous_delivery do
  include KubernetesHelpers

  let_it_be(:project, refind: true) { create(:project) }
  let_it_be(:maintainer) { create(:user) }
  let_it_be(:reporter) { create(:user) }

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

  before_all do
    project.add_maintainer(maintainer)
    project.add_reporter(reporter)
  end

  shared_examples_for 'failed request' do
    it 'returns an error' do
      expect(subject[:status]).to eq(:error)
      expect(subject[:message]).to eq(message)
    end
  end

  describe '#execute_async' do
    subject { service.execute_async(environment) }

    let(:environment) { create(:environment, project: project) }
    let(:params) { { weight: 50 } }
    let(:canary_ingress) { ::Gitlab::Kubernetes::Ingress.new(kube_ingress(track: :canary)) }

    context 'when the actor does not have permission to update environment' do
      let(:user) { reporter }

      it_behaves_like 'failed request' do
        let(:message) { "You do not have permission to update the environment." }
      end
    end

    context 'when weight parameter is invalid' do
      let(:params) { { weight: 'unknown' } }

      it_behaves_like 'failed request' do
        let(:message) { 'Canary weight must be specified and valid range (0..100).' }
      end
    end

    context 'when no parameters exist' do
      let(:params) { {} }

      it_behaves_like 'failed request' do
        let(:message) { 'Canary weight must be specified and valid range (0..100).' }
      end
    end

    context 'when environment has a running deployment' do
      before do
        allow(environment).to receive(:has_running_deployments?) { true }
      end

      it_behaves_like 'failed request' do
        let(:message) { 'There are running deployments on the environment. Please retry later.' }
      end
    end

    context 'when canary ingress was updated recently' do
      before do
        allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?) { true }
      end

      it_behaves_like 'failed request' do
        let(:message) { "This environment's canary ingress has been updated recently. Please retry later." }
      end
    end
  end

  describe '#execute' do
    subject { service.execute(environment) }

    let(:environment) { create(:environment, project: project) }
    let(:params) { { weight: 50 } }
    let(:canary_ingress) { ::Gitlab::Kubernetes::Ingress.new(kube_ingress(track: :canary)) }

    context 'when canary ingress is present in the environment' do
      before do
        allow(environment).to receive(:ingresses) { [canary_ingress] }
      end

      context 'when patch request succeeds' do
        let(:patch_data) do
          {
            metadata: {
              annotations: {
                Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
              }
            }
          }
        end

        before do
          allow(environment).to receive(:patch_ingress).with(canary_ingress, patch_data) { true }
        end

        it 'returns success' do
          expect(subject[:status]).to eq(:success)
          expect(subject[:message]).to be_nil
        end

        it 'clears all caches' do
          expect(environment).to receive(:clear_all_caches)

          subject
        end
      end

      context 'when patch request does not succeed' do
        before do
          allow(environment).to receive(:patch_ingress) { false }
        end

        it_behaves_like 'failed request' do
          let(:message) { 'Failed to update the Canary Ingress.' }
        end
      end
    end

    context 'when canary ingress is not present in the environment' do
      it_behaves_like 'failed request' do
        let(:message) { 'Canary Ingress does not exist in the environment.' }
      end
    end
  end
end


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

module Projects
  class ImportService < BaseService
    Error = Class.new(StandardError)
    PermissionError = Class.new(StandardError)

    # Returns true if this importer is supposed to perform its work in the
    # background.
    #
    # This method will only return `true` if async importing is explicitly
    # supported by an importer class (`Gitlab::GithubImport::ParallelImporter`
    # for example).
    def async?
      has_importer? && !!importer_class.try(:async?)
    end

    def execute
      track_start_import

      add_repository_to_project

      validate_repository_size!

      download_lfs_objects

      import_data

      after_execute_hook

      success
    rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError, StandardError => e
      Gitlab::Import::ImportFailureService.track(
        project_id: project.id,
        error_source: self.class.name,
        exception: e,
        metrics: true
      )

      message = Projects::ImportErrorFilter.filter_message(e.message)
      error(
        s_(
          "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
        ) % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: message }
      )
    end

    protected

    def extra_attributes_for_measurement
      {
        current_user: current_user&.name,
        project_full_path: project&.full_path,
        import_type: project&.import_type,
        file_path: project&.import_source
      }
    end

    private

    attr_reader :resolved_address

    def validate_repository_size!
      # Defined in EE::Projects::ImportService
    end

    def after_execute_hook
      # Defined in EE::Projects::ImportService
    end

    def track_start_import
      has_importer? && importer_class.try(:track_start_import, project)
    end

    def add_repository_to_project
      if project.external_import? && !unknown_url?
        begin
          @resolved_address = get_resolved_address
        rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError => e
          raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message }
        end
      end

      # We should skip the repository for a GitHub import or GitLab project import,
      # because these importers fetch the project repositories for us.
      return if importer_imports_repository?

      if unknown_url?
        # In this case, we only want to import issues, not a repository.
        create_repository
      elsif !project.repository_exists?
        import_repository
      end
    end

    def create_repository
      unless project.create_repository
        raise Error, s_('ImportProjects|The repository could not be created.')
      end
    end

    def import_repository
      refmap = importer_class.try(:refmap) if has_importer?

      if refmap
        project.ensure_repository
        project.repository.fetch_as_mirror(project.import_url, refmap: refmap, resolved_address: resolved_address)
      else
        project.repository.import_repository(project.import_url, resolved_address: resolved_address)
      end
    rescue ::Gitlab::Git::CommandError => e
      # Expire cache to prevent scenarios such as:
      # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
      # 2. Retried import, repo is broken or not imported but +exists?+ still returns true
      project.repository.expire_content_cache if project.repository_exists?

      raise Error, e.message
    end

    def download_lfs_objects
      # In this case, we only want to import issues
      return if unknown_url?

      # If it has its own repository importer, it has to implements its own lfs import download
      return if importer_imports_repository?

      return unless project.lfs_enabled?

      result = Projects::LfsPointers::LfsImportService.new(project).execute

      if result[:status] == :error
        # To avoid aborting the importing process, we silently fail
        # if any exception raises.
        Gitlab::AppLogger.error("The Lfs import process failed. #{result[:message]}")
      end
    end

    def import_data
      return unless has_importer?

      project.repository.expire_content_cache unless project.gitlab_project_import?

      unless importer.execute
        raise Error, s_('ImportProjects|The remote data could not be imported.')
      end
    end

    def importer_class
      @importer_class ||= Gitlab::ImportSources.importer(project.import_type)
    end

    def has_importer?
      Gitlab::ImportSources.importer_names.include?(project.import_type)
    end

    def importer
      importer_class.new(project)
    end

    def unknown_url?
      project.import_url == Project::UNKNOWN_IMPORT_URL
    end

    def importer_imports_repository?
      has_importer? && importer_class.try(:imports_repository?)
    end

    def get_resolved_address
      Gitlab::UrlBlocker
        .validate!(
          project.import_url,
          schemes: Project::VALID_IMPORT_PROTOCOLS,
          ports: Project::VALID_IMPORT_PORTS,
          allow_localhost: allow_local_requests?,
          allow_local_network: allow_local_requests?,
          dns_rebind_protection: dns_rebind_protection?)
        .then do |(import_url, resolved_host)|
          next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https])

          import_url.hostname.to_s
        end
    end

    def allow_local_requests?
      Rails.env.development? && # There is no known usecase for this in non-development environments
        Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
    end

    def dns_rebind_protection?
      return false if Gitlab.http_proxy_env?

      Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
    end
  end
end

Projects::ImportService.prepend_mod_with('Projects::ImportService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ImportService, feature_category: :importers do
  let!(:project) { create(:project) }
  let(:user) { project.creator }

  subject { described_class.new(project, user) }

  before do
    allow(project).to receive(:lfs_enabled?).and_return(true)
  end

  describe '#async?' do
    it 'returns true for an asynchronous importer' do
      importer_class = double(:importer, async?: true)

      allow(subject).to receive(:has_importer?).and_return(true)
      allow(subject).to receive(:importer_class).and_return(importer_class)

      expect(subject).to be_async
    end

    it 'returns false for a regular importer' do
      importer_class = double(:importer, async?: false)

      allow(subject).to receive(:has_importer?).and_return(true)
      allow(subject).to receive(:importer_class).and_return(importer_class)

      expect(subject).not_to be_async
    end

    it 'returns false when the importer does not define #async?' do
      importer_class = double(:importer)

      allow(subject).to receive(:has_importer?).and_return(true)
      allow(subject).to receive(:importer_class).and_return(importer_class)

      expect(subject).not_to be_async
    end

    it 'returns false when the importer does not exist' do
      allow(subject).to receive(:has_importer?).and_return(false)

      expect(subject).not_to be_async
    end
  end

  describe '#execute' do
    context 'with unknown url' do
      before do
        project.import_url = Project::UNKNOWN_IMPORT_URL
      end

      it 'succeeds if repository is created successfully' do
        expect(project).to receive(:create_repository).and_return(true)

        result = subject.execute

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

      it 'fails if repository creation fails' do
        expect(project).to receive(:create_repository).and_return(false)

        result = subject.execute

        expect(result[:status]).to eq :error
        expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - The repository could not be created."
      end

      context 'when repository creation succeeds' do
        it 'does not download lfs files' do
          expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)

          subject.execute
        end
      end
    end

    context 'with known url' do
      before do
        project.import_url = 'https://github.com/vim/vim.git'
        project.import_type = 'github'
      end

      context 'with a Github repository' do
        it 'tracks the start of import' do
          expect(Gitlab::GithubImport::ParallelImporter).to receive(:track_start_import)

          subject.execute
        end

        it 'succeeds if repository import was scheduled' do
          expect_any_instance_of(Gitlab::GithubImport::ParallelImporter)
            .to receive(:execute)
            .and_return(true)

          result = subject.execute

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

        it 'fails if repository import was not scheduled' do
          expect_any_instance_of(Gitlab::GithubImport::ParallelImporter)
            .to receive(:execute)
            .and_return(false)

          result = subject.execute

          expect(result[:status]).to eq :error
        end

        context 'when repository import scheduled' do
          it 'does not download lfs objects' do
            expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)

            subject.execute
          end
        end
      end

      context 'with a non Github repository' do
        before do
          project.import_url = 'https://bitbucket.org/vim/vim.git'
          project.import_type = 'bitbucket'
        end

        context 'when importer supports refmap' do
          before do
            project.import_type = 'gitea'
          end

          it 'succeeds if repository fetch as mirror is successful' do
            expect(project).to receive(:ensure_repository)
            expect(project.repository).to receive(:fetch_as_mirror).with('https://bitbucket.org/vim/vim.git', refmap: Gitlab::LegacyGithubImport::Importer.refmap, resolved_address: '').and_return(true)
            expect_next_instance_of(Gitlab::LegacyGithubImport::Importer) do |importer|
              expect(importer).to receive(:execute).and_return(true)
            end

            expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
              expect(service).to receive(:execute).and_return(status: :success)
            end

            result = subject.execute

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

          it 'fails if repository fetch as mirror fails' do
            expect(project).to receive(:ensure_repository)
            expect(project.repository)
              .to receive(:fetch_as_mirror)
              .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c')

            result = subject.execute

            expect(result[:status]).to eq :error
            expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
          end
        end

        context 'when importer does not support refmap' do
          it 'succeeds if repository import is successful' do
            expect_next_instance_of(Gitlab::BitbucketImport::ParallelImporter) do |importer|
              expect(importer).to receive(:execute).and_return(true)
            end

            result = subject.execute

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

          it 'fails if repository import fails' do
            expect_next_instance_of(Gitlab::BitbucketImport::ParallelImporter) do |importer|
              expect(importer).to receive(:execute)
                .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c')
            end

            result = subject.execute

            expect(result[:status]).to eq :error
            expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
          end

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

            it 'succeeds if repository import is successful' do
              expect(project.repository).to receive(:import_repository).and_return(true)
              expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer|
                expect(importer).to receive(:execute).and_return(true)
              end

              expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
                expect(service).to receive(:execute).and_return(status: :success)
              end

              result = subject.execute

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

            it 'fails if repository import fails' do
              expect(project.repository)
                .to receive(:import_repository)
                .with('https://bitbucket.org/vim/vim.git', resolved_address: '')
                .and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c')

              result = subject.execute

              expect(result[:status]).to eq :error
              expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
            end

            context 'when lfs import fails' do
              it 'logs the error' do
                error_message = 'error message'

                expect(project.repository).to receive(:import_repository).and_return(true)

                expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer|
                  expect(importer).to receive(:execute).and_return(true)
                end

                expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
                  expect(service).to receive(:execute).and_return(status: :error, message: error_message)
                end

                expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}")

                subject.execute
              end
            end

            context 'when repository import scheduled' do
              before do
                expect(project.repository).to receive(:import_repository).and_return(true)
                allow(subject).to receive(:import_data)
              end

              it 'downloads lfs objects if lfs_enabled is enabled for project' do
                allow(project).to receive(:lfs_enabled?).and_return(true)

                expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute)

                subject.execute
              end

              it 'does not download lfs objects if lfs_enabled is not enabled for project' do
                allow(project).to receive(:lfs_enabled?).and_return(false)
                expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)

                subject.execute
              end
            end
          end
        end
      end
    end

    context 'with valid importer' do
      before do
        provider = double(:provider).as_null_object
        stub_omniauth_setting(providers: [provider])

        project.import_url = 'https://github.com/vim/vim.git'
        project.import_type = 'github'

        allow(project).to receive(:import_data).and_return(double(:import_data).as_null_object)
      end

      it 'succeeds if importer succeeds' do
        allow_any_instance_of(Gitlab::GithubImport::ParallelImporter)
          .to receive(:execute).and_return(true)

        result = subject.execute

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

      it 'fails if importer fails' do
        allow_any_instance_of(Gitlab::GithubImport::ParallelImporter)
          .to receive(:execute)
          .and_return(false)

        result = subject.execute

        expect(result[:status]).to eq :error
      end

      context 'when importer' do
        it 'has a custom repository importer it does not download lfs objects' do
          allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(true)

          expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)

          subject.execute
        end

        it 'does not have a custom repository importer downloads lfs objects' do
          allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false)

          expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute)

          subject.execute
        end

        context 'when lfs import fails' do
          it 'logs the error' do
            error_message = 'error message'

            allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false)
            expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :error, message: error_message)
            expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}")

            subject.execute
          end
        end
      end
    end

    context 'with blocked import_URL' do
      it 'fails with localhost' do
        project.import_url = 'https://localhost:9000/vim/vim.git'

        result = described_class.new(project, user).execute

        expect(result[:status]).to eq :error
        expect(result[:message]).to include('Requests to localhost are not allowed')
      end

      it 'fails with port 25' do
        project.import_url = "https://github.com:25/vim/vim.git"

        result = subject.execute

        expect(result[:status]).to eq :error
        expect(result[:message]).to include('Only allowed ports are 80, 443')
      end

      it 'fails with file scheme' do
        project.import_url = "file:///tmp/dir.git"

        result = subject.execute

        expect(result[:status]).to eq :error
        expect(result[:message]).to include('Only allowed schemes are http, https')
      end
    end

    context 'when import is a local request' do
      before do
        project.import_url = "http://127.0.0.1/group/project"
      end

      context 'when local network requests are enabled' do
        before do
          stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
        end

        it 'returns an error' do
          expect(project.repository).not_to receive(:import_repository)
          expect(subject.execute).to include(
            status: :error,
            message: end_with('Requests to localhost are not allowed')
          )
        end

        context 'when environment is development' do
          before do
            stub_rails_env('development')
          end

          it 'imports successfully' do
            expect(project.repository)
              .to receive(:import_repository)
              .and_return(true)
            expect(subject.execute[:status]).to eq(:success)
          end
        end
      end
    end

    context 'when DNS rebind protection is disabled' do
      before do
        allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(false)
        project.import_url = "https://example.com/group/project"

        allow(Gitlab::UrlBlocker).to receive(:validate!)
          .with(
            project.import_url,
            ports: Project::VALID_IMPORT_PORTS,
            schemes: Project::VALID_IMPORT_PROTOCOLS,
            allow_local_network: false,
            allow_localhost: false,
            dns_rebind_protection: false
          )
          .and_return([Addressable::URI.parse("https://example.com/group/project"), nil])
      end

      it 'imports repository with url without additional resolved address' do
        expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '').and_return(true)

        expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
          expect(service).to receive(:execute).and_return(status: :success)
        end

        result = subject.execute

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

    context 'when DNS rebind protection is enabled' do
      before do
        allow(Gitlab::CurrentSettings).to receive(:http_proxy_env?).and_return(false)
        allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(true)
      end

      context 'when https url is provided' do
        before do
          project.import_url = "https://example.com/group/project"

          allow(Gitlab::UrlBlocker).to receive(:validate!)
            .with(
              project.import_url,
              ports: Project::VALID_IMPORT_PORTS,
              schemes: Project::VALID_IMPORT_PROTOCOLS,
              allow_local_network: false,
              allow_localhost: false,
              dns_rebind_protection: true
            )
            .and_return([Addressable::URI.parse("https://172.16.123.1/group/project"), 'example.com'])
        end

        it 'imports repository with url and additional resolved address' do
          expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '172.16.123.1').and_return(true)

          expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
            expect(service).to receive(:execute).and_return(status: :success)
          end

          result = subject.execute

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

        context 'when host resolves to an IPv6 address' do
          before do
            project.import_url = 'https://gitlab.com/gitlab-org/gitlab-development-kit'

            allow(Gitlab::UrlBlocker).to receive(:validate!)
              .with(
                project.import_url,
                ports: Project::VALID_IMPORT_PORTS,
                schemes: Project::VALID_IMPORT_PROTOCOLS,
                allow_local_network: false,
                allow_localhost: false,
                dns_rebind_protection: true
              )
             .and_return([Addressable::URI.parse('https://[2606:4700:90:0:f22e:fbec:5bed:a9b9]/gitlab-org/gitlab-development-kit'), 'gitlab.com'])
          end

          it 'imports repository with url and additional resolved bare IPv6 address' do
            expect(project.repository).to receive(:import_repository).with('https://gitlab.com/gitlab-org/gitlab-development-kit', resolved_address: '2606:4700:90:0:f22e:fbec:5bed:a9b9').and_return(true)

            expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
              expect(service).to receive(:execute).and_return(status: :success)
            end

            result = subject.execute

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

      context 'when http url is provided' do
        before do
          project.import_url = "http://example.com/group/project"

          allow(Gitlab::UrlBlocker).to receive(:validate!)
            .with(
              project.import_url,
              ports: Project::VALID_IMPORT_PORTS,
              schemes: Project::VALID_IMPORT_PROTOCOLS,
              allow_local_network: false,
              allow_localhost: false,
              dns_rebind_protection: true
            )
            .and_return([Addressable::URI.parse("http://172.16.123.1/group/project"), 'example.com'])
        end

        it 'imports repository with url and additional resolved address' do
          expect(project.repository).to receive(:import_repository).with('http://example.com/group/project', resolved_address: '172.16.123.1').and_return(true)

          expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
            expect(service).to receive(:execute).and_return(status: :success)
          end

          result = subject.execute

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

      context 'when git address is provided' do
        before do
          project.import_url = "git://example.com/group/project.git"

          allow(Gitlab::UrlBlocker).to receive(:validate!)
            .with(
              project.import_url,
              ports: Project::VALID_IMPORT_PORTS,
              schemes: Project::VALID_IMPORT_PROTOCOLS,
              allow_local_network: false,
              allow_localhost: false,
              dns_rebind_protection: true
            )
            .and_return([Addressable::URI.parse("git://172.16.123.1/group/project"), 'example.com'])
        end

        it 'imports repository with url and without resolved address' do
          expect(project.repository).to receive(:import_repository).with('git://example.com/group/project.git', resolved_address: '').and_return(true)

          expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
            expect(service).to receive(:execute).and_return(status: :success)
          end

          result = subject.execute

          expect(result[:status]).to eq(:success)
        end
      end
    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 open merge requests of
  # a project.
  class OpenMergeRequestsCountService < Projects::CountService
    def cache_key_name
      'open_merge_requests_count'
    end

    def self.query(project_ids)
      MergeRequest.opened.of_projects(project_ids)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching, feature_category: :code_review_workflow 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 open merge requests' do
      create(:merge_request, :opened, source_project: project, target_project: project)

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


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

module Projects
  # Service class that can be used to execute actions necessary after creating a
  # default branch.
  class ProtectDefaultBranchService
    attr_reader :project, :default_branch_protection

    # @param [Project] project
    def initialize(project)
      @project = project

      @default_branch_protection = Gitlab::Access::BranchProtection
        .new(project.namespace.default_branch_protection)
    end

    def execute
      protect_default_branch if default_branch
    end

    def protect_default_branch
      # Ensure HEAD points to the default branch in case it is not master
      project.change_head(default_branch)

      create_protected_branch if protect_branch? && !protected_branch_exists?
    end

    def create_protected_branch
      params = {
        name: default_branch,
        push_access_levels_attributes: [{ access_level: push_access_level }],
        merge_access_levels_attributes: [{ access_level: merge_access_level }]
      }

      # The creator of the project is always allowed to create protected
      # branches, so we skip the authorization check in this service class.
      ProtectedBranches::CreateService
        .new(project, project.creator, params)
        .execute(skip_authorization: true)
    end

    def protect_branch?
      default_branch_protection.any? &&
        !ProtectedBranch.protected?(project, default_branch)
    end

    def protected_branch_exists?
      project.all_protected_branches.find_by_name(default_branch).present?
    end

    def default_branch
      project.default_branch
    end

    def push_access_level
      if default_branch_protection.developer_can_push?
        Gitlab::Access::DEVELOPER
      else
        Gitlab::Access::MAINTAINER
      end
    end

    def merge_access_level
      if default_branch_protection.developer_can_merge?
        Gitlab::Access::DEVELOPER
      else
        Gitlab::Access::MAINTAINER
      end
    end
  end
end

Projects::ProtectDefaultBranchService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ProtectDefaultBranchService, feature_category: :source_code_management do
  let(:service) { described_class.new(project) }
  let(:project) { create(:project) }

  describe '#execute' do
    before do
      allow(service)
        .to receive(:protect_default_branch)
    end

    context 'without a default branch' do
      it 'does nothing' do
        allow(service)
          .to receive(:default_branch)
          .and_return(nil)

        service.execute

        expect(service)
          .not_to have_received(:protect_default_branch)
      end
    end

    context 'with a default branch' do
      it 'protects the default branch' do
        allow(service)
          .to receive(:default_branch)
          .and_return('master')

        service.execute

        expect(service)
          .to have_received(:protect_default_branch)
      end
    end
  end

  describe '#protect_default_branch' do
    before do
      allow(service)
        .to receive(:default_branch)
        .and_return('master')

      allow(project)
        .to receive(:change_head)
        .with('master')

      allow(service)
        .to receive(:create_protected_branch)
    end

    context 'when branch protection is needed' do
      before do
        allow(service)
          .to receive(:protect_branch?)
          .and_return(true)

        allow(service)
          .to receive(:create_protected_branch)
      end

      it 'changes the HEAD of the project' do
        service.protect_default_branch

        expect(project)
          .to have_received(:change_head)
      end

      it 'protects the default branch' do
        service.protect_default_branch

        expect(service)
          .to have_received(:create_protected_branch)
      end
    end

    context 'when branch protection is not needed' do
      before do
        allow(service)
          .to receive(:protect_branch?)
          .and_return(false)
      end

      it 'changes the HEAD of the project' do
        service.protect_default_branch

        expect(project)
          .to have_received(:change_head)
      end

      it 'does not protect the default branch' do
        service.protect_default_branch

        expect(service)
          .not_to have_received(:create_protected_branch)
      end
    end

    context 'when protected branch does not exist' do
      before do
        allow(service)
          .to receive(:protected_branch_exists?)
                .and_return(false)
        allow(service)
          .to receive(:protect_branch?)
                .and_return(true)
      end

      it 'changes the HEAD of the project' do
        service.protect_default_branch

        expect(project)
          .to have_received(:change_head)
      end

      it 'protects the default branch' do
        service.protect_default_branch

        expect(service)
          .to have_received(:create_protected_branch)
      end
    end

    context 'when protected branch already exists' do
      before do
        allow(service)
          .to receive(:protected_branch_exists?)
                .and_return(true)
      end

      it 'changes the HEAD of the project' do
        service.protect_default_branch

        expect(project)
          .to have_received(:change_head)
      end

      it 'does not protect the default branch' do
        service.protect_default_branch

        expect(service)
          .not_to have_received(:create_protected_branch)
      end
    end
  end

  describe '#create_protected_branch' do
    it 'creates the protected branch' do
      creator = instance_spy(User)
      create_service = instance_spy(ProtectedBranches::CreateService)
      access_level = Gitlab::Access::DEVELOPER
      params = {
        name: 'master',
        push_access_levels_attributes: [{ access_level: access_level }],
        merge_access_levels_attributes: [{ access_level: access_level }]
      }

      allow(project)
        .to receive(:creator)
        .and_return(creator)

      allow(ProtectedBranches::CreateService)
        .to receive(:new)
        .with(project, creator, params)
        .and_return(create_service)

      allow(service)
        .to receive(:push_access_level)
        .and_return(access_level)

      allow(service)
        .to receive(:merge_access_level)
        .and_return(access_level)

      allow(service)
        .to receive(:default_branch)
        .and_return('master')

      allow(create_service)
        .to receive(:execute)
        .with(skip_authorization: true)

      service.create_protected_branch

      expect(create_service)
        .to have_received(:execute)
    end
  end

  describe '#protect_branch?' do
    context 'when default branch protection is disabled' do
      it 'returns false' do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_NONE)

        expect(service.protect_branch?).to eq(false)
      end
    end

    context 'when default branch protection is enabled' do
      before do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)

        allow(service)
          .to receive(:default_branch)
          .and_return('master')
      end

      it 'returns false if the branch is already protected' do
        allow(ProtectedBranch)
          .to receive(:protected?)
          .with(project, 'master')
          .and_return(true)

        expect(service.protect_branch?).to eq(false)
      end

      it 'returns true if the branch is not yet protected' do
        allow(ProtectedBranch)
          .to receive(:protected?)
          .with(project, 'master')
          .and_return(false)

        expect(service.protect_branch?).to eq(true)
      end
    end
  end

  describe '#protected_branch_exists?' do
    let_it_be(:group) { create(:group) }
    let_it_be(:project) { create(:project, group: group) }

    let(:default_branch) { "default-branch" }

    before do
      allow(project).to receive(:default_branch).and_return(default_branch)
      create(:protected_branch, project: nil, group: group, name: default_branch)
    end

    context 'when feature flag `group_protected_branches` disabled' do
      before do
        stub_feature_flags(group_protected_branches: false)
        stub_feature_flags(allow_protected_branches_for_group: false)
      end

      it 'return false' do
        expect(service.protected_branch_exists?).to eq(false)
      end
    end

    context 'when feature flag `group_protected_branches` enabled' do
      before do
        stub_feature_flags(group_protected_branches: true)
        stub_feature_flags(allow_protected_branches_for_group: true)
      end

      it 'return true' do
        expect(service.protected_branch_exists?).to eq(true)
      end
    end
  end

  describe '#default_branch' do
    it 'returns the default branch of the project' do
      allow(project)
        .to receive(:default_branch)
        .and_return('master')

      expect(service.default_branch).to eq('master')
    end
  end

  describe '#push_access_level' do
    context 'when developers can push' do
      it 'returns the DEVELOPER access level' do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)

        expect(service.push_access_level).to eq(Gitlab::Access::DEVELOPER)
      end
    end

    context 'when developers can not push' do
      it 'returns the MAINTAINER access level' do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)

        expect(service.push_access_level).to eq(Gitlab::Access::MAINTAINER)
      end
    end
  end

  describe '#merge_access_level' do
    context 'when developers can merge' do
      it 'returns the DEVELOPER access level' do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)

        expect(service.merge_access_level).to eq(Gitlab::Access::DEVELOPER)
      end
    end

    context 'when developers can not merge' do
      it 'returns the MAINTAINER access level' do
        allow(project.namespace)
          .to receive(:default_branch_protection)
          .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)

        expect(service.merge_access_level).to eq(Gitlab::Access::MAINTAINER)
      end
    end
  end
end


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

module Projects
  class UpdateRepositoryStorageService
    include UpdateRepositoryStorageMethods

    delegate :project, to: :repository_storage_move

    private

    def track_repository(destination_storage_name)
      project.update_column(:repository_storage, destination_storage_name)

      # Connect project to pool repository from the new shard
      project.swap_pool_repository!

      # Connect project to the repository from the new shard
      project.track_project_repository

      # Link repository from the new shard to pool repository from the new shard
      project.link_pool_repository if replicate_object_pool_on_move_ff_enabled?
    end

    def mirror_repositories
      if project.repository_exists?
        mirror_repository(type: Gitlab::GlRepository::PROJECT)
      end

      if project.wiki.repository_exists?
        mirror_repository(type: Gitlab::GlRepository::WIKI)
      end

      if project.design_repository.exists?
        mirror_repository(type: ::Gitlab::GlRepository::DESIGN)
      end
    end

    def mirror_object_pool(destination_storage_name)
      return unless replicate_object_pool_on_move_ff_enabled?
      return unless project.repository_exists?

      pool_repository = project.pool_repository
      return unless pool_repository

      # If pool repository already exists, then we will link the moved project repository to it
      return if pool_repository_exists_for?(shard_name: destination_storage_name, pool_repository: pool_repository)

      target_pool_repository = create_pool_repository_for!(
        shard_name: destination_storage_name,
        pool_repository: pool_repository
      )

      Repositories::ReplicateService.new(pool_repository.object_pool.repository)
        .execute(target_pool_repository.object_pool.repository, :object_pool)
    end

    def remove_old_paths
      super

      if project.wiki.repository_exists?
        Gitlab::Git::Repository.new(
          source_storage_name,
          "#{project.wiki.disk_path}.git",
          nil,
          nil
        ).remove
      end

      if project.design_repository.exists?
        Gitlab::Git::Repository.new(
          source_storage_name,
          "#{project.design_repository.disk_path}.git",
          nil,
          nil
        ).remove
      end
    end

    def pool_repository_exists_for?(shard_name:, pool_repository:)
      PoolRepository.by_disk_path_and_shard_name(
        pool_repository.disk_path,
        shard_name
      ).exists?
    end

    def create_pool_repository_for!(shard_name:, pool_repository:)
      # Set state `ready` because we manually replicate object pool
      PoolRepository.create!(
        shard: Shard.by_name(shard_name),
        source_project: pool_repository.source_project,
        disk_path: pool_repository.disk_path,
        state: 'ready'
      )
    end

    def replicate_object_pool_on_move_ff_enabled?
      Feature.enabled?(:replicate_object_pool_on_move, project)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::UpdateRepositoryStorageService, feature_category: :source_code_management do
  include Gitlab::ShellAdapter

  subject { described_class.new(repository_storage_move) }

  describe "#execute" do
    let(:time) { Time.current }

    before do
      allow(Time).to receive(:now).and_return(time)

      stub_storage_settings('test_second_storage' => {})
    end

    context 'without wiki and design repository' do
      let!(:shard_default) { create(:shard, name: 'default') }
      let!(:shard_second_storage) { create(:shard, name: 'test_second_storage') }

      let(:project) { create(:project, :repository, wiki_enabled: false) }
      let(:destination) { 'test_second_storage' }
      let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }
      let!(:checksum) { project.repository.checksum }
      let(:project_repository_double) { double(:repository) }
      let(:original_project_repository_double) { double(:repository) }

      let(:object_pool_double) { double(:object_pool, repository: object_pool_repository_double) }
      let(:object_pool_repository_double) { double(:repository) }

      let(:original_object_pool_double) { double(:object_pool, repository: original_object_pool_repository_double) }
      let(:original_object_pool_repository_double) { double(:repository) }

      before do
        allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
        allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
        allow(Gitlab::Git::Repository).to receive(:new).and_call_original
        allow(Gitlab::Git::Repository).to receive(:new)
          .with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path)
          .and_return(project_repository_double)
        allow(Gitlab::Git::Repository).to receive(:new)
          .with('default', project.repository.raw.relative_path, nil, nil)
          .and_return(original_project_repository_double)

        allow(Gitlab::Git::ObjectPool).to receive(:new).and_call_original
        allow(Gitlab::Git::ObjectPool).to receive(:new)
          .with('test_second_storage', anything, anything, anything)
          .and_return(object_pool_double)
        allow(Gitlab::Git::ObjectPool).to receive(:new)
          .with('default', anything, anything, anything)
          .and_return(original_object_pool_double)

        allow(original_object_pool_double).to receive(:create)
        allow(object_pool_double).to receive(:create)
      end

      context 'when the move succeeds' do
        it 'moves the repository to the new storage and unmarks the repository as read-only' do
          expect(project_repository_double).to receive(:replicate)
            .with(project.repository.raw)
          expect(project_repository_double).to receive(:checksum)
            .and_return(checksum)
          expect(original_project_repository_double).to receive(:remove)

          result = subject.execute
          project.reload

          expect(result).to be_success
          expect(project).not_to be_repository_read_only
          expect(project.repository_storage).to eq('test_second_storage')
          expect(project.project_repository.shard_name).to eq('test_second_storage')
          expect(repository_storage_move.reload).to be_finished
          expect(repository_storage_move.error_message).to be_nil
        end
      end

      context 'when touch raises an exception' do
        let(:exception) { RuntimeError.new('Boom') }

        it 'marks the storage move as failed and restores read-write access' do
          allow(repository_storage_move).to receive(:container).and_return(project)

          allow(project).to receive(:touch).and_wrap_original do
            project.assign_attributes(updated_at: 1.second.ago)
            raise exception
          end

          expect(project_repository_double).to receive(:replicate)
            .with(project.repository.raw)
          expect(project_repository_double).to receive(:checksum)
            .and_return(checksum)

          expect { subject.execute }.to raise_error(exception)
          project.reload

          expect(project).not_to be_repository_read_only
          expect(repository_storage_move.reload).to be_failed
          expect(repository_storage_move.error_message).to eq('Boom')
        end
      end

      context 'when the filesystems are the same' do
        before do
          expect(Gitlab::GitalyClient).to receive(:filesystem_id).twice.and_return(SecureRandom.uuid)
        end

        it 'updates the database without trying to move the repostory', :aggregate_failures do
          result = subject.execute
          project.reload

          expect(result).to be_success
          expect(project).not_to be_repository_read_only
          expect(project.repository_storage).to eq('test_second_storage')
          expect(project.project_repository.shard_name).to eq('test_second_storage')
        end
      end

      context 'when the move fails' do
        it 'unmarks the repository as read-only without updating the repository storage' do
          allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
          allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)

          expect(project_repository_double).to receive(:replicate)
            .with(project.repository.raw)
            .and_raise(Gitlab::Git::CommandError, 'Boom')
          expect(project_repository_double).to receive(:remove)

          expect do
            subject.execute
          end.to raise_error(Gitlab::Git::CommandError)

          expect(project).not_to be_repository_read_only
          expect(project.repository_storage).to eq('default')
          expect(repository_storage_move).to be_failed
          expect(repository_storage_move.error_message).to eq('Boom')
        end
      end

      context 'when the cleanup fails' do
        it 'sets the correct state' do
          expect(project_repository_double).to receive(:replicate)
            .with(project.repository.raw)
          expect(project_repository_double).to receive(:checksum)
            .and_return(checksum)
          expect(original_project_repository_double).to receive(:remove)
            .and_raise(Gitlab::Git::CommandError)

          expect do
            subject.execute
          end.to raise_error(Gitlab::Git::CommandError)

          expect(repository_storage_move).to be_cleanup_failed
        end
      end

      context 'when the checksum does not match' do
        it 'unmarks the repository as read-only without updating the repository storage' do
          allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
          allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)

          expect(project_repository_double).to receive(:replicate)
            .with(project.repository.raw)
          expect(project_repository_double).to receive(:checksum)
            .and_return('not matching checksum')
          expect(project_repository_double).to receive(:remove)

          expect do
            subject.execute
          end.to raise_error(Repositories::ReplicateService::Error, /Failed to verify project repository checksum/)

          expect(project).not_to be_repository_read_only
          expect(project.repository_storage).to eq('default')
        end
      end

      context 'with repository pool' do
        let(:shard_from) { shard_default }
        let(:shard_to) { shard_second_storage }
        let(:old_object_pool_checksum) { 'abcd' }
        let(:new_object_pool_checksum) { old_object_pool_checksum }

        before do
          allow(project_repository_double).to receive(:replicate).with(project.repository.raw)
          allow(project_repository_double).to receive(:checksum).and_return(checksum)
          allow(original_project_repository_double).to receive(:remove)

          allow(object_pool_repository_double).to receive(:replicate).with(original_object_pool_repository_double)
          allow(object_pool_repository_double).to receive(:checksum).and_return(new_object_pool_checksum)
          allow(original_object_pool_repository_double).to receive(:checksum).and_return(old_object_pool_checksum)

          allow(object_pool_double).to receive(:link) do |repository|
            expect(repository.storage).to eq 'test_second_storage'
          end
        end

        context 'when project had a repository pool' do
          let!(:pool_repository) { create(:pool_repository, :ready, shard: shard_from, source_project: project) }

          it 'creates a new repository pool and connects project to it' do
            result = subject.execute
            expect(result).to be_success

            project.reload.cleanup

            new_pool_repository = project.pool_repository

            expect(new_pool_repository).not_to eq(pool_repository)
            expect(new_pool_repository.shard).to eq(shard_second_storage)
            expect(new_pool_repository.state).to eq('ready')
            expect(new_pool_repository.disk_path).to eq(pool_repository.disk_path)
            expect(new_pool_repository.source_project).to eq(project)

            expect(object_pool_double).to have_received(:link).with(project.repository.raw)
          end

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

            it 'just moves the repository without the object pool' do
              result = subject.execute
              expect(result).to be_success

              project.reload.cleanup

              new_pool_repository = project.pool_repository

              expect(new_pool_repository).to eq(pool_repository)
              expect(new_pool_repository.shard).to eq(shard_default)
              expect(new_pool_repository.state).to eq('ready')
              expect(new_pool_repository.source_project).to eq(project)

              expect(object_pool_repository_double).not_to have_received(:replicate)
              expect(object_pool_double).not_to have_received(:link)
            end
          end

          context 'when new shard has a repository pool' do
            let!(:new_pool_repository) { create(:pool_repository, :ready, shard: shard_to, source_project: project) }

            it 'connects project to it' do
              result = subject.execute
              expect(result).to be_success

              project.reload.cleanup

              project_pool_repository = project.pool_repository

              expect(project_pool_repository).to eq(new_pool_repository)
              expect(object_pool_double).to have_received(:link).with(project.repository.raw)
            end
          end

          context 'when new shard has a repository pool without the root project' do
            let!(:new_pool_repository) { create(:pool_repository, :ready, shard: shard_to, disk_path: pool_repository.disk_path) }

            before do
              pool_repository.update!(source_project: nil)
              new_pool_repository.update!(source_project: nil)
            end

            it 'connects project to it' do
              result = subject.execute
              expect(result).to be_success

              project.reload.cleanup

              project_pool_repository = project.pool_repository

              expect(project_pool_repository).to eq(new_pool_repository)
              expect(object_pool_double).to have_received(:link).with(project.repository.raw)
            end
          end

          context 'when repository does not exist' do
            let(:project) { create(:project) }
            let(:checksum) { nil }

            it 'does not mirror object pool' do
              result = subject.execute
              expect(result).to be_success

              expect(object_pool_repository_double).not_to have_received(:replicate)
            end
          end

          context 'when project belongs to repository pool, but not as a root project' do
            let!(:another_project) { create(:project, :repository) }
            let!(:pool_repository) { create(:pool_repository, :ready, shard: shard_from, source_project: another_project) }

            before do
              project.update!(pool_repository: pool_repository)
            end

            it 'creates a new repository pool and connects project to it' do
              result = subject.execute
              expect(result).to be_success

              project.reload.cleanup

              new_pool_repository = project.pool_repository

              expect(new_pool_repository).not_to eq(pool_repository)
              expect(new_pool_repository.shard).to eq(shard_second_storage)
              expect(new_pool_repository.state).to eq('ready')
              expect(new_pool_repository.source_project).to eq(another_project)

              expect(object_pool_double).to have_received(:link).with(project.repository.raw)
            end
          end

          context 'when project belongs to the repository pool without a root project' do
            let!(:pool_repository) { create(:pool_repository, :ready, shard: shard_from) }

            before do
              pool_repository.update!(source_project: nil)
              project.update!(pool_repository: pool_repository)
            end

            it 'creates a new repository pool without a root project and connects project to it' do
              result = subject.execute
              expect(result).to be_success

              project.reload.cleanup

              new_pool_repository = project.pool_repository

              expect(new_pool_repository).not_to eq(pool_repository)
              expect(new_pool_repository.shard).to eq(shard_second_storage)
              expect(new_pool_repository.state).to eq('ready')
              expect(new_pool_repository.source_project).to eq(nil)
              expect(new_pool_repository.disk_path).to eq(pool_repository.disk_path)

              expect(object_pool_double).to have_received(:link).with(project.repository.raw)
            end
          end

          context 'when object pool checksum does not match' do
            let(:new_object_pool_checksum) { 'not_match' }

            it 'raises an error and removes the new object pool repository' do
              expect(object_pool_repository_double).to receive(:remove)

              original_count = PoolRepository.count

              expect do
                subject.execute
              end.to raise_error(Repositories::ReplicateService::Error, /Failed to verify object_pool repository/)

              project.reload

              expect(PoolRepository.count).to eq(original_count)

              expect(project.pool_repository).to eq(pool_repository)
              expect(project.repository.shard).to eq('default')
            end
          end
        end
      end

      context 'when the repository move is finished' do
        let(:repository_storage_move) { create(:project_repository_storage_move, :finished, container: project, destination_storage_name: destination) }

        it 'is idempotent' do
          expect do
            result = subject.execute

            expect(result).to be_success
          end.not_to change(repository_storage_move, :state)
        end
      end

      context 'when the repository move is failed' do
        let(:repository_storage_move) { create(:project_repository_storage_move, :failed, container: project, destination_storage_name: destination) }

        it 'is idempotent' do
          expect do
            result = subject.execute

            expect(result).to be_success
          end.not_to change(repository_storage_move, :state)
        end
      end
    end

    context 'project with no repositories' do
      let(:project) { create(:project) }
      let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: 'test_second_storage') }

      it 'updates the database' do
        allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
        allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)

        result = subject.execute
        project.reload

        expect(result).to be_success
        expect(project).not_to be_repository_read_only
        expect(project.repository_storage).to eq('test_second_storage')
        expect(project.project_repository.shard_name).to eq('test_second_storage')
      end
    end

    context 'with wiki repository' do
      include_examples 'moves repository to another storage', 'wiki' do
        let(:project) { create(:project, :repository, wiki_enabled: true) }
        let(:repository) { project.wiki.repository }
        let(:destination) { 'test_second_storage' }
        let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }

        before do
          project.create_wiki
        end
      end
    end

    context 'with design repository' do
      include_examples 'moves repository to another storage', 'design' do
        let(:project) { create(:project, :repository) }
        let(:repository) { project.design_repository }
        let(:destination) { 'test_second_storage' }
        let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, container: project, destination_storage_name: destination) }

        before do
          project.design_repository.create_if_not_exists
        end
      end
    end
  end
end


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

module Projects
  class FetchStatisticsIncrementService
    attr_reader :project

    def initialize(project)
      @project = project
    end

    def execute
      increment_fetch_count_sql = <<~SQL
        INSERT INTO #{table_name} (project_id, date, fetch_count)
        VALUES (#{project.id}, '#{Date.today}', 1)
        ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1
      SQL

      ProjectDailyStatistic.connection.execute(increment_fetch_count_sql)
    end

    private

    def table_name
      ProjectDailyStatistic.table_name
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

module Projects
  RSpec.describe FetchStatisticsIncrementService, feature_category: :groups_and_projects do
    let(:project) { create(:project) }

    describe '#execute' do
      subject { described_class.new(project).execute }

      it 'creates a new record for today with count == 1' do
        expect { subject }.to change { ProjectDailyStatistic.count }.by(1)
        created_stat = ProjectDailyStatistic.last

        expect(created_stat.fetch_count).to eq(1)
        expect(created_stat.project).to eq(project)
        expect(created_stat.date).to eq(Date.today)
      end

      it "doesn't increment previous days statistics" do
        yesterday_stat = create(:project_daily_statistic, fetch_count: 5, project: project, date: 1.day.ago)

        expect { subject }.not_to change { yesterday_stat.reload.fetch_count }
      end

      context 'when the record already exists for today' do
        let!(:project_daily_stat) { create(:project_daily_statistic, fetch_count: 5, project: project, date: Date.today) }

        it 'increments the today record count by 1' do
          expect { subject }.to change { project_daily_stat.reload.fetch_count }.to(6)
        end
      end
    end
  end
end