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

module Ci
  class RetryPipelineService < ::BaseService
    include Gitlab::OptimisticLocking

    def execute(pipeline)
      access_response = check_access(pipeline)
      return access_response if access_response.error?

      pipeline.ensure_scheduling_type!

      builds_relation(pipeline).find_each do |build|
        next unless can_be_retried?(build)

        Ci::RetryJobService.new(project, current_user).clone!(build)
      end

      pipeline.processables.latest.skipped.find_each do |skipped|
        retry_optimistic_lock(skipped, name: 'ci_retry_pipeline') { |build| build.process(current_user) }
      end

      pipeline.reset_source_bridge!(current_user)

      ::MergeRequests::AddTodoWhenBuildFailsService
        .new(project: project, current_user: current_user)
        .close_all(pipeline)

      start_pipeline(pipeline)

      ServiceResponse.success
    rescue Gitlab::Access::AccessDeniedError => e
      ServiceResponse.error(message: e.message, http_status: :forbidden)
    end

    def check_access(pipeline)
      if can?(current_user, :update_pipeline, pipeline)
        ServiceResponse.success
      else
        ServiceResponse.error(message: '403 Forbidden', http_status: :forbidden)
      end
    end

    private

    def builds_relation(pipeline)
      pipeline.retryable_builds.preload_needs
    end

    def can_be_retried?(build)
      can?(current_user, :update_build, build)
    end

    def start_pipeline(pipeline)
      Ci::PipelineCreation::StartPipelineService.new(pipeline).execute
    end
  end
end

Ci::RetryPipelineService.prepend_mod_with('Ci::RetryPipelineService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::RetryPipelineService, '#execute', feature_category: :continuous_integration do
  include ProjectForksHelper

  let_it_be_with_refind(:user) { create(:user) }
  let_it_be_with_refind(:project) { create(:project) }

  let(:pipeline) { create(:ci_pipeline, project: project) }
  let(:build_stage) { create(:ci_stage, name: 'build', position: 0, pipeline: pipeline) }
  let(:test_stage) { create(:ci_stage, name: 'test', position: 1, pipeline: pipeline) }
  let(:deploy_stage) { create(:ci_stage, name: 'deploy', position: 2, pipeline: pipeline) }

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

  context 'when user has full ability to modify pipeline' do
    before do
      project.add_developer(user)

      create(:protected_branch, :developers_can_merge, name: pipeline.ref, project: project)
    end

    context 'when there are already retried jobs present' do
      before do
        create_build('rspec', :canceled, build_stage, retried: true)
        create_build('rspec', :failed, build_stage)
      end

      it 'does not retry jobs that has already been retried' do
        expect(statuses.first).to be_retried
        expect { service.execute(pipeline) }
          .to change { CommitStatus.count }.by(1)
      end
    end

    context 'when there are failed builds in the last stage' do
      before do
        create_build('rspec 1', :success, build_stage)
        create_build('rspec 2', :failed, test_stage)
        create_build('rspec 3', :canceled, test_stage)
      end

      it 'enqueues all builds in the last stage' do
        service.execute(pipeline)

        expect(build('rspec 2')).to be_pending
        expect(build('rspec 3')).to be_pending
        expect(pipeline.reload).to be_running
      end
    end

    context 'when there are failed or canceled builds in the first stage' do
      before do
        create_build('rspec 1', :failed, build_stage)
        create_build('rspec 2', :canceled, build_stage)
        create_build('rspec 3', :canceled, test_stage)
        create_build('spinach 1', :canceled, deploy_stage)
      end

      it 'retries builds failed builds and marks subsequent for processing' do
        service.execute(pipeline)

        expect(build('rspec 1')).to be_pending
        expect(build('rspec 2')).to be_pending
        expect(build('rspec 3')).to be_created
        expect(build('spinach 1')).to be_created
        expect(pipeline.reload).to be_running
      end

      it 'changes ownership of subsequent builds' do
        expect(build('rspec 2').user).not_to eq(user)
        expect(build('rspec 3').user).not_to eq(user)
        expect(build('spinach 1').user).not_to eq(user)

        service.execute(pipeline)

        expect(build('rspec 2').user).to eq(user)
        expect(build('rspec 3').user).to eq(user)
        expect(build('spinach 1').user).to eq(user)
      end
    end

    context 'when there is failed build present which was run on failure' do
      before do
        create_build('rspec 1', :failed, build_stage)
        create_build('rspec 2', :canceled, build_stage)
        create_build('rspec 3', :canceled, test_stage)
        create_build('report 1', :failed, deploy_stage)
      end

      it 'retries builds only in the first stage' do
        service.execute(pipeline)

        expect(build('rspec 1')).to be_pending
        expect(build('rspec 2')).to be_pending
        expect(build('rspec 3')).to be_created
        expect(build('report 1')).to be_created
        expect(pipeline.reload).to be_running
      end

      it 'creates a new job for report job in this case' do
        service.execute(pipeline)

        expect(statuses.find_by(name: 'report 1', status: 'failed')).to be_retried
      end
    end

    context 'when there is a failed test in a DAG' do
      before do
        create_build('build', :success, build_stage)
        create_build('build2', :success, build_stage)
        test_build = create_build('test', :failed, test_stage, scheduling_type: :dag)
        create(:ci_build_need, build: test_build, name: 'build')
        create(:ci_build_need, build: test_build, name: 'build2')
      end

      it 'retries the test' do
        service.execute(pipeline)

        expect(build('build')).to be_success
        expect(build('build2')).to be_success
        expect(build('test')).to be_pending
        expect(build('test').needs.map(&:name)).to match_array(%w[build build2])
      end

      context 'when there is a failed DAG test without needs' do
        before do
          create_build('deploy', :failed, deploy_stage, scheduling_type: :dag)
        end

        it 'retries the test' do
          service.execute(pipeline)

          expect(build('build')).to be_success
          expect(build('build2')).to be_success
          expect(build('test')).to be_pending
          expect(build('deploy')).to be_pending
        end
      end
    end

    context 'when the last stage was skipped' do
      before do
        create_build('build 1', :success, build_stage)
        create_build('test 2', :failed, test_stage)
        create_build('report 3', :skipped, deploy_stage)
        create_build('report 4', :skipped, deploy_stage)
      end

      it 'retries builds only in the first stage' do
        service.execute(pipeline)

        expect(build('build 1')).to be_success
        expect(build('test 2')).to be_pending
        expect(build('report 3')).to be_created
        expect(build('report 4')).to be_created
        expect(pipeline.reload).to be_running
      end
    end

    context 'when pipeline contains manual actions' do
      context 'when there are optional manual actions only' do
        context 'when there is a canceled manual action in first stage' do
          before do
            create_build('rspec 1', :failed, build_stage)
            create_build('staging', :canceled, build_stage, when: :manual, allow_failure: true)
            create_build('rspec 2', :canceled, test_stage)
          end

          it 'retries failed builds and marks subsequent for processing' do
            service.execute(pipeline)

            expect(build('rspec 1')).to be_pending
            expect(build('staging')).to be_manual
            expect(build('rspec 2')).to be_created
            expect(pipeline.reload).to be_running
          end

          it 'changes ownership of subsequent builds' do
            expect(build('staging').user).not_to eq(user)
            expect(build('rspec 2').user).not_to eq(user)

            service.execute(pipeline)

            expect(build('staging').user).to eq(user)
            expect(build('rspec 2').user).to eq(user)
          end
        end
      end

      context 'when pipeline has blocking manual actions defined' do
        context 'when pipeline retry should enqueue builds' do
          before do
            create_build('test', :failed, build_stage)
            create_build('deploy', :canceled, build_stage, when: :manual, allow_failure: false)
            create_build('verify', :canceled, test_stage)
          end

          it 'retries failed builds' do
            service.execute(pipeline)

            expect(build('test')).to be_pending
            expect(build('deploy')).to be_manual
            expect(build('verify')).to be_created
            expect(pipeline.reload).to be_running
          end
        end

        context 'when pipeline retry should block pipeline immediately' do
          before do
            create_build('test', :success, build_stage)
            create_build('deploy:1', :success, test_stage, when: :manual, allow_failure: false)
            create_build('deploy:2', :failed, test_stage, when: :manual, allow_failure: false)
            create_build('verify', :canceled, deploy_stage)
          end

          it 'reprocesses blocking manual action and blocks pipeline' do
            service.execute(pipeline)

            expect(build('deploy:1')).to be_success
            expect(build('deploy:2')).to be_manual
            expect(build('verify')).to be_created
            expect(pipeline.reload).to be_blocked
          end
        end
      end

      context 'when there is a skipped manual action in last stage' do
        before do
          create_build('rspec 1', :canceled, build_stage)
          create_build('rspec 2', :skipped, build_stage, when: :manual, allow_failure: true)
          create_build('staging', :skipped, test_stage, when: :manual, allow_failure: true)
        end

        it 'retries canceled job and reprocesses manual actions' do
          service.execute(pipeline)

          expect(build('rspec 1')).to be_pending
          expect(build('rspec 2')).to be_manual
          expect(build('staging')).to be_created
          expect(pipeline.reload).to be_running
        end
      end

      context 'when there is a created manual action in the last stage' do
        before do
          create_build('rspec 1', :canceled, build_stage)
          create_build('staging', :created, test_stage, when: :manual, allow_failure: true)
        end

        it 'retries canceled job and does not update the manual action' do
          service.execute(pipeline)

          expect(build('rspec 1')).to be_pending
          expect(build('staging')).to be_created
          expect(pipeline.reload).to be_running
        end
      end

      context 'when there is a created manual action in the first stage' do
        before do
          create_build('rspec 1', :canceled, build_stage)
          create_build('staging', :created, build_stage, when: :manual, allow_failure: true)
        end

        it 'retries canceled job and processes the manual action' do
          service.execute(pipeline)

          expect(build('rspec 1')).to be_pending
          expect(build('staging')).to be_manual
          expect(pipeline.reload).to be_running
        end
      end

      context 'when there is a failed manual action' do
        before do
          create_build('rspec', :success, build_stage)
          create_build('manual-rspec', :failed, build_stage, when: :manual, allow_failure: true)
        end

        it 'processes the manual action' do
          service.execute(pipeline)

          expect(build('rspec')).to be_success
          expect(build('manual-rspec')).to be_manual
          expect(pipeline.reload).to be_success
        end
      end
    end

    it 'closes all todos about failed jobs for pipeline' do
      expect(::MergeRequests::AddTodoWhenBuildFailsService)
        .to receive_message_chain(:new, :close_all)

      service.execute(pipeline)
    end

    it 'reprocesses the pipeline' do
      expect_any_instance_of(Ci::ProcessPipelineService).to receive(:execute)

      service.execute(pipeline)
    end

    context 'when pipeline has processables with nil scheduling_type' do
      let!(:build1) { create_build('build1', :success, build_stage) }
      let!(:build2) { create_build('build2', :failed, build_stage) }
      let!(:build3) { create_build('build3', :failed, test_stage) }
      let!(:build3_needs_build1) { create(:ci_build_need, build: build3, name: build1.name) }

      before do
        statuses.update_all(scheduling_type: nil)
      end

      it 'populates scheduling_type of processables' do
        service.execute(pipeline)

        expect(build1.reload.scheduling_type).to eq('stage')
        expect(build2.reload.scheduling_type).to eq('stage')
        expect(build3.reload.scheduling_type).to eq('dag')
      end
    end

    context 'when the pipeline is a downstream pipeline and the bridge is depended' do
      let!(:bridge) { create(:ci_bridge, :strategy_depend, status: 'success') }

      before do
        create(:ci_sources_pipeline, pipeline: pipeline, source_job: bridge)
      end

      context 'without permission' do
        it 'does nothing to the bridge' do
          expect { service.execute(pipeline) }.to not_change { bridge.reload.status }
           .and not_change { bridge.reload.user }
        end
      end

      context 'with permission' do
        let!(:bridge_pipeline) { create(:ci_pipeline, project: create(:project)) }
        let!(:bridge) do
          create(:ci_bridge, :strategy_depend, status: 'success', pipeline: bridge_pipeline)
        end

        before do
          bridge_pipeline.project.add_maintainer(user)
        end

        it 'marks source bridge as pending' do
          expect { service.execute(pipeline) }.to change { bridge.reload.status }.to('pending')
        end

        it 'assigns the current user to the source bridge' do
          expect { service.execute(pipeline) }.to change { bridge.reload.user }.to(user)
        end
      end
    end

    context 'when there are skipped jobs in later stages' do
      before do
        create_build('build 1', :success, build_stage)
        create_build('test 2', :failed, test_stage)
        create_build('report 3', :skipped, deploy_stage)
        create_bridge('deploy 4', :skipped, deploy_stage)
      end

      it 'retries failed jobs and processes skipped jobs' do
        service.execute(pipeline)

        expect(build('build 1')).to be_success
        expect(build('test 2')).to be_pending
        expect(build('report 3')).to be_created
        expect(build('deploy 4')).to be_created

        expect(pipeline.reload).to be_running
      end
    end

    context 'when user is not allowed to retry build' do
      before do
        build = create(:ci_build, pipeline: pipeline, status: :failed)
        allow_next_instance_of(Ci::RetryJobService) do |service|
          allow(service).to receive(:can?).with(user, :update_build, build).and_return(false)
        end
      end

      it 'returns an error' do
        response = service.execute(pipeline)

        expect(response.http_status).to eq(:forbidden)
        expect(response.errors).to include('403 Forbidden')
        expect(pipeline.reload).not_to be_running
      end
    end
  end

  context 'when user is not allowed to retry pipeline' do
    it 'returns an error' do
      response = service.execute(pipeline)

      expect(response.http_status).to eq(:forbidden)
      expect(response.errors).to include('403 Forbidden')
      expect(pipeline.reload).not_to be_running
    end
  end

  context 'when user is not allowed to trigger manual action' do
    before do
      project.add_developer(user)
      create(:protected_branch, :maintainers_can_push, name: pipeline.ref, project: project)
    end

    context 'when there is a failed manual action present' do
      before do
        create_build('test', :failed, build_stage)
        create_build('deploy', :failed, build_stage, when: :manual)
        create_build('verify', :canceled, test_stage)
      end

      it 'returns an error' do
        response = service.execute(pipeline)

        expect(response.http_status).to eq(:forbidden)
        expect(response.errors).to include('403 Forbidden')
        expect(pipeline.reload).not_to be_running
      end
    end

    context 'when there is a failed manual action in later stage' do
      before do
        create_build('test', :failed, build_stage)
        create_build('deploy', :failed, test_stage, when: :manual)
        create_build('verify', :canceled, deploy_stage)
      end

      it 'returns an error' do
        response = service.execute(pipeline)

        expect(response.http_status).to eq(:forbidden)
        expect(response.errors).to include('403 Forbidden')
        expect(pipeline.reload).not_to be_running
      end
    end
  end

  context 'when maintainer is allowed to push to forked project' do
    let(:user) { create(:user) }
    let(:project) { create(:project, :public) }
    let(:forked_project) { fork_project(project) }
    let(:pipeline) { create(:ci_pipeline, project: forked_project, ref: 'fixes') }

    before do
      project.add_maintainer(user)

      create_build('rspec 1', :failed, test_stage, project: project, ref: pipeline.ref)

      allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
      allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true)
    end

    it 'allows to retry failed pipeline' do
      service.execute(pipeline)

      expect(build('rspec 1')).to be_pending
      expect(pipeline).to be_running
    end
  end

  def statuses
    pipeline.reload.statuses
  end

  # The method name can be confusing because this can actually return both Ci::Build and Ci::Bridge
  def build(name)
    statuses.latest.find_by(name: name)
  end

  def create_build(name, status, stage, **opts)
    create_processable(:ci_build, name, status, stage, **opts)
  end

  def create_bridge(name, status, stage, **opts)
    create_processable(:ci_bridge, name, status, stage, **opts)
  end

  def create_processable(type, name, status, stage, **opts)
    create(
      type,
      name: name,
      status: status,
      ci_stage: stage,
      stage_idx: stage.position,
      pipeline: pipeline,
      **opts
    ) do |_job|
      ::Ci::ProcessPipelineService.new(pipeline).execute
    end
  end
end


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

module Ci
  class PipelineScheduleService < BaseService
    def execute(schedule)
      return unless project.persisted?

      # Ensure `next_run_at` is set properly before creating a pipeline.
      # Otherwise, multiple pipelines could be created in a short interval.
      schedule.schedule_next_run!
      RunPipelineScheduleWorker.perform_async(schedule.id, current_user&.id)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::PipelineScheduleService, feature_category: :continuous_integration do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project) }

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

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

    let_it_be(:schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }

    it 'schedules next run' do
      expect(schedule).to receive(:schedule_next_run!)

      subject
    end

    it 'runs RunPipelineScheduleWorker' do
      expect(RunPipelineScheduleWorker)
        .to receive(:perform_async).with(schedule.id, schedule.owner.id)

      subject
    end

    context 'when owner is nil' do
      let(:schedule) { create(:ci_pipeline_schedule, project: project, owner: nil) }

      it 'does not raise an error' do
        expect { subject }.not_to raise_error
      end
    end

    context 'when the project is missing' do
      let(:project) { create(:project).tap(&:delete) }

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

      it 'does not run RunPipelineScheduleWorker' do
        expect(RunPipelineScheduleWorker)
          .not_to receive(:perform_async).with(schedule.id, schedule.owner.id)

        subject
      end
    end
  end
end


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

module Ci
  ##
  # We call this service everytime we persist a CI/CD job.
  #
  # In most cases a job should already have a stage assigned,  but in cases it
  # doesn't have we need to either find existing one or create a brand new
  # stage.
  #
  class EnsureStageService < BaseService
    EnsureStageError = Class.new(StandardError)

    def execute(build)
      @build = build

      return if build.stage_id.present?
      return if build.invalid?

      ensure_stage.tap do |stage|
        build.stage_id = stage.id

        yield stage if block_given?
      end
    end

    private

    def ensure_stage(attempts: 2)
      find_stage || create_stage
    rescue ActiveRecord::RecordNotUnique
      retry if (attempts -= 1) > 0

      raise EnsureStageError, <<~EOS
        We failed to find or create a unique pipeline stage after 2 retries.
        This should never happen and is most likely the result of a bug in
        the database load balancing code.
      EOS
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def find_stage
      @build.pipeline.stages.find_by(name: @build.stage)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def create_stage
      Ci::Stage.create!(
        name: @build.stage,
        position: @build.stage_idx,
        pipeline: @build.pipeline,
        project: @build.project
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::EnsureStageService, '#execute', feature_category: :continuous_integration do
  let_it_be(:project) { create(:project) }
  let_it_be(:user) { create(:user) }

  let(:stage) { create(:ci_stage) }
  let(:job) { build(:ci_build) }

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

  context 'when build has a stage assigned' do
    it 'does not create a new stage' do
      job.assign_attributes(stage_id: stage.id)

      expect { service.execute(job) }.not_to change { Ci::Stage.count }
    end
  end

  context 'when build does not have a stage assigned' do
    it 'creates a new stage' do
      job.assign_attributes(stage_id: nil, stage: 'test')

      expect { service.execute(job) }.to change { Ci::Stage.count }.by(1)
    end
  end

  context 'when build is invalid' do
    it 'does not create a new stage' do
      job.assign_attributes(stage_id: nil, ref: nil)

      expect { service.execute(job) }.not_to change { Ci::Stage.count }
    end
  end

  context 'when new stage can not be created because of an exception' do
    before do
      allow(Ci::Stage).to receive(:create!)
        .and_raise(ActiveRecord::RecordNotUnique.new('Duplicates!'))
    end

    it 'retries up to two times' do
      job.assign_attributes(stage_id: nil)

      expect(service).to receive(:find_stage).twice

      expect { service.execute(job) }
        .to raise_error(Ci::EnsureStageService::EnsureStageError)
    end
  end
end


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

module Ci
  class ChangeVariableService < BaseContainerService
    def execute
      case params[:action]
      when :create
        container.variables.create(params[:variable_params])
      when :update
        variable.tap do |target_variable|
          target_variable.update(params[:variable_params].except(:key))
        end
      when :destroy
        variable.tap do |target_variable|
          target_variable.destroy
        end
      end
    end

    private

    def variable
      params[:variable] || find_variable
    end

    def find_variable
      identifier = params[:variable_params].slice(:id).presence || params[:variable_params].slice(:key)
      container.variables.find_by!(identifier) # rubocop:disable CodeReuse/ActiveRecord
    end
  end
end

::Ci::ChangeVariableService.prepend_mod_with('Ci::ChangeVariableService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::ChangeVariableService, feature_category: :secrets_management do
  let(:service) { described_class.new(container: group, current_user: user, params: params) }

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

  let(:group) { create(:group) }

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

    context 'when creating a variable' do
      let(:params) { { variable_params: { key: 'new_variable', value: 'variable_value' }, action: :create } }

      it 'persists a variable' do
        expect { execute }.to change(Ci::GroupVariable, :count).from(0).to(1)
      end
    end

    context 'when updating a variable' do
      let!(:variable) { create(:ci_group_variable, value: 'old_value') }
      let(:params) { { variable_params: { key: variable.key, value: 'new_value' }, action: :update } }

      before do
        group.variables << variable
      end

      it 'updates a variable' do
        expect { execute }.to change { variable.reload.value }.from('old_value').to('new_value')
      end

      context 'when the variable does not exist' do
        before do
          variable.destroy!
        end

        it 'raises a record not found error' do
          expect { execute }.to raise_error(::ActiveRecord::RecordNotFound)
        end
      end
    end

    context 'when destroying a variable' do
      let!(:variable) { create(:ci_group_variable) }
      let(:params) { { variable_params: { key: variable.key }, action: :destroy } }

      before do
        group.variables << variable
      end

      it 'destroys a variable' do
        expect { execute }.to change { Ci::GroupVariable.exists?(variable.id) }.from(true).to(false)
      end

      context 'when the variable does not exist' do
        before do
          variable.destroy!
        end

        it 'raises a record not found error' do
          expect { execute }.to raise_error(::ActiveRecord::RecordNotFound)
        end
      end
    end
  end
end


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

module Ci
  class DestroyPipelineService < BaseService
    def execute(pipeline)
      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline)

      Ci::ExpirePipelineCacheService.new.execute(pipeline, delete: true)

      # ensure cancellation happens sync so we accumulate compute minutes successfully
      # before deleting the pipeline.
      ::Ci::CancelPipelineService.new(
        pipeline: pipeline,
        current_user: current_user,
        cascade_to_children: true,
        execute_async: false).force_execute

      # The pipeline, the builds, job and pipeline artifacts all get destroyed here.
      # Ci::Pipeline#destroy triggers fast destroy on job_artifacts and
      # build_trace_chunks to remove the records and data stored in object storage.
      # ci_builds records are deleted using ON DELETE CASCADE from ci_pipelines
      #
      pipeline.reset.destroy!

      ServiceResponse.success(message: 'Pipeline not found')
    rescue ActiveRecord::RecordNotFound
      ServiceResponse.error(message: 'Pipeline not found')
    end
  end
end

Ci::DestroyPipelineService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::Ci::DestroyPipelineService, feature_category: :continuous_integration do
  let_it_be(:project) { create(:project, :repository) }

  let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }

  subject { described_class.new(project, user).execute(pipeline) }

  context 'user is owner' do
    let(:user) { project.first_owner }

    it 'destroys the pipeline' do
      subject

      expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
    end

    it 'clears the cache', :use_clean_rails_redis_caching do
      create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref)

      expect(project.pipeline_status.has_status?).to be_truthy

      subject

      # We need to reset lazy_latest_pipeline cache to simulate a new request
      BatchLoader::Executor.clear_current

      # Need to use find to avoid memoization
      expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey
    end

    it 'does not log an audit event' do
      expect { subject }.not_to change { AuditEvent.count }
    end

    context 'when the pipeline has jobs' do
      let!(:build) { create(:ci_build, project: project, pipeline: pipeline) }

      it 'destroys associated jobs' do
        subject

        expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
      end

      it 'destroys associated stages' do
        stages = pipeline.stages

        subject

        expect(stages).to all(raise_error(ActiveRecord::RecordNotFound))
      end

      context 'when job has artifacts' do
        let!(:artifact) { create(:ci_job_artifact, :archive, job: build) }

        it 'destroys associated artifacts' do
          subject

          expect { artifact.reload }.to raise_error(ActiveRecord::RecordNotFound)
        end

        it 'inserts deleted objects for object storage files' do
          expect { subject }.to change { Ci::DeletedObject.count }
        end
      end

      context 'when job has trace chunks' do
        let(:connection_params) { Gitlab.config.artifacts.object_store.connection.symbolize_keys }
        let(:connection) { ::Fog::Storage.new(connection_params) }

        before do
          stub_object_storage(connection_params: connection_params, remote_directory: 'artifacts')
          stub_artifacts_object_storage
        end

        let!(:trace_chunk) { create(:ci_build_trace_chunk, :fog_with_data, build: build) }

        it 'destroys associated trace chunks' do
          subject

          expect { trace_chunk.reload }.to raise_error(ActiveRecord::RecordNotFound)
        end

        it 'removes data from object store' do
          expect { subject }.to change { Ci::BuildTraceChunks::Fog.new.data(trace_chunk) }
        end
      end
    end

    context 'when pipeline is in cancelable state', :sidekiq_inline do
      let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
      let!(:child_pipeline) { create(:ci_pipeline, :running, child_of: pipeline) }
      let!(:child_build) { create(:ci_build, :running, pipeline: child_pipeline) }

      it 'cancels the pipelines sync' do
        cancel_pipeline_service = instance_double(::Ci::CancelPipelineService)
        expect(::Ci::CancelPipelineService)
          .to receive(:new)
          .with(pipeline: pipeline, current_user: user, cascade_to_children: true, execute_async: false)
          .and_return(cancel_pipeline_service)

        expect(cancel_pipeline_service).to receive(:force_execute)

        subject
      end
    end
  end

  context 'user is not owner' do
    let(:user) { create(:user) }

    it 'raises an exception' do
      expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
    end
  end
end


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

module Ci
  class AppendBuildTraceService
    Result = Struct.new(:status, :stream_size, keyword_init: true)
    TraceRangeError = Class.new(StandardError)

    attr_reader :build, :params

    def initialize(build, params)
      @build = build
      @params = params
    end

    def execute(body_data)
      # TODO:
      # it seems that `Content-Range` as formatted by runner is wrong,
      # the `byte_end` should point to final byte, but it points byte+1
      # that means that we have to calculate end of body,
      # as we cannot use `content_length[1]`
      # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275

      content_range = stream_range.split('-')
      body_start = content_range[0].to_i
      body_end = body_start + body_data.bytesize

      if first_debug_chunk?(body_start)
        # Update the build metadata prior to appending trace content
        build.enable_debug_trace!
      end

      if trace_size_exceeded?(body_end)
        build.drop(:trace_size_exceeded)

        return Result.new(status: 403)
      end

      stream_size = build.trace.append(body_data, body_start)

      unless stream_size == body_end
        log_range_error(stream_size, body_end)

        return Result.new(status: 416, stream_size: stream_size)
      end

      Result.new(status: 202, stream_size: stream_size)
    end

    private

    delegate :project, to: :build

    def first_debug_chunk?(body_start)
      body_start == 0 && debug_trace
    end

    def stream_range
      params.fetch(:content_range)
    end

    def debug_trace
      params.fetch(:debug_trace, false)
    end

    def log_range_error(stream_size, body_end)
      extra = {
        build_id: build.id,
        body_end: body_end,
        stream_size: stream_size,
        stream_class: stream_size.class,
        stream_range: stream_range
      }

      build.trace_chunks.last.try do |chunk|
        extra.merge!(
          chunk_index: chunk.chunk_index,
          chunk_store: chunk.data_store,
          chunks_count: build.trace_chunks.count
        )
      end

      ::Gitlab::ErrorTracking
        .log_exception(TraceRangeError.new, extra)
    end

    def trace_size_exceeded?(size)
      project.actual_limits.exceeded?(:ci_jobs_trace_size_limit, size / 1.megabyte)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::AppendBuildTraceService, feature_category: :continuous_integration do
  let_it_be(:project) { create(:project) }
  let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
  let_it_be_with_reload(:build) { create(:ci_build, :running, pipeline: pipeline) }

  before do
    stub_feature_flags(ci_enable_live_trace: true)
  end

  context 'build trace append is successful' do
    it 'returns a correct stream size and status code' do
      stream_size = 192.kilobytes
      body_data = 'x' * stream_size
      content_range = "0-#{stream_size}"

      result = described_class
        .new(build, content_range: content_range)
        .execute(body_data)

      expect(result.status).to eq 202
      expect(result.stream_size).to eq stream_size
      expect(build.trace_chunks.count).to eq 2
    end
  end

  context 'when could not correctly append to a trace' do
    it 'responds with content range violation and data stored' do
      allow(build).to receive_message_chain(:trace, :append) { 16 }

      result = described_class
        .new(build, content_range: '0-128')
        .execute('x' * 128)

      expect(result.status).to eq 416
      expect(result.stream_size).to eq 16
    end

    it 'logs exception if build has live trace' do
      build.trace.append('abcd', 0)

      expect(::Gitlab::ErrorTracking)
        .to receive(:log_exception)
        .with(anything, hash_including(chunk_index: 0, chunk_store: 'redis_trace_chunks'))

      result = described_class
        .new(build, content_range: '0-128')
        .execute('x' * 128)

      expect(result.status).to eq 416
      expect(result.stream_size).to eq 4
    end
  end

  context 'when the trace size is exceeded' do
    before do
      project.actual_limits.update!(ci_jobs_trace_size_limit: 1)
    end

    it 'returns 403 status code' do
      stream_size = 1.25.megabytes
      body_data = 'x' * stream_size
      content_range = "0-#{stream_size}"

      result = described_class
        .new(build, content_range: content_range)
        .execute(body_data)

      expect(result.status).to eq 403
      expect(result.stream_size).to be_nil
      expect(build.trace_chunks.count).to eq 0
      expect(build.reload).to be_failed
      expect(build.failure_reason).to eq 'trace_size_exceeded'
    end
  end

  context 'when debug_trace param is provided' do
    let(:metadata) { Ci::BuildMetadata.find_by(build_id: build) }
    let(:stream_size) { 192.kilobytes }
    let(:body_data) { 'x' * stream_size }
    let(:content_range) { "#{body_start}-#{stream_size}" }

    context 'when sending the first trace' do
      let(:body_start) { 0 }

      it 'updates build metadata debug_trace_enabled' do
        described_class
          .new(build, content_range: content_range, debug_trace: true)
          .execute(body_data)

        expect(metadata.debug_trace_enabled).to be(true)
      end
    end

    context 'when sending the second trace' do
      let(:body_start) { 1 }

      it 'does not update build metadata debug_trace_enabled', :aggregate_failures do
        query_recorder = ActiveRecord::QueryRecorder.new do
          described_class.new(build, content_range: content_range, debug_trace: true).execute(body_data)
        end

        expect(metadata.debug_trace_enabled).to be(false)
        expect(query_recorder.log).not_to include(/p_ci_builds_metadata/)
      end
    end
  end
end


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

module Ci
  class UpdateBuildQueueService
    InvalidQueueTransition = Class.new(StandardError)

    attr_reader :metrics

    def initialize(metrics = ::Gitlab::Ci::Queue::Metrics)
      @metrics = metrics
    end

    ##
    # Add a build to the pending builds queue
    #
    def push(build, transition)
      raise InvalidQueueTransition unless transition.to == 'pending'

      transition.within_transaction do
        result = build.create_queuing_entry!

        unless result.empty?
          metrics.increment_queue_operation(:build_queue_push)

          result.rows.dig(0, 0)
        end
      end
    end

    ##
    # Remove a build from the pending builds queue
    #
    def pop(build, transition)
      raise InvalidQueueTransition unless transition.from == 'pending'

      transition.within_transaction { remove!(build) }
    end

    ##
    # Force remove build from the queue, without checking a transition state
    #
    def remove!(build)
      removed = build.all_queuing_entries.delete_all

      if removed > 0
        metrics.increment_queue_operation(:build_queue_pop)

        build.id
      end
    end

    ##
    # Add shared runner build tracking entry (used for queuing).
    #
    def track(build, transition)
      return unless build.shared_runner_build?

      raise InvalidQueueTransition unless transition.to == 'running'

      transition.within_transaction do
        result = ::Ci::RunningBuild.upsert_shared_runner_build!(build)

        unless result.empty?
          metrics.increment_queue_operation(:shared_runner_build_new)

          result.rows.dig(0, 0)
        end
      end
    end

    ##
    # Remove a runtime build tracking entry for a shared runner build (used for
    # queuing).
    #
    def untrack(build, transition)
      return unless build.shared_runner_build?

      raise InvalidQueueTransition unless transition.from == 'running'

      transition.within_transaction do
        removed = build.all_runtime_metadata.delete_all

        if removed > 0
          metrics.increment_queue_operation(:shared_runner_build_done)

          build.id
        end
      end
    end

    ##
    # Unblock runner associated with given project / build
    #
    def tick(build)
      tick_for(build, build.project.all_available_runners)
    end

    private

    def tick_for(build, runners)
      runners = runners.with_recent_runner_queue
      runners = runners.with_tags

      metrics.observe_active_runners(-> { runners.to_a.size })

      runners.each do |runner|
        metrics.increment_runner_tick(runner)

        runner.pick_build!(build)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::UpdateBuildQueueService, feature_category: :continuous_integration do
  let(:project) { create(:project, :repository) }
  let(:pipeline) { create(:ci_pipeline, project: project) }
  let(:build) { create(:ci_build, pipeline: pipeline) }

  describe 'pending builds queue push / pop' do
    describe '#push' do
      let(:transition) { double('transition') }

      before do
        allow(transition).to receive(:to).and_return('pending')
        allow(transition).to receive(:within_transaction).and_yield
      end

      context 'when pending build can be created' do
        it 'creates a new pending build in transaction' do
          queued = subject.push(build, transition)

          expect(queued).to eq build.id
        end

        it 'increments queue push metric' do
          metrics = spy('metrics')

          described_class.new(metrics).push(build, transition)

          expect(metrics)
            .to have_received(:increment_queue_operation)
            .with(:build_queue_push)
        end
      end

      context 'when invalid transition is detected' do
        it 'raises an error' do
          allow(transition).to receive(:to).and_return('created')

          expect { subject.push(build, transition) }
            .to raise_error(described_class::InvalidQueueTransition)
        end
      end

      context 'when duplicate entry exists' do
        before do
          create(:ci_pending_build, build: build, project: build.project)
        end

        it 'does nothing and returns build id' do
          queued = subject.push(build, transition)

          expect(queued).to eq build.id
        end
      end
    end

    describe '#pop' do
      let(:transition) { double('transition') }

      before do
        allow(transition).to receive(:from).and_return('pending')
        allow(transition).to receive(:within_transaction).and_yield
      end

      context 'when pending build exists' do
        before do
          create(:ci_pending_build, build: build, project: build.project)
        end

        it 'removes pending build in a transaction' do
          dequeued = subject.pop(build, transition)

          expect(dequeued).to eq build.id
        end

        it 'increments queue pop metric' do
          metrics = spy('metrics')

          described_class.new(metrics).pop(build, transition)

          expect(metrics)
            .to have_received(:increment_queue_operation)
            .with(:build_queue_pop)
        end
      end

      context 'when pending build does not exist' do
        it 'does nothing if there is no pending build to remove' do
          dequeued = subject.pop(build, transition)

          expect(dequeued).to be_nil
        end
      end

      context 'when invalid transition is detected' do
        it 'raises an error' do
          allow(transition).to receive(:from).and_return('created')

          expect { subject.pop(build, transition) }
            .to raise_error(described_class::InvalidQueueTransition)
        end
      end
    end

    describe '#remove!' do
      context 'when pending build exists' do
        before do
          create(:ci_pending_build, build: build, project: build.project)
        end

        it 'removes pending build in a transaction' do
          dequeued = subject.remove!(build)

          expect(dequeued).to eq build.id
        end
      end

      context 'when pending build does not exist' do
        it 'does nothing if there is no pending build to remove' do
          dequeued = subject.remove!(build)

          expect(dequeued).to be_nil
        end
      end
    end
  end

  describe 'shared runner builds tracking' do
    let(:runner) { create(:ci_runner, :instance_type) }
    let(:build) { create(:ci_build, runner: runner, pipeline: pipeline) }

    describe '#track' do
      let(:transition) { double('transition') }

      before do
        allow(transition).to receive(:to).and_return('running')
        allow(transition).to receive(:within_transaction).and_yield
      end

      context 'when a shared runner build can be tracked' do
        it 'creates a new shared runner build tracking entry' do
          build_id = subject.track(build, transition)

          expect(build_id).to eq build.id
        end

        it 'increments new shared runner build metric' do
          metrics = spy('metrics')

          described_class.new(metrics).track(build, transition)

          expect(metrics)
            .to have_received(:increment_queue_operation)
            .with(:shared_runner_build_new)
        end
      end

      context 'when invalid transition is detected' do
        it 'raises an error' do
          allow(transition).to receive(:to).and_return('pending')

          expect { subject.track(build, transition) }
            .to raise_error(described_class::InvalidQueueTransition)
        end
      end

      context 'when duplicate entry exists' do
        before do
          create(:ci_running_build, build: build, project: project, runner: runner)
        end

        it 'does nothing and returns build id' do
          build_id = subject.track(build, transition)

          expect(build_id).to eq build.id
        end
      end
    end

    describe '#untrack' do
      let(:transition) { double('transition') }

      before do
        allow(transition).to receive(:from).and_return('running')
        allow(transition).to receive(:within_transaction).and_yield
      end

      context 'when shared runner build tracking entry exists' do
        before do
          create(:ci_running_build, build: build, project: project, runner: runner)
        end

        it 'removes shared runner build' do
          build_id = subject.untrack(build, transition)

          expect(build_id).to eq build.id
        end

        it 'increments shared runner build done metric' do
          metrics = spy('metrics')

          described_class.new(metrics).untrack(build, transition)

          expect(metrics)
            .to have_received(:increment_queue_operation)
            .with(:shared_runner_build_done)
        end
      end

      context 'when tracking entry does not exist' do
        it 'does nothing if there is no tracking entry to remove' do
          build_id = subject.untrack(build, transition)

          expect(build_id).to be_nil
        end
      end

      context 'when invalid transition is detected' do
        it 'raises an error' do
          allow(transition).to receive(:from).and_return('pending')

          expect { subject.untrack(build, transition) }
            .to raise_error(described_class::InvalidQueueTransition)
        end
      end
    end
  end

  describe '#tick' do
    shared_examples 'refreshes runner' do
      it 'ticks runner queue value' do
        expect { subject.tick(build) }.to change { runner.ensure_runner_queue_value }
      end
    end

    shared_examples 'does not refresh runner' do
      it 'ticks runner queue value' do
        expect { subject.tick(build) }.not_to change { runner.ensure_runner_queue_value }
      end
    end

    shared_examples 'matching build' do
      context 'when there is a online runner that can pick build' do
        before do
          runner.update!(contacted_at: 30.minutes.ago)
        end

        it_behaves_like 'refreshes runner'

        it 'avoids running redundant queries' do
          expect(Ci::Runner).not_to receive(:owned_or_instance_wide)

          subject.tick(build)
        end
      end
    end

    shared_examples 'mismatching tags' do
      context 'when there is no runner that can pick build due to tag mismatch' do
        before do
          build.tag_list = [:docker]
        end

        it_behaves_like 'does not refresh runner'
      end
    end

    shared_examples 'recent runner queue' do
      context 'when there is runner with expired cache' do
        before do
          runner.update!(contacted_at: Ci::Runner.recent_queue_deadline)
        end

        it_behaves_like 'does not refresh runner'
      end
    end

    context 'when updating project runners' do
      let(:runner) { create(:ci_runner, :project, projects: [project]) }

      it_behaves_like 'matching build'
      it_behaves_like 'mismatching tags'
      it_behaves_like 'recent runner queue'

      context 'when the runner is assigned to another project' do
        let(:another_project) { create(:project) }
        let(:runner) { create(:ci_runner, :project, projects: [another_project]) }

        it_behaves_like 'does not refresh runner'
      end
    end

    context 'when updating shared runners' do
      let(:runner) { create(:ci_runner, :instance) }

      it_behaves_like 'matching build'
      it_behaves_like 'mismatching tags'
      it_behaves_like 'recent runner queue'

      context 'when there is no runner that can pick build due to being disabled on project' do
        before do
          build.project.shared_runners_enabled = false
        end

        it_behaves_like 'does not refresh runner'
      end
    end

    context 'when updating group runners' do
      let(:group) { create(:group) }
      let(:project) { create(:project, group: group) }
      let(:runner) { create(:ci_runner, :group, groups: [group]) }

      it_behaves_like 'matching build'
      it_behaves_like 'mismatching tags'
      it_behaves_like 'recent runner queue'

      context 'when there is no runner that can pick build due to being disabled on project' do
        before do
          build.project.group_runners_enabled = false
        end

        it_behaves_like 'does not refresh runner'
      end
    end

    context 'avoids N+1 queries', :request_store do
      let!(:build) { create(:ci_build, pipeline: pipeline, tag_list: %w[a b]) }
      let!(:project_runner) { create(:ci_runner, :project, :online, projects: [project], tag_list: %w[a b c]) }

      it 'does execute the same amount of queries regardless of number of runners' do
        control_count = ActiveRecord::QueryRecorder.new { subject.tick(build) }.count

        create_list(:ci_runner, 10, :project, :online, projects: [project], tag_list: %w[b c d])

        expect { subject.tick(build) }.not_to exceed_all_query_limit(control_count)
      end
    end
  end
end


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

module Ci
  # TODO: when using this class with exposed artifacts we see that there are
  # 2 responsibilities:
  # 1. reactive caching interface (same in all cases)
  # 2. data generator (report comparison in most of the case but not always)
  # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
  class CompareReportsBaseService < ::BaseService
    def execute(base_pipeline, head_pipeline)
      return parsing_payload(base_pipeline, head_pipeline) if base_pipeline&.running?

      base_report = get_report(base_pipeline)
      head_report = get_report(head_pipeline)
      comparer = build_comparer(base_report, head_report)

      {
        status: :parsed,
        key: key(base_pipeline, head_pipeline),
        data: serializer_class
          .new(**serializer_params)
          .represent(comparer).as_json
      }
    rescue Gitlab::Ci::Parsers::ParserError => e
      {
        status: :error,
        key: key(base_pipeline, head_pipeline),
        status_reason: e.message
      }
    end

    def latest?(base_pipeline, head_pipeline, data)
      data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
    end

    protected

    def parsing_payload(base_pipeline, head_pipeline)
      {
        status: :parsing,
        key: key(base_pipeline, head_pipeline)
      }
    end

    def build_comparer(base_report, head_report)
      comparer_class.new(base_report, head_report)
    end

    private

    def key(base_pipeline, head_pipeline)
      [
        base_pipeline&.id, base_pipeline&.updated_at,
        head_pipeline&.id, head_pipeline&.updated_at
      ]
    end

    def comparer_class
      raise NotImplementedError
    end

    def serializer_class
      raise NotImplementedError
    end

    def serializer_params
      { project: project, current_user: current_user }
    end

    def get_report(pipeline)
      raise NotImplementedError
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::CompareReportsBaseService, feature_category: :continuous_integration do
  let(:service) { described_class.new(project) }
  let(:project) { create(:project, :repository) }

  let!(:base_pipeline) { nil }
  let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
  let!(:key) { service.send(:key, base_pipeline, head_pipeline) }

  describe '#latest?' do
    subject { service.latest?(base_pipeline, head_pipeline, data) }

    context 'when cache key is latest' do
      let(:data) { { key: key } }

      it { is_expected.to be_truthy }
    end

    context 'when cache key is outdated' do
      before do
        head_pipeline.update_column(:updated_at, 10.minutes.ago)
      end

      let(:data) { { key: key } }

      it { is_expected.to be_falsy }
    end

    context 'when cache key is empty' do
      let(:data) { { key: nil } }

      it { is_expected.to be_falsy }
    end
  end

  describe '#execute' do
    context 'when base_pipeline is running' do
      let!(:base_pipeline) { create(:ci_pipeline, :running, project: project) }

      subject { service.execute(base_pipeline, head_pipeline) }

      it { is_expected.to eq(status: :parsing, key: key) }
    end
  end
end


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

module Ci
  class CreateCommitStatusService < BaseService
    include ::Gitlab::ExclusiveLeaseHelpers
    include ::Gitlab::Utils::StrongMemoize
    include ::Services::ReturnServiceResponses

    delegate :sha, to: :commit

    def execute(optional_commit_status_params:)
      in_lock(pipeline_lock_key, **pipeline_lock_params) do
        @optional_commit_status_params = optional_commit_status_params
        unsafe_execute
      end
    end

    private

    attr_reader :pipeline, :stage, :commit_status, :optional_commit_status_params

    def unsafe_execute
      return not_found('Commit') if commit.blank?
      return bad_request('State is required') if params[:state].blank?
      return not_found('References for commit') if ref.blank?

      @pipeline = first_matching_pipeline || create_pipeline
      return forbidden unless ::Ability.allowed?(current_user, :update_pipeline, pipeline)

      @stage = find_or_create_external_stage
      @commit_status = find_or_build_external_commit_status

      return bad_request(commit_status.errors.messages) if commit_status.invalid?

      response = add_or_update_external_job

      return bad_request(response.message) if response.error?

      update_merge_request_head_pipeline
      response
    end

    def ref
      params[:ref] || first_matching_pipeline&.ref ||
        repository.branch_names_contains(sha).first
    end
    strong_memoize_attr :ref

    def commit
      project.commit(params[:sha])
    end
    strong_memoize_attr :commit

    def first_matching_pipeline
      pipelines = project.ci_pipelines.newest_first(sha: sha)
      pipelines = pipelines.for_ref(params[:ref]) if params[:ref]
      pipelines = pipelines.id_in(params[:pipeline_id]) if params[:pipeline_id]
      pipelines.first
    end
    strong_memoize_attr :first_matching_pipeline

    def name
      params[:name] || params[:context] || 'default'
    end

    def create_pipeline
      project.ci_pipelines.build(
        source: :external,
        sha: sha,
        ref: ref,
        user: current_user,
        protected: project.protected_for?(ref)
      ).tap do |new_pipeline|
        new_pipeline.ensure_project_iid!
        new_pipeline.save!
      end
    end

    def find_or_create_external_stage
      pipeline.stages.safe_find_or_create_by!(name: 'external') do |stage| # rubocop:disable Performance/ActiveRecordSubtransactionMethods
        stage.position = ::GenericCommitStatus::EXTERNAL_STAGE_IDX
        stage.project = project
      end
    end

    def find_or_build_external_commit_status
      ::GenericCommitStatus.running_or_pending.find_or_initialize_by( # rubocop:disable CodeReuse/ActiveRecord
        project: project,
        pipeline: pipeline,
        name: name,
        ref: ref,
        user: current_user,
        protected: project.protected_for?(ref),
        ci_stage: stage,
        stage_idx: stage.position,
        stage: 'external'
      ).tap do |new_commit_status|
        new_commit_status.assign_attributes(optional_commit_status_params)
      end
    end

    def add_or_update_external_job
      ::Ci::Pipelines::AddJobService.new(pipeline).execute!(commit_status) do |job|
        apply_job_state!(job)
      end
    end

    def update_merge_request_head_pipeline
      return unless pipeline.latest?

      ::MergeRequest
        .from_project(project).from_source_branches(ref)
        .update_all(head_pipeline_id: pipeline.id)
    end

    def apply_job_state!(job)
      case params[:state]
      when 'pending'
        job.enqueue!
      when 'running'
        job.enqueue
        job.run!
      when 'success'
        job.success!
      when 'failed'
        job.drop!(:api_failure)
      when 'canceled'
        job.cancel!
      else
        raise('invalid state')
      end
    end

    def pipeline_lock_key
      "api:commit_statuses:project:#{project.id}:sha:#{params[:sha]}"
    end

    def pipeline_lock_params
      {
        ttl: 5.seconds,
        sleep_sec: 0.1.seconds,
        retries: 20
      }
    end

    def not_found(message)
      error("404 #{message} Not Found", :not_found)
    end

    def bad_request(message)
      error(message, :bad_request)
    end

    def forbidden
      error("403 Forbidden", :forbidden)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature_category: :continuous_integration do
  using RSpec::Parameterized::TableSyntax

  subject(:response) { execute_service(params) }

  let_it_be_with_refind(:project) { create(:project, :repository) }
  let_it_be(:commit) { project.repository.commit }
  let_it_be(:guest) { create_user(:guest) }
  let_it_be(:reporter) { create_user(:reporter) }
  let_it_be(:developer) { create_user(:developer) }

  let(:user) { developer }
  let(:sha) { commit.id }
  let(:params) { { state: 'pending' } }
  let(:job) { response.payload[:job] }

  %w[pending running success failed canceled].each do |status|
    context "for #{status}" do
      let(:params) { { state: status } }

      context 'when pipeline for sha does not exists' do
        it 'creates commit status and sets pipeline iid' do
          expect(response).to be_success
          expect(job.sha).to eq(commit.id)
          expect(job.status).to eq(status)
          expect(job.name).to eq('default')
          expect(job.ref).not_to be_empty
          expect(job.target_url).to be_nil
          expect(job.description).to be_nil
          expect(job.pipeline_id).not_to be_nil

          expect(CommitStatus.find(job.id)).to be_api_failure if status == 'failed'

          expect(::Ci::Pipeline.last.iid).not_to be_nil
        end
      end
    end
  end

  context 'when status transitions from pending' do
    before do
      execute_service(state: 'pending')
    end

    %w[running success failed canceled].each do |status|
      context "for #{status}" do
        let(:params) { { state: status } }

        it "changes to #{status}" do
          expect { response }
            .to not_change { ::Ci::Pipeline.count }.from(1)
            .and not_change { ::Ci::Stage.count }.from(1)
            .and not_change { ::CommitStatus.count }.from(1)

          expect(response).to be_success
          expect(job.status).to eq(status)
        end
      end
    end

    context 'for invalid transition' do
      let(:params) { { state: 'pending' } }

      it 'returns bad request and error message' do
        expect { response }
          .to not_change { ::Ci::Pipeline.count }.from(1)
          .and not_change { ::Ci::Stage.count }.from(1)
          .and not_change { ::CommitStatus.count }.from(1)

        expect(response).to be_error
        expect(response.http_status).to eq(:bad_request)
        expect(response.message).to eq(
          "Cannot transition status via :enqueue from :pending (Reason(s): Status cannot transition via \"enqueue\")"
        )
      end
    end
  end

  context 'with all optional parameters' do
    context 'when creating a commit status' do
      let(:params) do
        {
          sha: sha,
          state: 'success',
          context: 'coverage',
          ref: 'master',
          description: 'test',
          coverage: 80.0,
          target_url: 'http://gitlab.com/status'
        }
      end

      it 'creates commit status' do
        expect { response }
          .to change { ::Ci::Pipeline.count }.by(1)
          .and change { ::Ci::Stage.count }.by(1)
          .and change { ::CommitStatus.count }.by(1)

        expect(response).to be_success
        expect(job.sha).to eq(commit.id)
        expect(job.status).to eq('success')
        expect(job.name).to eq('coverage')
        expect(job.ref).to eq('master')
        expect(job.coverage).to eq(80.0)
        expect(job.description).to eq('test')
        expect(job.target_url).to eq('http://gitlab.com/status')
      end

      context 'when merge request exists for given branch' do
        let!(:merge_request) do
          create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop')
        end

        it 'sets head pipeline' do
          expect { response }
            .to change { ::Ci::Pipeline.count }.by(1)
            .and change { ::Ci::Stage.count }.by(1)
            .and change { ::CommitStatus.count }.by(1)

          expect(response).to be_success
          expect(merge_request.reload.head_pipeline).not_to be_nil
        end
      end
    end

    context 'when updating a commit status' do
      let(:parameters) do
        {
          state: 'success',
          name: 'coverage',
          ref: 'master'
        }
      end

      let(:updatable_optional_attributes) do
        {
          description: 'new description',
          coverage: 90.0
        }
      end

      let(:params) { parameters.merge(updatable_optional_attributes) }

      # creating the initial commit status
      before do
        execute_service(
          sha: sha,
          state: 'running',
          context: 'coverage',
          ref: 'master',
          description: 'coverage test',
          coverage: 10.0,
          target_url: 'http://gitlab.com/status'
        )
      end

      it 'updates a commit status' do
        expect { response }
          .to not_change { ::Ci::Pipeline.count }.from(1)
          .and not_change { ::Ci::Stage.count }.from(1)
          .and not_change { ::CommitStatus.count }.from(1)

        expect(response).to be_success
        expect(job.sha).to eq(commit.id)
        expect(job.status).to eq('success')
        expect(job.name).to eq('coverage')
        expect(job.ref).to eq('master')
        expect(job.coverage).to eq(90.0)
        expect(job.description).to eq('new description')
        expect(job.target_url).to eq('http://gitlab.com/status')
      end

      context 'when the `state` parameter is sent the same' do
        let(:parameters) do
          {
            sha: sha,
            state: 'running',
            name: 'coverage',
            ref: 'master'
          }
        end

        it 'does not update the commit status' do
          expect { response }
            .to not_change { ::Ci::Pipeline.count }.from(1)
            .and not_change { ::Ci::Stage.count }.from(1)
            .and not_change { ::CommitStatus.count }.from(1)

          expect(response).to be_error
          expect(response.http_status).to eq(:bad_request)
          expect(response.message).to eq(
            "Cannot transition status via :run from :running (Reason(s): Status cannot transition via \"run\")"
          )

          commit_status = project.commit_statuses.find_by!(name: 'coverage')

          expect(commit_status.description).to eq('coverage test')
          expect(commit_status.coverage).to eq(10.0)
        end
      end
    end

    context 'when a pipeline id is specified' do
      let!(:first_pipeline) do
        project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p|
          p.ensure_project_iid! # Necessary to avoid cross-database modification error
          p.save!
        end
      end

      let!(:other_pipeline) do
        project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p|
          p.ensure_project_iid! # Necessary to avoid cross-database modification error
          p.save!
        end
      end

      let(:params) do
        {
          sha: sha,
          pipeline_id: other_pipeline.id,
          state: 'success',
          ref: 'master'
        }
      end

      it 'update the correct pipeline', :sidekiq_might_not_need_inline do
        expect { response }
          .to not_change { ::Ci::Pipeline.count }.from(2)
          .and change { ::Ci::Stage.count }.by(1)
          .and change { ::CommitStatus.count }.by(1)

        expect(first_pipeline.reload.status).to eq('created')
        expect(other_pipeline.reload.status).to eq('success')
      end
    end
  end

  context 'when retrying a commit status' do
    subject(:response) do
      execute_service(state: 'failed', name: 'test', ref: 'master')

      execute_service(state: 'success', name: 'test', ref: 'master')
    end

    it 'correctly posts a new commit status' do
      expect { response }
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and change { ::CommitStatus.count }.by(2)

      expect(response).to be_success
      expect(job.sha).to eq(commit.id)
      expect(job.status).to eq('success')
    end

    it 'retries the commit status', :sidekiq_might_not_need_inline do
      response

      expect(CommitStatus.count).to eq 2
      expect(CommitStatus.first).to be_retried
      expect(CommitStatus.last.pipeline).to be_success
    end
  end

  context 'when status is invalid' do
    let(:params) { { state: 'invalid' } }

    it 'does not create commit status' do
      expect { response }
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and not_change { ::CommitStatus.count }.from(0)

      expect(response).to be_error
      expect(response.http_status).to eq(:bad_request)
      expect(response.message).to eq('invalid state')
    end
  end

  context 'when request without a state made' do
    let(:params) { {} }

    it 'does not create commit status' do
      expect { response }
        .to not_change { ::Ci::Pipeline.count }.from(0)
        .and not_change { ::Ci::Stage.count }.from(0)
        .and not_change { ::CommitStatus.count }.from(0)

      expect(response).to be_error
      expect(response.http_status).to eq(:bad_request)
      expect(response.message).to eq('State is required')
    end
  end

  context 'when updating a protected ref' do
    let(:params) { { state: 'running', ref: 'master' } }

    before do
      create(:protected_branch, project: project, name: 'master')
    end

    context 'with user as developer' do
      let(:user) { developer }

      it 'does not create commit status' do
        expect { response }
          .to change { ::Ci::Pipeline.count }.by(1)
          .and not_change { ::Ci::Stage.count }.from(0)
          .and not_change { ::CommitStatus.count }.from(0)

        expect(response).to be_error
        expect(response.http_status).to eq(:forbidden)
        expect(response.message).to eq('403 Forbidden')
      end
    end

    context 'with user as maintainer' do
      let(:user) { create_user(:maintainer) }

      it 'creates commit status' do
        expect { response }
          .to change { ::Ci::Pipeline.count }.by(1)
          .and change { ::Ci::Stage.count }.by(1)
          .and change { ::CommitStatus.count }.by(1)

        expect(response).to be_success
      end
    end
  end

  context 'when commit SHA is invalid' do
    let(:sha) { 'invalid_sha' }
    let(:params) { { state: 'running', sha: sha } }

    it 'returns not found error' do
      expect { response }
        .to not_change { ::Ci::Pipeline.count }.from(0)
        .and not_change { ::Ci::Stage.count }.from(0)
        .and not_change { ::CommitStatus.count }.from(0)

      expect(response).to be_error
      expect(response.http_status).to eq(:not_found)
      expect(response.message).to eq('404 Commit Not Found')
    end
  end

  context 'when target URL is an invalid address' do
    let(:params) { { state: 'pending', target_url: 'invalid url' } }

    it 'responds with bad request status and validation errors' do
      expect { response }
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and not_change { ::CommitStatus.count }.from(0)

      expect(response).to be_error
      expect(response.http_status).to eq(:bad_request)
      expect(response.message[:target_url])
        .to include 'is blocked: Only allowed schemes are http, https'
    end
  end

  context 'when target URL is an unsupported scheme' do
    let(:params) { { state: 'pending', target_url: 'git://example.com' } }

    it 'responds with bad request status and validation errors' do
      expect { response }
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and not_change { ::CommitStatus.count }.from(0)

      expect(response).to be_error
      expect(response.http_status).to eq(:bad_request)
      expect(response.message[:target_url])
          .to include 'is blocked: Only allowed schemes are http, https'
    end
  end

  context 'when trying to update a status of a different type' do
    let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') }
    let!(:ci_build) { create(:ci_build, pipeline: pipeline, name: 'test-job') }
    let(:params) { { state: 'pending', name: 'test-job' } }

    before do
      execute_service(params)
    end

    it 'responds with bad request status and validation errors' do
      expect { response }
        .to not_change { ::Ci::Pipeline.count }.from(1)
        .and not_change { ::Ci::Stage.count }.from(2)
        .and not_change { ::CommitStatus.count }.from(1)

      expect(response).to be_error
      expect(response.http_status).to eq(:bad_request)
      expect(response.message[:name])
          .to include 'has already been taken'
    end
  end

  context 'with partitions', :ci_partitionable do
    let(:current_partition_id) { ci_testing_partition_id }
    let(:params) { { state: 'running' } }

    before do
      allow(Ci::Pipeline)
        .to receive(:current_partition_value) { current_partition_id }
    end

    it 'creates records in the current partition' do
      expect { response }
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and change { ::CommitStatus.count }.by(1)

      expect(response).to be_success

      status = CommitStatus.find(job.id)

      expect(status.partition_id).to eq(current_partition_id)
      expect(status.pipeline.partition_id).to eq(current_partition_id)
    end
  end

  context 'for race condition' do
    let(:licenses_snyk_params) { { state: 'running', name: 'licenses', description: 'testing' } }
    let(:security_snyk_params) { { state: 'running', name: 'security', description: 'testing' } }
    let(:snyk_params_list) { [licenses_snyk_params, security_snyk_params] }

    it 'creates one pipeline and two jobs (one for licenses, one for security)' do
      expect do
        snyk_params_list.map do |snyk_params|
          Thread.new do
            response = execute_service(snyk_params)
            expect(response).to be_success
          end
        end.each(&:join)
      end
        .to change { ::Ci::Pipeline.count }.by(1)
        .and change { ::Ci::Stage.count }.by(1)
        .and change { ::CommitStatus.count }.by(2)
    end
  end

  def create_user(access_level_trait)
    user = create(:user)
    create(:project_member, access_level_trait, user: user, project: project)
    user
  end

  def execute_service(params = self.params)
    described_class
      .new(project, user, params)
      .execute(optional_commit_status_params: params.slice(*%i[target_url description coverage]))
  end
end


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

module Ci
  class CompareCodequalityReportsService < CompareReportsBaseService
    def comparer_class
      Gitlab::Ci::Reports::CodequalityReportsComparer
    end

    def serializer_class
      CodequalityReportsComparerSerializer
    end

    def get_report(pipeline)
      pipeline&.codequality_reports
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::CompareCodequalityReportsService, feature_category: :continuous_integration do
  let(:service) { described_class.new(project) }
  let(:project) { create(:project, :repository) }

  describe '#execute' do
    subject { service.execute(base_pipeline, head_pipeline) }

    context 'when head pipeline has a codequality report' do
      let(:base_pipeline) { nil }
      let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }

      it 'returns status and data' do
        expect(subject[:status]).to eq(:parsed)
        expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
      end
    end

    context 'when base and head pipelines have codequality reports' do
      let(:base_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
      let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }

      it 'returns status and data' do
        expect(subject[:status]).to eq(:parsed)
        expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
      end
    end
  end
end


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

module Ci
  class DeleteObjectsService
    TransactionInProgressError = Class.new(StandardError)
    TRANSACTION_MESSAGE = "can't perform network calls inside a database transaction"
    BATCH_SIZE = 100
    RETRY_IN = 10.minutes

    def execute
      objects = load_next_batch
      destroy_everything(objects)
    end

    def remaining_batches_count(max_batch_count:)
      Ci::DeletedObject
        .ready_for_destruction(max_batch_count * BATCH_SIZE)
        .size
        .fdiv(BATCH_SIZE)
        .ceil
    end

    private

    # rubocop: disable CodeReuse/ActiveRecord
    def load_next_batch
      # `find_by_sql` performs a write in this case and we need to wrap it in
      # a transaction to stick to the primary database.
      Ci::DeletedObject.transaction do
        Ci::DeletedObject.find_by_sql([next_batch_sql, new_pick_up_at: RETRY_IN.from_now])
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def next_batch_sql
      <<~SQL.squish
      UPDATE "ci_deleted_objects"
        SET "pick_up_at" = :new_pick_up_at
        WHERE "ci_deleted_objects"."id" IN (#{locked_object_ids_sql})
        RETURNING *
      SQL
    end

    def locked_object_ids_sql
      Ci::DeletedObject.lock_for_destruction(BATCH_SIZE).to_sql
    end

    def destroy_everything(objects)
      raise TransactionInProgressError, TRANSACTION_MESSAGE if transaction_open?
      return unless objects.any?

      deleted = objects.select(&:delete_file_from_storage)
      Ci::DeletedObject.id_in(deleted.map(&:id)).delete_all
    end

    def transaction_open?
      Ci::DeletedObject.connection.transaction_open?
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::DeleteObjectsService, :aggregate_failures, feature_category: :continuous_integration do
  let(:service) { described_class.new }
  let(:artifact) { create(:ci_job_artifact, :archive) }
  let(:data) { [artifact] }

  describe '#execute' do
    before do
      Ci::DeletedObject.bulk_import(data)
      # We disable the check because the specs are wrapped in a transaction
      allow(service).to receive(:transaction_open?).and_return(false)
    end

    subject(:execute) { service.execute }

    it 'deletes records' do
      expect { execute }.to change { Ci::DeletedObject.count }.by(-1)
    end

    it 'deletes files' do
      expect { execute }.to change { artifact.file.exists? }
    end

    context 'when trying to execute without records' do
      let(:data) { [] }

      it 'does not change the number of objects' do
        expect { execute }.not_to change { Ci::DeletedObject.count }
      end
    end

    context 'when trying to remove the same file multiple times' do
      let(:objects) { Ci::DeletedObject.all.to_a }

      before do
        expect(service).to receive(:load_next_batch).twice.and_return(objects)
      end

      it 'executes successfully' do
        2.times { expect(service.execute).to be_truthy }
      end
    end

    context 'with artifacts both ready and not ready for deletion' do
      let(:data) { [] }

      let!(:past_ready) { create(:ci_deleted_object, pick_up_at: 2.days.ago) }
      let!(:ready) { create(:ci_deleted_object, pick_up_at: 1.day.ago) }

      it 'skips records with pick_up_at in the future' do
        not_ready = create(:ci_deleted_object, pick_up_at: 1.day.from_now)

        expect { execute }.to change { Ci::DeletedObject.count }.from(3).to(1)
        expect(not_ready.reload.present?).to be_truthy
      end

      it 'limits the number of records removed' do
        stub_const("#{described_class}::BATCH_SIZE", 1)

        expect { execute }.to change { Ci::DeletedObject.count }.by(-1)
      end

      it 'removes records in order' do
        stub_const("#{described_class}::BATCH_SIZE", 1)

        execute

        expect { past_ready.reload }.to raise_error(ActiveRecord::RecordNotFound)
        expect(ready.reload.present?).to be_truthy
      end

      it 'updates pick_up_at timestamp' do
        allow(service).to receive(:destroy_everything)

        execute

        expect(past_ready.reload.pick_up_at).to be_like_time(10.minutes.from_now)
      end

      it 'does not delete objects for which file deletion has failed' do
        expect(past_ready)
          .to receive(:delete_file_from_storage)
          .and_return(false)

        expect(service)
          .to receive(:load_next_batch)
          .and_return([past_ready, ready])

        expect { execute }.to change { Ci::DeletedObject.count }.from(2).to(1)
        expect(past_ready.reload.present?).to be_truthy
      end
    end

    context 'with an open database transaction' do
      it 'raises an exception and does not remove records' do
        expect(service).to receive(:transaction_open?).and_return(true)

        expect { execute }
          .to raise_error(Ci::DeleteObjectsService::TransactionInProgressError)
          .and change { Ci::DeletedObject.count }.by(0)
      end
    end
  end

  describe '#remaining_batches_count' do
    subject { service.remaining_batches_count(max_batch_count: 3) }

    context 'when there is less than one batch size' do
      before do
        Ci::DeletedObject.bulk_import(data)
      end

      it { is_expected.to eq(1) }
    end

    context 'when there is more than one batch size' do
      before do
        objects_scope = double

        expect(Ci::DeletedObject)
          .to receive(:ready_for_destruction)
          .and_return(objects_scope)

        expect(objects_scope).to receive(:size).and_return(110)
      end

      it { is_expected.to eq(2) }
    end
  end
end


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

module Ci
  class DropPipelineService
    PRELOADED_RELATIONS = [:project, :pipeline, :metadata, :deployment, :taggings].freeze

    # execute service asynchronously for each cancelable pipeline
    def execute_async_for_all(pipelines, failure_reason, context_user)
      pipelines.cancelable.select(:id).find_in_batches do |pipelines_batch|
        Ci::DropPipelineWorker.bulk_perform_async_with_contexts(
          pipelines_batch,
          arguments_proc: -> (pipeline) { [pipeline.id, failure_reason] },
          context_proc: -> (_) { { user: context_user } }
        )
      end
    end

    def execute(pipeline, failure_reason, retries: 3)
      Gitlab::OptimisticLocking.retry_lock(pipeline.cancelable_statuses, retries, name: 'ci_pipeline_drop_running') do |cancelables|
        cancelables.find_in_batches do |batch|
          preload_associations_for_drop(batch)

          batch.each do |job|
            job.drop(failure_reason)
          end
        end
      end
    end

    private

    def preload_associations_for_drop(commit_status_batch)
      Preloaders::CommitStatusPreloader.new(commit_status_batch).execute(PRELOADED_RELATIONS)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::DropPipelineService, feature_category: :continuous_integration do
  let_it_be(:user) { create(:user) }

  let(:failure_reason) { :user_blocked }

  let!(:cancelable_pipeline) { create(:ci_pipeline, :running, user: user) }
  let!(:running_build) { create(:ci_build, :running, pipeline: cancelable_pipeline) }
  let!(:commit_status_running) { create(:commit_status, :running, pipeline: cancelable_pipeline) }
  let!(:success_pipeline) { create(:ci_pipeline, :success, user: user) }
  let!(:success_build) { create(:ci_build, :success, pipeline: success_pipeline) }
  let!(:commit_status_success) { create(:commit_status, :success, pipeline: cancelable_pipeline) }

  describe '#execute_async_for_all' do
    subject { described_class.new.execute_async_for_all(user.pipelines, failure_reason, user) }

    it 'drops only cancelable pipelines asynchronously', :sidekiq_inline do
      subject

      expect(cancelable_pipeline.reload).to be_failed
      expect(running_build.reload).to be_failed

      expect(success_pipeline.reload).to be_success
      expect(success_build.reload).to be_success
    end
  end

  describe '#execute' do
    subject { described_class.new.execute(cancelable_pipeline.id, failure_reason) }

    def drop_pipeline!(pipeline)
      described_class.new.execute(pipeline, failure_reason)
    end

    it 'drops each cancelable build in the pipeline', :aggregate_failures do
      drop_pipeline!(cancelable_pipeline)

      expect(running_build.reload).to be_failed
      expect(running_build.failure_reason).to eq(failure_reason.to_s)

      expect(success_build.reload).to be_success
    end

    it 'avoids N+1 queries when reading data' do
      control_count = ActiveRecord::QueryRecorder.new do
        drop_pipeline!(cancelable_pipeline)
      end.count

      writes_per_build = 2
      load_balancer_queries = 3
      expected_reads_count = control_count - writes_per_build

      create_list(:ci_build, 5, :running, pipeline: cancelable_pipeline)

      expect do
        drop_pipeline!(cancelable_pipeline)
      end.not_to exceed_query_limit(expected_reads_count + (5 * writes_per_build) + load_balancer_queries)
    end
  end
end


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

module Ci
  class PipelineTriggerService < BaseService
    include Gitlab::Utils::StrongMemoize
    include Services::ReturnServiceResponses
    include Ci::DownstreamPipelineHelpers

    def execute
      if trigger_from_token
        set_application_context_from_trigger(trigger_from_token)
        create_pipeline_from_trigger(trigger_from_token)
      elsif job_from_token
        set_application_context_from_job(job_from_token)
        create_pipeline_from_job(job_from_token)
      end

    rescue Ci::AuthJobFinder::AuthError => e
      error(e.message, 401)
    end

    private

    PAYLOAD_VARIABLE_KEY = 'TRIGGER_PAYLOAD'
    PAYLOAD_VARIABLE_HIDDEN_PARAMS = %i[token].freeze

    def create_pipeline_from_trigger(trigger)
      # this check is to not leak the presence of the project if user cannot read it
      return unless trigger.project == project
      return unless can?(trigger.owner, :read_project, project)

      response = Ci::CreatePipelineService
        .new(project, trigger.owner, ref: params[:ref], variables_attributes: variables)
        .execute(:trigger, ignore_skip_ci: true) do |pipeline|
          pipeline.trigger_requests.build(trigger: trigger)
        end

      pipeline_service_response(response.payload)
    end

    def pipeline_service_response(pipeline)
      if pipeline.created_successfully?
        success(pipeline: pipeline)
      elsif pipeline.persisted?
        err = pipeline.errors.messages.presence || pipeline.failure_reason.presence || 'Could not create pipeline'
        error(err, :unprocessable_entity)
      else
        error(pipeline.errors.messages, :bad_request)
      end
    end

    def trigger_from_token
      strong_memoize(:trigger) do
        Ci::Trigger.find_by_token(params[:token].to_s)
      end
    end

    def create_pipeline_from_job(job)
      # this check is to not leak the presence of the project if user cannot read it
      return unless can?(job.user, :read_project, project)

      response = Ci::CreatePipelineService
        .new(project, job.user, ref: params[:ref], variables_attributes: variables)
        .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
          source = job.sourced_pipelines.build(
            source_pipeline: job.pipeline,
            source_project: job.project,
            pipeline: pipeline,
            project: project)

          pipeline.source_pipeline = source
        end

      log_downstream_pipeline_creation(response.payload)
      pipeline_service_response(response.payload)
    end

    def job_from_token
      strong_memoize(:job) do
        Ci::AuthJobFinder.new(token: params[:token].to_s).execute!
      end
    end

    def variables
      param_variables + [payload_variable]
    end

    def param_variables
      params[:variables].to_h.map do |key, value|
        { key: key, value: value }
      end
    end

    def payload_variable
      { key: PAYLOAD_VARIABLE_KEY,
        value: Gitlab::Json.dump(params.except(*PAYLOAD_VARIABLE_HIDDEN_PARAMS)),
        variable_type: :file }
    end

    def set_application_context_from_trigger(trigger)
      Gitlab::ApplicationContext.push(
        user: trigger.owner,
        project: trigger.project
      )
    end

    def set_application_context_from_job(job)
      Gitlab::ApplicationContext.push(
        user: job.user,
        project: job.project,
        runner: job.runner
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::PipelineTriggerService, feature_category: :continuous_integration do
  include AfterNextHelpers

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

  before do
    stub_ci_pipeline_to_return_yaml_file
  end

  describe '#execute' do
    let_it_be(:user) { create(:user) }

    let(:result) { described_class.new(project, user, params).execute }

    before do
      project.add_developer(user)
    end

    shared_examples 'detecting an unprocessable pipeline trigger' do
      context 'when the pipeline was not created successfully' do
        let(:fail_pipeline) do
          receive(:execute).and_wrap_original do |original, *args, **kwargs|
            response = original.call(*args, **kwargs)
            pipeline = response.payload
            pipeline.update!(failure_reason: 'unknown_failure')

            response
          end
        end

        before do
          allow_next(Ci::CreatePipelineService).to fail_pipeline
        end

        it 'has the correct status code' do
          expect { result }.to change { Ci::Pipeline.count }
          expect(result).to be_error
          expect(result.http_status).to eq(:unprocessable_entity)
        end
      end
    end

    context 'with a trigger token' do
      let(:trigger) { create(:ci_trigger, project: project, owner: user) }

      context 'when trigger belongs to a different project' do
        let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
        let(:trigger) { create(:ci_trigger, project: create(:project), owner: user) }

        it 'does nothing' do
          expect { result }.not_to change { Ci::Pipeline.count }
        end
      end

      context 'when trigger owner does not have a permission to read a project' do
        let(:params) { { token: trigger.token, ref: 'master', variables: nil } }
        let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }

        it 'does nothing' do
          expect { result }.not_to change { Ci::Pipeline.count }
        end
      end

      context 'when params have an existing trigger token' do
        context 'when params have an existing ref' do
          let(:params) { { token: trigger.token, ref: 'master', variables: nil } }

          it 'triggers a pipeline' do
            expect { result }.to change { Ci::Pipeline.count }.by(1)
            expect(result[:pipeline].ref).to eq('master')
            expect(result[:pipeline].project).to eq(project)
            expect(result[:pipeline].user).to eq(trigger.owner)
            expect(result[:pipeline].trigger_requests.to_a)
              .to eq(result[:pipeline].builds.map(&:trigger_request).uniq)
            expect(result[:status]).to eq(:success)
          end

          it 'stores the payload as a variable' do
            expect { result }.to change { Ci::PipelineVariable.count }.by(1)

            var = result[:pipeline].variables.first

            expect(var.key).to eq('TRIGGER_PAYLOAD')
            expect(var.value).to eq('{"ref":"master","variables":null}')
            expect(var.variable_type).to eq('file')
          end

          context 'when commit message has [ci skip]' do
            before do
              allow_next(Ci::Pipeline).to receive(:git_commit_message) { '[ci skip]' }
            end

            it 'ignores [ci skip] and create as general' do
              expect { result }.to change { Ci::Pipeline.count }.by(1)
              expect(result).to be_success
            end
          end

          context 'when params have a variable' do
            let(:params) { { token: trigger.token, ref: 'master', variables: variables } }
            let(:variables) { { 'AAA' => 'AAA123' } }

            it 'has variables' do
              expect { result }.to change { Ci::PipelineVariable.count }.by(2)
                               .and change { Ci::TriggerRequest.count }.by(1)
              expect(result[:pipeline].variables.map { |v| { v.key => v.value } }.first).to eq(variables)
              expect(result[:pipeline].trigger_requests.last.variables).to be_nil
            end
          end

          context 'when params have duplicate variables' do
            let(:params) { { token: trigger.token, ref: 'master', variables: variables } }
            let(:variables) { { 'TRIGGER_PAYLOAD' => 'duplicate value' } }

            it 'creates a failed pipeline without variables' do
              expect { result }.to change { Ci::Pipeline.count }
              expect(result).to be_error
              expect(result.message[:base]).to eq(['Duplicate variable name: TRIGGER_PAYLOAD'])
            end
          end

          it_behaves_like 'detecting an unprocessable pipeline trigger'
        end

        context 'when params have a non-existant ref' do
          let(:params) { { token: trigger.token, ref: 'invalid-ref', variables: nil } }

          it 'does not trigger a pipeline' do
            expect { result }.not_to change { Ci::Pipeline.count }
            expect(result).to be_error
            expect(result.http_status).to eq(:bad_request)
          end
        end
      end

      context 'when params have a non-existant trigger token' do
        let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }

        it 'does not trigger a pipeline' do
          expect { result }.not_to change { Ci::Pipeline.count }
          expect(result).to be_nil
        end
      end
    end

    context 'with a pipeline job token' do
      let!(:pipeline) { create(:ci_empty_pipeline, project: project) }
      let(:job) { create(:ci_build, :running, pipeline: pipeline, user: user) }

      context 'when job user does not have a permission to read a project' do
        let(:params) { { token: job.token, ref: 'master', variables: nil } }
        let(:job) { create(:ci_build, pipeline: pipeline, user: create(:user)) }

        it 'does nothing' do
          expect { result }.not_to change { Ci::Pipeline.count }
        end
      end

      context 'when job is not running' do
        let(:params) { { token: job.token, ref: 'master', variables: nil } }
        let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) }

        it 'does nothing', :aggregate_failures do
          expect { result }.not_to change { Ci::Pipeline.count }
          expect(result[:message]).to eq('Job is not running')
          expect(result[:http_status]).to eq(401)
        end
      end

      context 'when job does not have a project' do
        let(:params) { { token: job.token, ref: 'master', variables: nil } }
        let(:job) { create(:ci_build, status: :running, pipeline: pipeline, user: user) }

        it 'does nothing', :aggregate_failures do
          job.update!(project: nil)

          expect { result }.not_to change { Ci::Pipeline.count }
          expect(result[:message]).to eq('Project has been deleted!')
          expect(result[:http_status]).to eq(401)
        end
      end

      context 'when params have an existsed job token' do
        context 'when params have an existsed ref' do
          let(:params) { { token: job.token, ref: 'master', variables: nil } }

          it 'triggers a pipeline' do
            expect { result }.to change { Ci::Pipeline.count }.by(1)
            expect(result[:pipeline].ref).to eq('master')
            expect(result[:pipeline].project).to eq(project)
            expect(result[:pipeline].user).to eq(job.user)
            expect(result[:status]).to eq(:success)
          end

          it_behaves_like 'logs downstream pipeline creation' do
            let(:downstream_pipeline) { result[:pipeline] }
            let(:expected_root_pipeline) { pipeline }
            let(:expected_hierarchy_size) { 2 }
            let(:expected_downstream_relationship) { :multi_project }
          end

          context 'when commit message has [ci skip]' do
            before do
              allow_next_instance_of(Ci::Pipeline) do |instance|
                allow(instance).to receive(:git_commit_message) { '[ci skip]' }
              end
            end

            it 'ignores [ci skip] and create as general' do
              expect { result }.to change { Ci::Pipeline.count }.by(1)
              expect(result[:status]).to eq(:success)
            end
          end

          context 'when params have a variable' do
            let(:params) { { token: job.token, ref: 'master', variables: variables } }
            let(:variables) { { 'AAA' => 'AAA123' } }

            it 'has variables' do
              expect { result }.to change { Ci::PipelineVariable.count }.by(2)
                               .and change { Ci::Sources::Pipeline.count }.by(1)
              expect(result[:pipeline].variables.map { |v| { v.key => v.value } }.first).to eq(variables)
              expect(job.sourced_pipelines.last.pipeline_id).to eq(result[:pipeline].id)
            end
          end

          context 'when params have duplicate variables' do
            let(:params) { { token: job.token, ref: 'master', variables: variables } }
            let(:variables) { { 'TRIGGER_PAYLOAD' => 'duplicate value' } }

            it 'creates a failed pipeline without variables' do
              expect { result }.to change { Ci::Pipeline.count }
              expect(result).to be_error
              expect(result.message[:base]).to eq(['Duplicate variable name: TRIGGER_PAYLOAD'])
            end
          end

          it_behaves_like 'detecting an unprocessable pipeline trigger'
        end

        context 'when params have a non-existant ref' do
          let(:params) { { token: job.token, ref: 'invalid-ref', variables: nil } }

          it 'does not trigger a job in the pipeline' do
            expect { result }.not_to change { Ci::Pipeline.count }
            expect(result).to be_error
            expect(result.http_status).to eq(:bad_request)
          end
        end
      end

      context 'when params have a non-existsed trigger token' do
        let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }

        it 'does not trigger a pipeline' do
          expect { result }.not_to change { Ci::Pipeline.count }
          expect(result).to be_nil
        end
      end
    end
  end
end