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

module DesignManagement
  class SaveDesignsService < DesignService
    include RunsDesignActions
    include OnSuccessCallbacks

    MAX_FILES = 10

    def initialize(project, user, params = {})
      super

      @files = params.fetch(:files)
    end

    def execute
      return error("Not allowed!") unless can_create_designs?
      return error("Only #{MAX_FILES} files are allowed simultaneously") if files.size > MAX_FILES
      return error("Duplicate filenames are not allowed!") if files.map(&:original_filename).uniq.length != files.length
      return error("Design copy is in progress") if design_collection.copy_in_progress?

      uploaded_designs, version = upload_designs!
      skipped_designs = designs - uploaded_designs

      create_events
      design_collection.reset_copy!

      success({ designs: uploaded_designs, version: version, skipped_designs: skipped_designs })
    rescue ::ActiveRecord::RecordInvalid => e
      error(e.message)
    end

    private

    attr_reader :files

    def upload_designs!
      ::DesignManagement::Version.with_lock(project.id, repository) do
        actions = build_actions

        [
          actions.map(&:design),
          actions.presence && run_actions(actions)
        ]
      end
    end

    # Returns `Design` instances that correspond with `files`.
    # New `Design`s will be created where a file name does not match
    # an existing `Design`
    def designs
      @designs ||= files.map do |file|
        collection.find_or_create_design!(filename: file.original_filename)
      end
    end

    def build_actions
      @actions ||= files.zip(designs).flat_map do |(file, design)|
        Array.wrap(build_design_action(file, design))
      end
    end

    def build_design_action(file, design)
      content = file_content(file, design.full_path)
      return if design_unchanged?(design, content)

      action = new_file?(design) ? :create : :update
      on_success do
        track_usage_metrics(action)
      end

      DesignManagement::DesignAction.new(design, action, content)
    end

    # Returns true if the design file is the same as its latest version
    def design_unchanged?(design, content)
      content == existing_blobs[design]&.data
    end

    def create_events
      by_action = @actions.group_by(&:action).transform_values { |grp| grp.map(&:design) }

      event_create_service.save_designs(current_user, **by_action)
    end

    def event_create_service
      @event_create_service ||= EventCreateService.new
    end

    def commit_message
      <<~MSG
      Updated #{files.size} #{'designs'.pluralize(files.size)}

      #{formatted_file_list}
      MSG
    end

    def formatted_file_list
      filenames.map { |name| "- #{name}" }.join("\n")
    end

    def filenames
      @filenames ||= files.map(&:original_filename)
    end

    def can_create_designs?
      Ability.allowed?(current_user, :create_design, issue)
    end

    def new_file?(design)
      !existing_blobs[design]
    end

    def file_content(file, full_path)
      transformer = ::Lfs::FileTransformer.new(project, repository, target_branch)
      transformer.new_file(full_path, file.to_io, detect_content_type: Feature.enabled?(:design_management_allow_dangerous_images, project)).content
    end

    # Returns the latest blobs for the designs as a Hash of `{ Design => Blob }`
    def existing_blobs
      @existing_blobs ||= begin
        items = designs.map { |d| [target_branch, d.full_path] }

        repository.blobs_at(items).each_with_object({}) do |blob, h|
          design = designs.find { |d| d.full_path == blob.path }

          h[design] = blob
        end
      end
    end

    def track_usage_metrics(action)
      if action == :update
        ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
          .track_issue_designs_modified_action(author: current_user, project: project)
      else
        ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
          .track_issue_designs_added_action(author: current_user, project: project)
      end

      ::Gitlab::UsageDataCounters::DesignsCounter.count(action)
    end
  end
end

DesignManagement::SaveDesignsService.prepend_mod_with('DesignManagement::SaveDesignsService')

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe DesignManagement::SaveDesignsService, feature_category: :design_management do
  include DesignManagementTestHelpers
  include ConcurrentHelpers

  let_it_be_with_reload(:issue) { create(:issue) }
  let_it_be(:developer) { create(:user, developer_projects: [issue.project]) }

  let(:project) { issue.project }
  let(:user) { developer }
  let(:files) { [rails_sample] }
  let(:design_repository) { project.find_or_create_design_management_repository.repository }

  let(:rails_sample_name) { 'rails_sample.jpg' }
  let(:rails_sample) { sample_image(rails_sample_name) }
  let(:dk_png) { sample_image('dk.png') }

  def sample_image(filename)
    fixture_file_upload("spec/fixtures/#{filename}")
  end

  def commit_count
    design_repository.expire_statistics_caches
    design_repository.expire_root_ref_cache
    design_repository.commit_count
  end

  before do
    if issue.design_collection.repository.exists?
      issue.design_collection.repository.expire_all_method_caches
      issue.design_collection.repository.raw.delete_all_refs_except([Gitlab::Git::BLANK_SHA])
    end

    allow(::DesignManagement::NewVersionWorker)
      .to receive(:perform_async).with(Integer, false).and_return(nil)
  end

  def run_service(files_to_upload = nil)
    design_files = files_to_upload || files
    design_files.each(&:rewind)

    service = described_class.new(project, user, issue: issue, files: design_files)
    service.execute
  end

  # Randomly alter the content of files.
  # This allows the files to be updated by the service, as unmodified
  # files are rejected.
  def touch_files(files_to_touch = nil)
    design_files = files_to_touch || files

    design_files.each do |f|
      f.tempfile.write(SecureRandom.random_bytes)
    end
  end

  let(:response) { run_service }

  shared_examples 'a service error' do
    it 'returns an error', :aggregate_failures do
      expect(response).to match(a_hash_including(status: :error))
    end
  end

  shared_examples 'an execution error' do
    it 'returns an error', :aggregate_failures do
      expect { service.execute }.to raise_error(some_error)
    end
  end

  describe '#execute' do
    context 'when the feature is not available' do
      before do
        enable_design_management(false)
      end

      it_behaves_like 'a service error'

      it 'does not create an event in the activity stream' do
        expect { run_service }.not_to change { Event.count }
      end
    end

    context 'when the feature is available' do
      before do
        enable_design_management(true)
      end

      describe 'repository existence' do
        def repository_exists
          # Expire the memoized value as the service creates it's own instance
          design_repository.expire_exists_cache
          design_repository.exists?
        end

        it 'is ensured when the service runs' do
          run_service

          expect(repository_exists).to be true
        end
      end

      it 'creates a commit, an event in the activity stream and updates the creation count', :aggregate_failures do
        counter = Gitlab::UsageDataCounters::DesignsCounter

        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_designs_added_action)
                                                                           .with(author: user, project: project)

        expect { run_service }
          .to change { Event.count }.by(1)
          .and change { Event.for_design.created_action.count }.by(1)
          .and change { counter.read(:create) }.by(1)

        expect(design_repository.commit).to have_attributes(
          author: user,
          message: include(rails_sample_name)
        )
      end

      it_behaves_like 'internal event tracking' do
        let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_DESIGNS_ADDED }
        let(:namespace) { project.namespace }
        subject(:service_action) { run_service }
      end

      it 'can run the same command in parallel' do
        parellism = 4

        blocks = Array.new(parellism).map do
          unique_files = [RenameableUpload.unique_file('rails_sample.jpg')]

          -> { run_service(unique_files) }
        end

        expect { run_parallel(blocks) }.to change(DesignManagement::Version, :count).by(parellism)
      end

      context 'when the design collection is in the process of being copied', :clean_gitlab_redis_shared_state do
        before do
          issue.design_collection.start_copy!
        end

        it_behaves_like 'a service error'
      end

      context 'when the design collection has a copy error', :clean_gitlab_redis_shared_state do
        before do
          issue.design_collection.copy_state = 'error'
          issue.design_collection.send(:set_stored_copy_state!)
        end

        it 'resets the copy state' do
          expect { run_service }.to change { issue.design_collection.copy_state }.from('error').to('ready')
        end
      end

      describe 'the response' do
        it 'includes designs with the expected properties' do
          updated_designs = response[:designs]

          expect(updated_designs).to all(have_attributes(diff_refs: be_present))
          expect(updated_designs.size).to eq(1)
          expect(updated_designs.first.versions.size).to eq(1)
          expect(updated_designs.first.versions.first.author).to eq(user)
        end
      end

      describe 'saving the file to LFS' do
        before do
          expect_next_instance_of(Lfs::FileTransformer) do |transformer|
            expect(transformer).to receive(:lfs_file?).and_return(true)
          end
        end

        it 'saves the design to LFS and saves the repository_type of the LfsObjectsProject as design' do
          expect { run_service }
            .to change { LfsObject.count }.by(1)
            .and change { project.lfs_objects_projects.count }.from(0).to(1)

          expect(project.lfs_objects_projects.first.repository_type).to eq('design')
        end
      end

      context 'when HEAD branch is different from master' do
        before do
          stub_feature_flags(main_branch_over_master: true)
        end

        it 'does not raise an exception during update' do
          run_service

          expect { run_service }.not_to raise_error
        end
      end

      context 'when a design is being updated' do
        before do
          run_service
          touch_files
        end

        it 'creates a new version for the existing design and updates the file' do
          expect(issue.designs.size).to eq(1)
          expect(DesignManagement::Version.for_designs(issue.designs).size).to eq(1)

          updated_designs = response[:designs]

          expect(updated_designs.size).to eq(1)
          expect(updated_designs.first.versions.size).to eq(2)
        end

        it 'updates UsageData for changed designs' do
          expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_designs_modified_action)
                                                                             .with(author: user, project: project)

          run_service
        end

        it_behaves_like 'internal event tracking' do
          let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_DESIGNS_MODIFIED }
          let(:namespace) { project.namespace }
          subject(:service_action) { run_service }
        end

        it 'records the correct events' do
          counter = Gitlab::UsageDataCounters::DesignsCounter
          expect { run_service }
            .to change { counter.read(:update) }.by(1)
            .and change { Event.count }.by(1)
            .and change { Event.for_design.updated_action.count }.by(1)
        end

        context 'when uploading a new design' do
          it 'does not link the new version to the existing design' do
            existing_design = issue.designs.first

            updated_designs = run_service([dk_png])[:designs]

            expect(existing_design.versions.reload.size).to eq(1)
            expect(updated_designs.size).to eq(1)
            expect(updated_designs.first.versions.size).to eq(1)
          end
        end

        context 'when detecting content type' do
          it 'detects content type when feature flag is enabled' do
            expect_next_instance_of(::Lfs::FileTransformer) do |file_transformer|
              expect(file_transformer).to receive(:new_file)
                .with(anything, anything, hash_including(detect_content_type: true)).and_call_original
            end

            run_service
          end

          it 'skips content type detection when feature flag is disabled' do
            stub_feature_flags(design_management_allow_dangerous_images: false)
            expect_next_instance_of(::Lfs::FileTransformer) do |file_transformer|
              expect(file_transformer).to receive(:new_file)
                .with(anything, anything, hash_including(detect_content_type: false)).and_call_original
            end

            run_service
          end
        end
      end

      context 'when a design has not changed since its previous version' do
        before do
          run_service
        end

        it 'does not create a new version, and returns the design in `skipped_designs`' do
          response = nil

          expect { response = run_service }.not_to change { issue.design_versions.count }

          expect(response[:designs]).to be_empty
          expect(response[:skipped_designs].size).to eq(1)
        end
      end

      context 'when doing a mixture of updates and creations' do
        let(:files) { [rails_sample, dk_png] }

        before do
          # Create just the first one, which we will later update.
          run_service([files.first])
          touch_files([files.first])
        end

        it 'has the correct side-effects' do
          counter = Gitlab::UsageDataCounters::DesignsCounter

          expect(::DesignManagement::NewVersionWorker)
            .to receive(:perform_async).once.with(Integer, false).and_return(nil)

          expect { run_service }
            .to change { Event.count }.by(2)
            .and change { Event.for_design.count }.by(2)
            .and change { Event.created_action.count }.by(1)
            .and change { Event.updated_action.count }.by(1)
            .and change { counter.read(:create) }.by(1)
            .and change { counter.read(:update) }.by(1)
            .and change { commit_count }.by(1)
        end
      end

      context 'when uploading multiple files' do
        let(:files) { [rails_sample, dk_png] }

        it 'returns information about both designs in the response' do
          expect(response).to include(designs: have_attributes(size: 2), status: :success)
        end

        it 'has the correct side-effects', :request_store do
          counter = Gitlab::UsageDataCounters::DesignsCounter
          service = described_class.new(project, user, issue: issue, files: files)

          # Some unrelated calls that are usually cached or happen only once
          # We expect:
          #  - An exists?
          #  - a check for existing blobs
          #  - default branch
          #  - an after_commit callback on LfsObjectsProject
          design_repository.create_if_not_exists
          design_repository.has_visible_content?

          expect(::DesignManagement::NewVersionWorker)
            .to receive(:perform_async).once.with(Integer, false).and_return(nil)

          expect { service.execute }
            .to change { issue.designs.count }.from(0).to(2)
            .and change { DesignManagement::Version.count }.by(1)
            .and change { counter.read(:create) }.by(2)
            .and change { Gitlab::GitalyClient.get_request_count }.by(3)
            .and change { commit_count }.by(1)
        end

        context 'when uploading too many files' do
          let(:files) { Array.new(DesignManagement::SaveDesignsService::MAX_FILES + 1) { dk_png } }

          it 'returns the correct error' do
            expect(response[:message]).to match(/only \d+ files are allowed simultaneously/i)
          end
        end

        context 'when uploading duplicate files' do
          let(:files) { [rails_sample, dk_png, rails_sample] }

          it 'returns the correct error' do
            expect(response[:message]).to match('Duplicate filenames are not allowed!')
          end
        end
      end

      context 'when the user is not allowed to upload designs' do
        let(:user) { build_stubbed(:user, id: non_existing_record_id) }

        it_behaves_like 'a service error'
      end

      describe 'failure modes' do
        let(:service) { described_class.new(project, user, issue: issue, files: files) }
        let(:response) { service.execute }

        before do
          expect(service).to receive(:run_actions).and_raise(some_error)
        end

        context 'when creating the commit fails' do
          let(:some_error) { Gitlab::Git::BaseError }

          it_behaves_like 'an execution error'
        end

        context 'when creating the versions fails' do
          let(:some_error) { ActiveRecord::RecordInvalid }

          it_behaves_like 'a service error'
        end
      end

      context "when a design already existed in the repo but we didn't know about it in the database" do
        let(:filename) { rails_sample_name }

        before do
          path = File.join(build(:design, issue: issue, filename: filename).full_path)
          design_repository.create_if_not_exists
          design_repository.create_file(
            user,
            path, 'something fake',
            branch_name: project.default_branch_or_main,
            message: 'Somehow created without being tracked in db'
          )
        end

        it 'creates the design and a new version for it' do
          first_updated_design = response[:designs].first

          expect(first_updated_design.filename).to eq(filename)
          expect(first_updated_design.versions.size).to eq(1)
        end
      end

      describe 'scalability', skip: 'See: https://gitlab.com/gitlab-org/gitlab/-/issues/213169' do
        before do
          run_service([sample_image('banana_sample.gif')]) # ensure project, issue, etc are created
        end

        it 'runs the same queries for all requests, regardless of number of files' do
          one = [dk_png]
          two = [rails_sample, dk_png]

          baseline = ActiveRecord::QueryRecorder.new { run_service(one) }

          expect { run_service(two) }.not_to exceed_query_limit(baseline)
        end
      end
    end
  end
end


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

module DesignManagement
  class DeleteDesignsService < DesignService
    include RunsDesignActions
    include OnSuccessCallbacks

    def initialize(project, user, params = {})
      super

      @designs = params.fetch(:designs)
    end

    def execute
      return error('Forbidden!') unless can_delete_designs?

      version = delete_designs!
      EventCreateService.new.destroy_designs(designs, current_user)
      Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_designs_removed_action(
        author: current_user,
        project: project
      )
      TodosDestroyer::DestroyedDesignsWorker.perform_async(designs.map(&:id))

      success(version: version)
    end

    def commit_message
      n = designs.size

      <<~MSG
      Removed #{n} #{'designs'.pluralize(n)}

      #{formatted_file_list}
      MSG
    end

    private

    attr_reader :designs

    def delete_designs!
      DesignManagement::Version.with_lock(project.id, repository) do
        run_actions(build_actions)
      end
    end

    def can_delete_designs?
      Ability.allowed?(current_user, :destroy_design, issue)
    end

    def build_actions
      designs.map { |d| design_action(d) }
    end

    def design_action(design)
      on_success do
        counter.count(:delete)
      end

      DesignManagement::DesignAction.new(design, :delete)
    end

    def counter
      ::Gitlab::UsageDataCounters::DesignsCounter
    end

    def formatted_file_list
      designs.map { |design| "- #{design.full_path}" }.join("\n")
    end
  end
end

DesignManagement::DeleteDesignsService.prepend_mod_with('DesignManagement::DeleteDesignsService')

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe DesignManagement::DeleteDesignsService, feature_category: :design_management do
  include DesignManagementTestHelpers

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

  let(:designs) { create_designs }

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

  # Defined as a method so that the response is not cached. We also construct
  # a new service executor each time to avoid the intermediate cached values
  # it constructs during its execution.
  def run_service(delenda = nil)
    service = described_class.new(project, user, issue: issue, designs: delenda || designs)
    service.execute
  end

  let(:response) { run_service }

  shared_examples 'a service error' do
    it 'returns an error', :aggregate_failures do
      expect(response).to include(status: :error)
    end
  end

  shared_examples 'a top-level error' do
    let(:expected_error) { StandardError }
    it 'raises an en expected error', :aggregate_failures do
      expect { run_service }.to raise_error(expected_error)
    end
  end

  shared_examples 'a success' do
    it 'returns successfully', :aggregate_failures do
      expect(response).to include(status: :success)
    end

    it 'saves the user as the author' do
      version = response[:version]

      expect(version.author).to eq(user)
    end
  end

  before do
    enable_design_management(enabled)
    project.add_developer(user)
  end

  describe "#execute" do
    context "when the feature is not available" do
      let(:enabled) { false }

      it_behaves_like "a service error"

      it 'does not create any events in the activity stream' do
        expect do
          run_service
        rescue StandardError
          nil
        end.not_to change { Event.count }
      end
    end

    context "when the feature is available" do
      let(:enabled) { true }

      it 'is able to delete designs' do
        expect(service.send(:can_delete_designs?)).to be true
      end

      context 'no designs were passed' do
        let(:designs) { [] }

        it_behaves_like "a top-level error"

        it 'does not log any events' do
          counter = ::Gitlab::UsageDataCounters::DesignsCounter

          expect do
            run_service
          rescue StandardError
            nil
          end
            .not_to change { [counter.totals, Event.count] }
        end

        it 'does not log any UsageData metrics' do
          redis_hll = ::Gitlab::UsageDataCounters::HLLRedisCounter
          event = Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_DESIGNS_REMOVED

          expect do
            run_service
          rescue StandardError
            nil
          end
            .not_to change { redis_hll.unique_events(event_names: event, start_date: Date.today, end_date: 1.week.from_now) }

          begin
            run_service
          rescue StandardError
            nil
          end
        end
      end

      context 'one design is passed' do
        before do
          create_designs(2)
        end

        let!(:designs) { create_designs(1) }

        it 'removes that design' do
          expect { run_service }.to change { issue.designs.current.count }.from(3).to(2)
        end

        it 'logs a deletion event' do
          counter = ::Gitlab::UsageDataCounters::DesignsCounter
          expect { run_service }.to change { counter.read(:delete) }.by(1)
        end

        it 'updates UsageData for removed designs' do
          expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_designs_removed_action)
                                                                             .with(author: user, project: project)

          run_service
        end

        it 'creates an event in the activity stream' do
          expect { run_service }
            .to change { Event.count }.by(1)
            .and change { Event.destroyed_action.for_design.count }.by(1)
        end

        it 'informs the new-version-worker' do
          expect(::DesignManagement::NewVersionWorker).to receive(:perform_async).with(Integer, false)

          run_service
        end

        it 'creates a new version' do
          expect { run_service }.to change { DesignManagement::Version.where(issue: issue).count }.by(1)
        end

        it 'returns the new version' do
          version = response[:version]

          expect(version).to eq(DesignManagement::Version.for_issue(issue).ordered.first)
        end

        it_behaves_like "a success"

        it 'removes the design from the current design list' do
          run_service

          expect(issue.designs.current).not_to include(designs.first)
        end

        it 'marks the design as deleted' do
          expect { run_service }
            .to change { designs.first.deleted? }.from(false).to(true)
        end

        it 'schedules deleting todos for that design' do
          expect(TodosDestroyer::DestroyedDesignsWorker).to receive(:perform_async).with([designs.first.id])

          run_service
        end

        it_behaves_like 'internal event tracking' do
          let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_DESIGNS_REMOVED }
          let(:namespace) { project.namespace }

          subject(:service_action) { run_service }
        end
      end

      context 'more than one design is passed' do
        before do
          create_designs(1)
        end

        let!(:designs) { create_designs(2) }

        it 'makes the correct changes' do
          counter = ::Gitlab::UsageDataCounters::DesignsCounter

          expect { run_service }
            .to change { issue.designs.current.count }.from(3).to(1)
            .and change { counter.read(:delete) }.by(2)
            .and change { Event.count }.by(2)
            .and change { Event.destroyed_action.for_design.count }.by(2)
        end

        it 'schedules deleting todos for that design' do
          expect(TodosDestroyer::DestroyedDesignsWorker).to receive(:perform_async).with(designs.map(&:id))

          run_service
        end

        it_behaves_like "a success"

        context 'after executing the service' do
          let(:deleted_designs) { designs.map(&:reset) }

          let!(:version) { run_service[:version] }

          it 'removes the removed designs from the current design list' do
            expect(issue.designs.current).not_to include(*deleted_designs)
          end

          it 'does not make the designs impossible to find' do
            expect(issue.designs).to include(*deleted_designs)
          end

          it 'associates the new version with all the designs' do
            current_versions = deleted_designs.map { |d| d.most_recent_action.version }
            expect(current_versions).to all(eq version)
          end

          it 'marks all deleted designs as deleted' do
            expect(deleted_designs).to all(be_deleted)
          end

          it 'marks all deleted designs with the same deletion version' do
            expect(deleted_designs.map { |d| d.most_recent_action.version_id }.uniq)
              .to have_attributes(size: 1)
          end
        end
      end

      describe 'scalability' do
        before do
          run_service(create_designs(1)) # ensure project, issue, etc are created
        end

        it 'makes the same number of DB requests for one design as for several' do
          one = create_designs(1)
          many = create_designs(5)

          baseline = ActiveRecord::QueryRecorder.new { run_service(one) }

          expect { run_service(many) }.not_to exceed_query_limit(baseline)
        end
      end
    end
  end

  private

  def create_designs(how_many = 2)
    create_list(:design, how_many, :with_lfs_file, issue: issue)
  end
end


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

module DesignManagement
  # This service generates smaller image versions for `DesignManagement::Design`
  # records within a given `DesignManagement::Version`.
  class GenerateImageVersionsService < DesignService
    # We limit processing to only designs with file sizes that don't
    # exceed `MAX_DESIGN_SIZE`.
    #
    # Note, we may be able to remove checking this limit, if when we come to
    # implement a file size limit for designs, there are no designs that
    # exceed 40MB on GitLab.com
    #
    # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22860#note_281780387
    MAX_DESIGN_SIZE = 40.megabytes.freeze

    def initialize(version)
      super(version.project, version.author, issue: version.issue)

      @version = version
    end

    def execute
      # rubocop: disable CodeReuse/ActiveRecord
      version.actions.includes(:design).each do |action|
        generate_image(action)
      end
      # rubocop: enable CodeReuse/ActiveRecord

      success(version: version)
    end

    private

    attr_reader :version

    def generate_image(action)
      raw_file = get_raw_file(action)

      unless raw_file
        log_error("No design file found for Action: #{action.id}")
        return
      end

      # Skip attempting to process images that would be rejected by CarrierWave.
      return unless DesignManagement::DesignV432x230Uploader::MIME_TYPE_ALLOWLIST.include?(raw_file.content_type)

      # Store and process the file
      action.image_v432x230.store!(raw_file)
      action.save!
    rescue CarrierWave::IntegrityError => e
      Gitlab::ErrorTracking.log_exception(e, project_id: project.id, design_id: action.design_id, version_id: action.version_id)
      log_error(e.message)
    rescue CarrierWave::UploadError => e
      Gitlab::ErrorTracking.track_exception(e, project_id: project.id, design_id: action.design_id, version_id: action.version_id)
      log_error(e.message)
    end

    # Returns the `CarrierWave::SanitizedFile` of the original design file
    def get_raw_file(action)
      raw_files_by_path[action.design.full_path]
    end

    # Returns the `Carrierwave:SanitizedFile` instances for all of the original
    # design files, mapping to { design.filename => `Carrierwave::SanitizedFile` }.
    #
    # As design files are stored in Git LFS, the only way to retrieve their original
    # files is to first fetch the LFS pointer file data from the Git design repository.
    # The LFS pointer file data contains an "OID" that lets us retrieve `LfsObject`
    # records, which have an Uploader (`LfsObjectUploader`) for the original design file.
    def raw_files_by_path
      @raw_files_by_path ||= LfsObject.for_oids(blobs_by_oid.keys).each_with_object({}) do |lfs_object, h|
        blob = blobs_by_oid[lfs_object.oid]
        file = lfs_object.file.file
        # The `CarrierWave::SanitizedFile` is loaded without knowing the `content_type`
        # of the file, due to the file not having an extension.
        #
        # Set the content_type from the `Blob`.
        file.content_type = blob.content_type
        h[blob.path] = file
      end
    end

    # Returns the `Blob`s that correspond to the design files in the repository.
    #
    # All design `Blob`s are LFS Pointer files, and are therefore small amounts
    # of data to load.
    #
    # `Blob`s whose size are above a certain threshold: `MAX_DESIGN_SIZE`
    # are filtered out.
    def blobs_by_oid
      @blobs ||= begin
        items = version.designs.map { |design| [version.sha, design.full_path] }
        blobs = repository.blobs_at(items)
        blobs.reject! { |blob| blob.lfs_size > MAX_DESIGN_SIZE }
        blobs.index_by(&:lfs_oid)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe DesignManagement::GenerateImageVersionsService, feature_category: :design_management do
  let_it_be(:project) { create(:project) }
  let_it_be(:issue) { create(:issue, project: project) }
  let_it_be(:version) { create(:design, :with_lfs_file, issue: issue).versions.first }
  let_it_be(:action) { version.actions.first }

  describe '#execute' do
    it 'generates the image' do
      expect { described_class.new(version).execute }
        .to change { action.reload.image_v432x230.file }
        .from(nil).to(CarrierWave::SanitizedFile)
    end

    it 'skips generating image versions if the mime type is not allowlisted' do
      stub_const('DesignManagement::DesignV432x230Uploader::MIME_TYPE_ALLOWLIST', [])

      described_class.new(version).execute

      expect(action.reload.image_v432x230.file).to eq(nil)
    end

    it 'skips generating image versions if the design file size is too large' do
      stub_const("#{described_class.name}::MAX_DESIGN_SIZE", 1.byte)

      described_class.new(version).execute

      expect(action.reload.image_v432x230.file).to eq(nil)
    end

    it 'returns the status' do
      result = described_class.new(version).execute

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

    it 'returns the version' do
      result = described_class.new(version).execute

      expect(result[:version]).to eq(version)
    end

    it 'logs if the raw image cannot be found' do
      version.designs.first.update!(filename: 'foo.png')

      expect(Gitlab::AppLogger).to receive(:error).with("No design file found for Action: #{action.id}")

      described_class.new(version).execute
    end

    context 'when an error is encountered when generating the image versions' do
      context "CarrierWave::IntegrityError" do
        before do
          expect_next_instance_of(DesignManagement::DesignV432x230Uploader) do |uploader|
            expect(uploader).to receive(:cache!).and_raise(CarrierWave::IntegrityError, 'foo')
          end
        end

        it 'logs the exception' do
          expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
            instance_of(CarrierWave::IntegrityError),
            project_id: project.id, version_id: version.id, design_id: version.designs.first.id
          )

          described_class.new(version).execute
        end

        it 'logs the error' do
          expect(Gitlab::AppLogger).to receive(:error).with('foo')

          described_class.new(version).execute
        end
      end

      context "CarrierWave::UploadError" do
        before do
          expect_next_instance_of(DesignManagement::DesignV432x230Uploader) do |uploader|
            expect(uploader).to receive(:cache!).and_raise(CarrierWave::UploadError, 'foo')
          end
        end

        it 'logs the error' do
          expect(Gitlab::AppLogger).to receive(:error).with('foo')

          described_class.new(version).execute
        end

        it 'tracks the error' do
          expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
            instance_of(CarrierWave::UploadError),
            project_id: project.id, version_id: version.id, design_id: version.designs.first.id
          )

          described_class.new(version).execute
        end
      end
    end
  end
end


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

# Service to copy a DesignCollection from one Issue to another.
# Copies the DesignCollection's Designs, Versions, and Notes on Designs.
module DesignManagement
  module CopyDesignCollection
    class CopyService < DesignService
      # rubocop: disable CodeReuse/ActiveRecord
      def initialize(project, user, params = {})
        super

        @target_issue = params.fetch(:target_issue)
        @target_project = @target_issue.project
        @target_repository = @target_project.design_repository
        @target_design_collection = @target_issue.design_collection
        @temporary_branch = "CopyDesignCollectionService_#{SecureRandom.hex}"
        # The user who triggered the copy may not have permissions to push
        # to the design repository.
        @git_user = @target_project.first_owner

        @designs = DesignManagement::Design.unscoped.where(issue: issue).order(:id).load
        @versions = DesignManagement::Version.unscoped.where(issue: issue).order(:id).includes(:designs).load

        @sha_attribute = Gitlab::Database::ShaAttribute.new
        @shas = []
        @event_enum_map = DesignManagement::DesignAction::EVENT_FOR_GITALY_ACTION.invert
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def execute
        return error('User cannot copy design collection to issue') unless user_can_copy?
        return error('Target design collection must first be queued') unless target_design_collection.copy_in_progress?
        return error('Design collection has no designs') if designs.empty?
        return error('Target design collection already has designs') unless target_design_collection.empty?

        with_temporary_branch do
          copy_commits!

          ApplicationRecord.transaction do
            design_ids = copy_designs!
            version_ids = copy_versions!
            copy_actions!(design_ids, version_ids)
            link_lfs_files!
            copy_notes!(design_ids)
            finalize!
          end
        end

        ServiceResponse.success
      rescue StandardError => error
        log_exception(error)

        target_design_collection.error_copy!

        error('Designs were unable to be copied successfully')
      end

      private

      attr_reader :designs, :event_enum_map, :git_user, :sha_attribute, :shas,
        :temporary_branch, :target_design_collection, :target_issue,
        :target_repository, :target_project, :versions

      alias_method :merge_branch, :target_branch

      def log_exception(exception)
        payload = {
          issue_id: issue.id,
          project_id: project.id,
          target_issue_id: target_issue.id,
          target_project: target_project.id
        }

        Gitlab::ErrorTracking.track_exception(exception, payload)
      end

      def error(message)
        ServiceResponse.error(message: message)
      end

      def user_can_copy?
        current_user.can?(:read_design, design_collection) &&
          current_user.can?(:admin_issue, target_issue)
      end

      def with_temporary_branch(&block)
        target_repository.create_if_not_exists

        create_default_branch! if target_repository.empty?
        create_temporary_branch!

        yield
      ensure
        remove_temporary_branch!
      end

      # A project that does not have any designs will have a blank design
      # repository. To create a temporary branch from default branch we need to
      # create default branch first by adding a file to it.
      def create_default_branch!
        target_repository.create_file(
          git_user,
          ".CopyDesignCollectionService_#{Time.now.to_i}",
          '.gitlab',
          message: "Commit to create #{merge_branch} branch in CopyDesignCollectionService",
          branch_name: merge_branch
        )
      end

      def create_temporary_branch!
        target_repository.add_branch(
          git_user,
          temporary_branch,
          target_repository.root_ref
        )
      end

      def remove_temporary_branch!
        return unless target_repository.branch_exists?(temporary_branch)

        target_repository.rm_branch(git_user, temporary_branch)
      end

      # Merge the temporary branch containing the commits to default branch
      # and update the state of the target_design_collection.
      def finalize!
        source_sha = shas.last

        target_repository.raw.merge(
          git_user,
          source_sha: source_sha,
          target_branch: merge_branch,
          message: 'CopyDesignCollectionService finalize merge'
        ) { nil }

        target_design_collection.end_copy!
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def copy_commits!
        # Execute another query to include actions and their designs
        DesignManagement::Version.unscoped.where(id: versions).order(:id).includes(actions: :design).find_each(batch_size: 100) do |version|
          gitaly_actions = version.actions.map do |action|
            design = action.design
            # Map the raw Action#event enum value to a Gitaly "action" for the
            # `Repository#commit_files` call.
            gitaly_action_name = @event_enum_map[action.event_before_type_cast]
            # `content` will be the LfsPointer file and not the design file,
            # and can be nil for deletions.
            content = blobs.dig(version.sha, design.filename)&.data
            file_path = DesignManagement::Design.build_full_path(target_issue, design)

            {
              action: gitaly_action_name,
              file_path: file_path,
              content: content
            }.compact
          end

          sha = target_repository.commit_files(
            git_user,
            branch_name: temporary_branch,
            message: commit_message(version),
            actions: gitaly_actions
          )

          shas << sha
        end
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def copy_designs!
        design_attributes = attributes_config[:design_attributes]

        ::DesignManagement::Design.with_project_iid_supply(target_project) do |supply|
          new_rows = designs.each_with_index.map do |design, i|
            design.attributes.slice(*design_attributes).merge(
              issue_id: target_issue.id,
              project_id: target_project.id,
              iid: supply.next_value
            )
          end

          # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
          # once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
          # When this is fixed, we can remove the call to
          # `with_project_iid_supply` above, since the objects will be instantiated
          # and callbacks (including `ensure_project_iid!`) will fire.
          ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
            DesignManagement::Design.table_name,
            new_rows,
            return_ids: true
          )
        end
      end

      def copy_versions!
        version_attributes = attributes_config[:version_attributes]
        # `shas` are the list of Git commits made during the Git copy phase,
        # and will be ordered 1:1 with old versions
        shas_enum = shas.to_enum

        new_rows = versions.map do |version|
          version.attributes.slice(*version_attributes).merge(
            issue_id: target_issue.id,
            sha: sha_attribute.serialize(shas_enum.next)
          )
        end

        # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
        # once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
        ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
          DesignManagement::Version.table_name,
          new_rows,
          return_ids: true
        )
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def copy_actions!(new_design_ids, new_version_ids)
        # Create a map of <Old design id> => <New design id>
        design_id_map = new_design_ids.each_with_index.to_h do |design_id, i|
          [designs[i].id, design_id]
        end

        # Create a map of <Old version id> => <New version id>
        version_id_map = new_version_ids.each_with_index.to_h do |version_id, i|
          [versions[i].id, version_id]
        end

        actions = DesignManagement::Action.unscoped.select(:design_id, :version_id, :event).where(design: designs, version: versions)

        new_rows = actions.map do |action|
          {
            design_id: design_id_map[action.design_id],
            version_id: version_id_map[action.version_id],
            event: action.event_before_type_cast
          }
        end

        # We cannot use `BulkInsertSafe` because of the uploader mounted in `Action`.
        ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
          DesignManagement::Action.table_name,
          new_rows
        )
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def commit_message(version)
        "Copy commit #{version.sha} from issue #{issue.to_reference(full: true)}"
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def copy_notes!(design_ids)
        new_designs = DesignManagement::Design.unscoped.find(design_ids)

        # Execute another query to filter only designs with notes
        DesignManagement::Design.unscoped.where(id: designs).joins(:notes).distinct.find_each(batch_size: 100) do |old_design|
          new_design = new_designs.find { |d| d.filename == old_design.filename }

          Notes::CopyService.new(current_user, old_design, new_design).execute
        end
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # rubocop: disable CodeReuse/ActiveRecord
      def link_lfs_files!
        oids = blobs.values.flat_map(&:values).map(&:lfs_oid)
        repository_type = LfsObjectsProject.repository_types[:design]

        new_rows = LfsObject.where(oid: oids).find_each(batch_size: 1000).map do |lfs_object|
          {
            project_id: target_project.id,
            lfs_object_id: lfs_object.id,
            repository_type: repository_type
          }
        end

        # We cannot use `BulkInsertSafe` due to the LfsObjectsProject#update_project_statistics
        # callback that fires after_commit.
        ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
          LfsObjectsProject.table_name,
          new_rows,
          on_conflict: :do_nothing # Upsert
        )
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # Blob data is used to find the oids for LfsObjects and to copy to Git.
      # Blobs are reasonably small in memory, as their data are LFS Pointer files.
      #
      # Returns all blobs for the designs as a Hash of `{ Blob#commit_id => { Design#filename => Blob } }`
      def blobs
        @blobs ||= begin
          items = versions.flat_map { |v| v.designs.map { |d| [v.sha, DesignManagement::Design.build_full_path(issue, d)] } }

          repository.blobs_at(items).each_with_object({}) do |blob, h|
            design = designs.find { |d| DesignManagement::Design.build_full_path(issue, d) == blob.path }

            h[blob.commit_id] ||= {}
            h[blob.commit_id][design.filename] = blob
          end
        end
      end

      def attributes_config
        @attributes_config ||= YAML.load_file(attributes_config_file).symbolize_keys
      end

      def attributes_config_file
        Rails.root.join('lib/gitlab/design_management/copy_design_collection_model_attributes.yml')
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitlab_redis_shared_state, feature_category: :portfolio_management do
  include DesignManagementTestHelpers

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

  let(:target_issue) { create(:issue) }

  subject { described_class.new(project, user, issue: issue, target_issue: target_issue).execute }

  before do
    enable_design_management
  end

  shared_examples 'service error' do |message:|
    it 'returns an error response', :aggregate_failures do
      expect(subject).to be_kind_of(ServiceResponse)
      expect(subject).to be_error
      expect(subject.message).to eq(message)
    end
  end

  shared_examples 'service success' do
    it 'returns a success response', :aggregate_failures do
      expect(subject).to be_kind_of(ServiceResponse)
      expect(subject).to be_success
    end
  end

  include_examples 'service error', message: 'User cannot copy design collection to issue'

  context 'when user has permission to read the design collection' do
    before_all do
      project.add_reporter(user)
    end

    include_examples 'service error', message: 'User cannot copy design collection to issue'

    context 'when the user also has permission to admin the target issue' do
      let(:target_repository) { target_issue.project.design_repository }

      before do
        target_issue.project.add_reporter(user)
      end

      include_examples 'service error', message: 'Target design collection must first be queued'

      context 'when the target design collection has been queued' do
        before do
          target_issue.design_collection.start_copy!
        end

        include_examples 'service error', message: 'Design collection has no designs'

        context 'when design collection has designs' do
          let_it_be(:designs) do
            create_list(:design, 3, :with_lfs_file, :with_relative_position, issue: issue, project: project)
          end

          context 'when target issue already has designs' do
            before do
              create(:design, issue: target_issue, project: target_issue.project)
            end

            include_examples 'service error', message: 'Target design collection already has designs'
          end

          context 'when target project already has designs' do
            let!(:issue_x) { create(:issue, project: target_issue.project) }
            let!(:existing) { create(:design, issue: issue_x, project: target_issue.project) }

            let(:new_designs) do
              target_issue.reset
              target_issue.designs.where.not(id: existing.id)
            end

            it 'sets IIDs for new designs above existing ones' do
              subject

              expect(new_designs).to all(have_attributes(iid: (be > existing.iid)))
            end

            it 'does not allow for IID collisions' do
              subject
              create(:design, issue: issue_x, project: target_issue.project)

              design_iids = target_issue.project.designs.map(&:id)

              expect(design_iids).to match_array(design_iids.uniq)
            end
          end

          include_examples 'service success'

          it 'creates a design repository for the target project' do
            expect { subject }.to change { target_repository.exists? }.from(false).to(true)
          end

          context 'when the target project already has a design repository' do
            before do
              target_repository.create_if_not_exists
            end

            include_examples 'service success'
          end

          it 'copies the designs correctly', :aggregate_failures do
            expect { subject }.to change { target_issue.designs.count }.by(3)

            old_designs = issue.designs.ordered
            new_designs = target_issue.designs.ordered

            new_designs.zip(old_designs).each do |new_design, old_design|
              expect(new_design).to have_attributes(
                filename: old_design.filename,
                description: old_design.description,
                relative_position: old_design.relative_position,
                issue: target_issue,
                project: target_issue.project
              )
            end
          end

          it 'copies the design versions correctly', :aggregate_failures do
            expect { subject }.to change { target_issue.design_versions.count }.by(3)

            old_versions = issue.design_versions.ordered
            new_versions = target_issue.design_versions.ordered

            new_versions.zip(old_versions).each do |new_version, old_version|
              expect(new_version).to have_attributes(
                created_at: old_version.created_at,
                author_id: old_version.author_id
              )
              expect(new_version.designs.pluck(:filename)).to eq(old_version.designs.pluck(:filename))
              expect(new_version.actions.pluck(:event)).to eq(old_version.actions.pluck(:event))
            end
          end

          it 'copies the design actions correctly', :aggregate_failures do
            expect { subject }.to change { DesignManagement::Action.count }.by(3)

            old_actions = issue.design_versions.ordered.flat_map(&:actions)
            new_actions = target_issue.design_versions.ordered.flat_map(&:actions)

            new_actions.zip(old_actions).each do |new_action, old_action|
              # This is a way to identify if the versions linked to the actions
              # are correct is to compare design filenames, as the SHA changes.
              new_design_filenames = new_action.version.designs.ordered.pluck(:filename)
              old_design_filenames = old_action.version.designs.ordered.pluck(:filename)

              expect(new_design_filenames).to eq(old_design_filenames)
              expect(new_action.event).to eq(old_action.event)
              expect(new_action.design.filename).to eq(old_action.design.filename)
            end
          end

          it 'copies design notes correctly', :aggregate_failures, :sidekiq_inline do
            old_notes = [
              create(:diff_note_on_design, note: 'first note', noteable: designs.first, project: project, author: create(:user)),
              create(:diff_note_on_design, note: 'second note', noteable: designs.first, project: project, author: create(:user))
            ]
            matchers = old_notes.map do |note|
              have_attributes(
                note.attributes.slice(
                  :type,
                  :author_id,
                  :note,
                  :position
                )
              )
            end

            expect { subject }.to change { Note.count }.by(2)

            new_notes = target_issue.designs.first.notes.fresh

            expect(new_notes).to match_array(matchers)
          end

          it 'links the LfsObjects' do
            expect { subject }.to change { target_issue.project.lfs_objects.count }.by(3)
          end

          it 'copies the Git repository data', :aggregate_failures do
            subject

            expect(commits_on_master(limit: 99)).to include(*target_issue.design_versions.ordered.pluck(:sha))
          end

          it 'creates a default branch if none previously existed' do
            expect { subject }.to change { target_repository.branch_names }.from([]).to([project.design_repository.root_ref])
          end

          it 'does not create default branch when one exists' do
            target_repository.create_if_not_exists
            target_repository.create_file(user, '.meta', '.gitlab', branch_name: 'new-branch', message: 'message')

            expect { subject }.not_to change { target_repository.branch_names }
            expect(target_repository.branch_names).to eq(['new-branch'])
          end

          it 'leaves the design collection in the correct copy state' do
            subject

            expect(target_issue.design_collection).to be_copy_ready
          end

          describe 'rollback' do
            before do
              # Ensure the very last step throws an error
              expect_next_instance_of(described_class) do |service|
                expect(service).to receive(:finalize!).and_raise
              end
            end

            include_examples 'service error', message: 'Designs were unable to be copied successfully'

            it 'rollsback all PostgreSQL data created', :aggregate_failures do
              expect { subject }.not_to change {
                [
                  DesignManagement::Design.count,
                  DesignManagement::Action.count,
                  DesignManagement::Version.count,
                  Note.count
                ]
              }

              collections = [
                target_issue.design_collection,
                target_issue.designs,
                target_issue.design_versions
              ]

              expect(collections).to all(be_empty)
            end

            it 'does not alter master branch', :aggregate_failures do
              # Add some Git data to the target_repository, so we are testing
              # that any original data remains
              issue_2 = create(:issue, project: target_issue.project)
              create(:design, :with_file, issue: issue_2, project: target_issue.project)

              expect { subject }.not_to change { commits_on_master }
            end

            it 'sets the design collection copy state' do
              subject

              expect(target_issue.design_collection).to be_copy_error
            end
          end

          def commits_on_master(limit: 10)
            target_repository.commits(target_repository.root_ref, limit: limit).map(&:id)
          end
        end
      end
    end
  end

  describe 'Alert if schema changes', :aggregate_failures do
    let_it_be(:config_file) { Rails.root.join('lib/gitlab/design_management/copy_design_collection_model_attributes.yml') }
    let_it_be(:config) { YAML.load_file(config_file).symbolize_keys }

    %w[Design Action Version].each do |model|
      specify do
        attributes = config["#{model.downcase}_attributes".to_sym] || []
        ignored_attributes = config["ignore_#{model.downcase}_attributes".to_sym]

        expect(attributes + ignored_attributes).to contain_exactly(
          *DesignManagement.const_get(model, false).column_names
        ), failure_message(model)
      end
    end

    def failure_message(model)
      <<-MSG
      The schema of the `#{model}` model has changed.

      `#{described_class.name}` refers to specific lists of attributes of `#{model}` to either
      copy or ignore, so that we continue to copy designs correctly after schema changes.

      Please update:
      #{config_file}
      to reflect the latest changes to `#{model}`. See that file for more information.
      MSG
    end
  end
end


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

# Service for setting the initial copy_state on the target DesignCollection
# and queuing a CopyDesignCollectionWorker.
module DesignManagement
  module CopyDesignCollection
    class QueueService
      def initialize(current_user, issue, target_issue)
        @current_user = current_user
        @issue = issue
        @target_issue = target_issue
        @target_design_collection = target_issue.design_collection
      end

      def execute
        return error('User cannot copy designs to issue') unless user_can_copy?
        return error('Target design collection copy state must be `ready`') unless target_design_collection.can_start_copy?

        target_design_collection.start_copy!

        DesignManagement::CopyDesignCollectionWorker.perform_async(current_user.id, issue.id, target_issue.id)

        ServiceResponse.success
      end

      private

      delegate :design_collection, to: :issue

      attr_reader :current_user, :issue, :target_design_collection, :target_issue

      def error(message)
        ServiceResponse.error(message: message)
      end

      def user_can_copy?
        current_user.can?(:read_design, issue) &&
          current_user.can?(:admin_issue, target_issue)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe DesignManagement::CopyDesignCollection::QueueService, :clean_gitlab_redis_shared_state,
  feature_category: :design_management do
  include DesignManagementTestHelpers

  let_it_be(:user) { create(:user) }
  let_it_be(:issue) { create(:issue) }
  let_it_be(:target_issue, refind: true) { create(:issue) }
  let_it_be(:design) { create(:design, issue: issue, project: issue.project) }

  subject { described_class.new(user, issue, target_issue).execute }

  before do
    enable_design_management
  end

  it 'returns an error if user does not have permission' do
    expect(subject).to be_kind_of(ServiceResponse)
    expect(subject).to be_error
    expect(subject.message).to eq('User cannot copy designs to issue')
  end

  context 'when user has permission' do
    before_all do
      issue.project.add_reporter(user)
      target_issue.project.add_reporter(user)
    end

    it 'returns an error if design collection copy_state is not queuable' do
      target_issue.design_collection.start_copy!

      expect(subject).to be_kind_of(ServiceResponse)
      expect(subject).to be_error
      expect(subject.message).to eq('Target design collection copy state must be `ready`')
    end

    it 'sets the design collection copy state' do
      expect { subject }.to change { target_issue.design_collection.copy_state }.from('ready').to('in_progress')
    end

    it 'queues a DesignManagement::CopyDesignCollectionWorker', :clean_gitlab_redis_queues do
      expect { subject }.to change(DesignManagement::CopyDesignCollectionWorker.jobs, :size).by(1)
    end

    it 'returns success' do
      expect(subject).to be_kind_of(ServiceResponse)
      expect(subject).to be_success
    end
  end
end


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

module UserPreferences
  class UpdateService < BaseService
    def initialize(user, params = {})
      @preferences = user.user_preference
      @params = params.to_h.dup.with_indifferent_access
    end

    def execute
      if @preferences.update(@params)
        ServiceResponse.success(
          message: 'Preference was updated',
          payload: { preferences: @preferences })
      else
        ServiceResponse.error(message: 'Could not update preference')
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe UserPreferences::UpdateService, feature_category: :user_profile do
  let(:user) { create(:user) }
  let(:params) { { view_diffs_file_by_file: false, pass_user_identities_to_ci_jwt: true } }

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

    context 'successfully updating the record' do
      it 'updates the preference and returns a success' do
        result = service.execute

        expect(result.status).to eq(:success)
        expect(result.payload[:preferences].view_diffs_file_by_file).to eq(params[:view_diffs_file_by_file])
        expect(result.payload[:preferences].pass_user_identities_to_ci_jwt
              ).to eq(params[:pass_user_identities_to_ci_jwt])
      end
    end

    context 'unsuccessfully updating the record' do
      before do
        allow(user.user_preference).to receive(:update).and_return(false)
      end

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

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


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

module Users
  class ValidateManualOtpService < BaseService
    include ::Gitlab::Auth::Otp::Fortinet
    include ::Gitlab::Auth::Otp::DuoAuth

    def initialize(current_user)
      @current_user = current_user
      @strategy = if forti_authenticator_enabled?(current_user)
                    ::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp.new(current_user)
                  elsif forti_token_cloud_enabled?(current_user)
                    ::Gitlab::Auth::Otp::Strategies::FortiTokenCloud.new(current_user)
                  elsif duo_auth_enabled?(current_user)
                    ::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp.new(current_user)
                  else
                    ::Gitlab::Auth::Otp::Strategies::Devise.new(current_user)
                  end
    end

    def execute(otp_code)
      strategy.validate(otp_code)
    rescue StandardError => ex
      Gitlab::ErrorTracking.log_exception(ex)
      error(ex.message)
    end

    private

    attr_reader :strategy
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::ValidateManualOtpService, feature_category: :user_profile do
  let_it_be(:user) { create(:user) }

  let(:otp_code) { 42 }

  subject(:validate) { described_class.new(user).execute(otp_code) }

  context 'Devise' do
    it 'calls Devise strategy' do
      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::Devise) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once
      end

      validate
    end
  end

  context 'FortiAuthenticator' do
    before do
      stub_feature_flags(forti_authenticator: user)
      allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(true)
    end

    it 'calls ManualOtp strategy' do
      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once
      end

      validate
    end

    it 'handles unexpected error' do
      error_message = "boom!"

      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message)
      end
      expect(Gitlab::ErrorTracking).to receive(:log_exception)

      result = validate

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

  context 'FortiTokenCloud' do
    before do
      stub_feature_flags(forti_token_cloud: user)
      allow(::Gitlab.config.forti_token_cloud).to receive(:enabled).and_return(true)
    end

    it 'calls FortiTokenCloud strategy' do
      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiTokenCloud) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once
      end

      validate
    end
  end

  context 'DuoAuth' do
    before do
      allow(::Gitlab.config.duo_auth).to receive(:enabled).and_return(true)
    end

    it 'calls DuoAuth strategy' do
      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once
      end

      validate
    end

    it "handles unexpected error" do
      error_message = "boom!"

      expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy|
        expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message)
      end
      expect(Gitlab::ErrorTracking).to receive(:log_exception)

      result = validate

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


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

module Users
  class ActivityService
    LEASE_TIMEOUT = 1.minute.to_i

    def initialize(author:, namespace: nil, project: nil)
      @user = if author.respond_to?(:username)
                author
              elsif author.respond_to?(:user)
                author.user
              end

      @user = nil unless user.is_a?(User)
      @namespace = namespace
      @project = project
    end

    def execute
      return unless user

      ::Gitlab::Database::LoadBalancing::Session.without_sticky_writes { record_activity }
    end

    private

    attr_reader :user, :namespace, :project

    def record_activity
      return if Gitlab::Database.read_only?

      today = Date.today
      return if user.last_activity_on == today

      lease = Gitlab::ExclusiveLease.new("activity_service:#{user.id}", timeout: LEASE_TIMEOUT)
      return unless lease.try_obtain

      user.update_attribute(:last_activity_on, today)

      Gitlab::UsageDataCounters::HLLRedisCounter.track_event('unique_active_user', values: user.id)

      Gitlab::Tracking.event(
        'Users::ActivityService',
        'perform_action',
        user: user,
        namespace: namespace,
        project: project,
        label: 'redis_hll_counters.manage.unique_active_users_monthly',
        context: [
          Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: 'unique_active_user').to_context
        ]
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::ActivityService, feature_category: :user_profile do
  include ExclusiveLeaseHelpers

  let(:user) { create(:user, last_activity_on: last_activity_on) }

  subject { described_class.new(author: user) }

  describe '#execute', :clean_gitlab_redis_shared_state do
    shared_examples 'does not update last_activity_on' do
      it 'does not update user attribute' do
        expect { subject.execute }.not_to change(user, :last_activity_on)
      end

      it 'does not track Snowplow event' do
        subject.execute

        expect_no_snowplow_event
      end
    end

    context 'when last activity is nil' do
      let(:last_activity_on) { nil }

      it 'updates last_activity_on for the user' do
        expect { subject.execute }
          .to change(user, :last_activity_on).from(last_activity_on).to(Date.today)
      end
    end

    context 'when last activity is in the past' do
      let(:last_activity_on) { Date.today - 1.week }

      it 'updates last_activity_on for the user' do
        expect { subject.execute }
          .to change(user, :last_activity_on)
                .from(last_activity_on)
                .to(Date.today)
      end

      it 'tries to obtain ExclusiveLease' do
        expect(Gitlab::ExclusiveLease).to receive(:new).with("activity_service:#{user.id}", anything).and_call_original

        subject.execute
      end

      it 'tracks RedisHLL event' do
        expect(Gitlab::UsageDataCounters::HLLRedisCounter)
          .to receive(:track_event)
          .with('unique_active_user', values: user.id)

        subject.execute
      end

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        subject(:record_activity) { described_class.new(author: user, namespace: namespace, project: project).execute }

        let(:category) { described_class.name }
        let(:action) { 'perform_action' }
        let(:label) { 'redis_hll_counters.manage.unique_active_users_monthly' }
        let(:namespace) { build(:group) }
        let(:project) { build(:project) }
        let(:context) do
          payload = Gitlab::Tracking::ServicePingContext.new(
            data_source: :redis_hll,
            event: 'unique_active_user'
          ).to_context

          [Gitlab::Json.dump(payload)]
        end
      end
    end

    context 'when a bad object is passed' do
      let(:fake_object) { double(username: 'hello') }

      it 'does not record activity' do
        service = described_class.new(author: fake_object)

        expect(service).not_to receive(:record_activity)

        service.execute
      end
    end

    context 'when last activity is today' do
      let(:last_activity_on) { Date.today }

      it_behaves_like 'does not update last_activity_on'

      it 'does not try to obtain ExclusiveLease' do
        expect(Gitlab::ExclusiveLease).not_to receive(:new).with("activity_service:#{user.id}", anything)

        subject.execute
      end
    end

    context 'when in GitLab read-only instance' do
      let(:last_activity_on) { nil }

      before do
        allow(Gitlab::Database).to receive(:read_only?).and_return(true)
      end

      it_behaves_like 'does not update last_activity_on'
    end

    context 'when a lease could not be obtained' do
      let(:last_activity_on) { nil }

      before do
        stub_exclusive_lease_taken("activity_service:#{user.id}", timeout: 1.minute.to_i)
      end

      it_behaves_like 'does not update last_activity_on'
    end
  end

  context 'with DB Load Balancing' do
    let(:user) { create(:user, last_activity_on: last_activity_on) }

    context 'when last activity is in the past' do
      let(:user) { create(:user, last_activity_on: Date.today - 1.week) }

      context 'database load balancing is configured' do
        before do
          ::Gitlab::Database::LoadBalancing::Session.clear_session
        end

        let(:service) do
          service = described_class.new(author: user)

          ::Gitlab::Database::LoadBalancing::Session.clear_session

          service
        end

        it 'does not stick to primary' do
          expect(::Gitlab::Database::LoadBalancing::Session.current).not_to be_performed_write

          service.execute

          expect(user.last_activity_on).to eq(Date.today)
          expect(::Gitlab::Database::LoadBalancing::Session.current).to be_performed_write
          expect(::Gitlab::Database::LoadBalancing::Session.current).not_to be_using_primary
        end
      end

      context 'database load balancing is not configured' do
        let(:service) { described_class.new(author: user) }

        it 'updates user without error' do
          service.execute

          expect(user.last_activity_on).to eq(Date.today)
        end
      end
    end
  end
end


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

module Users
  class SetNamespaceCommitEmailService
    include Gitlab::Allowable

    attr_reader :current_user, :target_user, :namespace, :email_id

    def initialize(current_user, namespace, email_id, params)
      @current_user = current_user
      @target_user = params.delete(:user) || current_user
      @namespace = namespace
      @email_id = email_id
    end

    def execute
      return error(_('Namespace must be provided.')) if namespace.nil?

      unless can?(current_user, :admin_user_email_address, target_user)
        return error(_("User doesn't exist or you don't have permission to change namespace commit emails."))
      end

      unless can?(target_user, :read_namespace_via_membership, namespace)
        return error(_("Namespace doesn't exist or you don't have permission."))
      end

      email = target_user.emails.find_by(id: email_id) unless email_id.nil? # rubocop: disable CodeReuse/ActiveRecord
      existing_namespace_commit_email = target_user.namespace_commit_email_for_namespace(namespace)
      if existing_namespace_commit_email.nil?
        return error(_('Email must be provided.')) if email.nil?

        create_namespace_commit_email(email)
      elsif email_id.nil?
        remove_namespace_commit_email(existing_namespace_commit_email)
      else
        update_namespace_commit_email(existing_namespace_commit_email, email)
      end
    end

    private

    def remove_namespace_commit_email(namespace_commit_email)
      namespace_commit_email.destroy
      success(nil)
    end

    def create_namespace_commit_email(email)
      namespace_commit_email = ::Users::NamespaceCommitEmail.new(
        user: target_user,
        namespace: namespace,
        email: email
      )

      save_namespace_commit_email(namespace_commit_email)
    end

    def update_namespace_commit_email(namespace_commit_email, email)
      namespace_commit_email.email = email

      save_namespace_commit_email(namespace_commit_email)
    end

    def save_namespace_commit_email(namespace_commit_email)
      if !namespace_commit_email.save
        error_in_save(namespace_commit_email)
      else
        success(namespace_commit_email)
      end
    end

    def success(namespace_commit_email)
      ServiceResponse.success(payload: {
        namespace_commit_email: namespace_commit_email
      })
    end

    def error(message)
      ServiceResponse.error(message: message)
    end

    def error_in_save(namespace_commit_email)
      return error(_('Failed to save namespace commit email.')) if namespace_commit_email.errors.empty?

      error(namespace_commit_email.errors.full_messages.to_sentence)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::SetNamespaceCommitEmailService, feature_category: :user_profile do
  include AfterNextHelpers

  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }
  let_it_be(:email) { create(:email, user: user) }
  let_it_be(:existing_achievement) { create(:achievement, namespace: group) }

  let(:namespace) { group }
  let(:current_user) { user }
  let(:target_user) { user }
  let(:email_id) { email.id }
  let(:params) { { user: target_user } }
  let(:service) { described_class.new(current_user, namespace, email_id, params) }

  before_all do
    group.add_reporter(user)
  end

  shared_examples 'success' do
    it 'creates namespace commit email' do
      result = service.execute

      expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail)
      expect(result.payload[:namespace_commit_email]).to be_persisted
    end
  end

  describe '#execute' do
    context 'when current_user is not provided' do
      let(:current_user) { nil }

      it 'returns error message' do
        expect(service.execute.message)
          .to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
      end
    end

    context 'when current_user does not have permission to change namespace commit emails' do
      let(:target_user) { create(:user) }

      it 'returns error message' do
        expect(service.execute.message)
          .to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
      end
    end

    context 'when target_user does not have permission to access the namespace' do
      let(:namespace) { create(:group) }

      it 'returns error message' do
        expect(service.execute.message).to eq("Namespace doesn't exist or you don't have permission.")
      end
    end

    context 'when namespace is not provided' do
      let(:namespace) { nil }

      it 'returns error message' do
        expect(service.execute.message).to eq('Namespace must be provided.')
      end
    end

    context 'when target user is not current user' do
      context 'when current user is an admin' do
        let(:current_user) { create(:user, :admin) }

        context 'when admin mode is enabled', :enable_admin_mode do
          it 'creates namespace commit email' do
            result = service.execute

            expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail)
            expect(result.payload[:namespace_commit_email]).to be_persisted
          end
        end

        context 'when admin mode is not enabled' do
          it 'returns error message' do
            expect(service.execute.message)
              .to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
          end
        end
      end

      context 'when current user is not an admin' do
        let(:current_user) { create(:user) }

        it 'returns error message' do
          expect(service.execute.message)
            .to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
        end
      end
    end

    context 'when namespace commit email does not exist' do
      context 'when email_id is not provided' do
        let(:email_id) { nil }

        it 'returns error message' do
          expect(service.execute.message).to eq('Email must be provided.')
        end
      end

      context 'when model save fails' do
        before do
          allow_next(::Users::NamespaceCommitEmail).to receive(:save).and_return(false)
        end

        it 'returns error message' do
          expect(service.execute.message).to eq('Failed to save namespace commit email.')
        end
      end

      context 'when namepsace is a group' do
        it_behaves_like 'success'
      end

      context 'when namespace is a user' do
        let(:namespace) { current_user.namespace }

        it_behaves_like 'success'
      end

      context 'when namespace is a project' do
        let_it_be(:project) { create(:project) }

        let(:namespace) { project.project_namespace }

        before do
          project.add_reporter(current_user)
        end

        it_behaves_like 'success'
      end
    end

    context 'when namespace commit email already exists' do
      let!(:existing_namespace_commit_email) do
        create(:namespace_commit_email,
          user: target_user,
          namespace: namespace,
          email: create(:email, user: target_user))
      end

      context 'when email_id is not provided' do
        let(:email_id) { nil }

        it 'destroys the namespace commit email' do
          result = service.execute

          expect(result.message).to be_nil
          expect(result.payload[:namespace_commit_email]).to be_nil
        end
      end

      context 'and email_id is provided' do
        let(:email_id) { create(:email, user: current_user).id }

        it 'updates namespace commit email' do
          result = service.execute

          existing_namespace_commit_email.reload

          expect(result.payload[:namespace_commit_email]).to eq(existing_namespace_commit_email)
          expect(existing_namespace_commit_email.email_id).to eq(email_id)
        end
      end

      context 'when model save fails' do
        before do
          allow_any_instance_of(::Users::NamespaceCommitEmail).to receive(:save).and_return(false) # rubocop:disable RSpec/AnyInstanceOf
        end

        it 'returns generic error message' do
          expect(service.execute.message).to eq('Failed to save namespace commit email.')
        end

        context 'with model errors' do
          before do
            allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :empty?).and_return(false) # rubocop:disable RSpec/AnyInstanceOf
            allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :full_messages, :to_sentence).and_return('Model error') # rubocop:disable RSpec/AnyInstanceOf
          end

          it 'returns the model error message' do
            expect(service.execute.message).to eq('Model error')
          end
        end
      end
    end
  end
end


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

module Users
  class BannedUserBaseService < BaseService
    def initialize(current_user)
      @current_user = current_user
    end

    def execute(user)
      return permission_error unless allowed?
      return state_error(user) unless valid_state?(user)

      if update_user(user)
        log_event(user)
        track_event(user)
        success
      else
        messages = user.errors.full_messages
        error(messages.uniq.join('. '))
      end
    end

    private

    attr_reader :current_user

    # Overridden in Users::BanService
    def track_event(_); end

    def state_error(user)
      error(_("You cannot %{action} %{state} users." % { action: action.to_s, state: user.state }), :forbidden)
    end

    def allowed?
      can?(current_user, :admin_all_resources)
    end

    def permission_error
      error(_("You are not allowed to %{action} a user" % { action: action.to_s }), :forbidden)
    end

    def log_event(user)
      Gitlab::AppLogger.info(message: "User #{action}", user: user.username.to_s, email: user.email.to_s, "#{action}_by": current_user.username.to_s, ip_address: current_user.current_sign_in_ip.to_s)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::BannedUserBaseService, feature_category: :user_management do
  let(:admin) { create(:admin) }
  let(:base_service) { described_class.new(admin) }

  describe '#initialize' do
    it 'sets the current_user instance value' do
      expect(base_service.instance_values["current_user"]).to eq(admin)
    end
  end
end


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

module Users
  class TrustService < BaseService
    def initialize(current_user)
      @current_user = current_user
    end

    def execute(user)
      UserCustomAttribute.set_trusted_by(user: user, trusted_by: @current_user)
      success
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::TrustService, feature_category: :user_management do
  let_it_be(:current_user) { create(:admin) }

  subject(:service) { described_class.new(current_user) }

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

    subject(:operation) { service.execute(user) }

    it 'updates the custom attributes', :aggregate_failures do
      expect(user.custom_attributes).to be_empty

      operation
      user.reload

      expect(user.custom_attributes.by_key(UserCustomAttribute::TRUSTED_BY)).to be_present
    end
  end
end


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

module Users
  class BlockService < BaseService
    def initialize(current_user)
      @current_user = current_user
    end

    def execute(user)
      return error('An internal user cannot be blocked', 403) if user.internal?

      if user.block
        after_block_hook(user)
        success
      else
        messages = user.errors.full_messages
        error(messages.uniq.join('. '))
      end
    end

    private

    # overridden by EE module
    def after_block_hook(user)
      custom_attribute = {
        user_id: user.id,
        key: UserCustomAttribute::BLOCKED_BY,
        value: "#{current_user.username}/#{current_user.id}+#{Time.current}"
      }
      UserCustomAttribute.upsert_custom_attributes([custom_attribute])
    end
  end
end

Users::BlockService.prepend_mod_with('Users::BlockService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::BlockService, feature_category: :user_management do
  let_it_be(:current_user) { create(:admin) }

  subject(:service) { described_class.new(current_user) }

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

    context 'when successful' do
      let(:user) { create(:user) }

      it { is_expected.to eq(status: :success) }

      it "change the user's state" do
        expect { operation }.to change { user.state }.to('blocked')
      end

      it 'saves a custom attribute', :aggregate_failures, :freeze_time, feature_category: :insider_threat do
        operation

        custom_attribute = user.custom_attributes.last

        expect(custom_attribute.key).to eq(UserCustomAttribute::BLOCKED_BY)
        expect(custom_attribute.value).to eq("#{current_user.username}/#{current_user.id}+#{Time.current}")
      end
    end

    context 'when failed' do
      let(:user) { create(:user, :blocked) }

      it 'returns error result' do
        aggregate_failures 'error result' do
          expect(operation[:status]).to eq(:error)
          expect(operation[:message]).to match(/State cannot transition/)
        end
      end

      it "does not change the user's state" do
        expect { operation }.not_to change { user.state }
      end
    end

    context 'when internal user' do
      let(:user) { create(:user, :bot) }

      it 'returns error result' do
        expect(operation[:status]).to eq(:error)
        expect(operation[:message]).to eq('An internal user cannot be blocked')
        expect(operation[:http_status]).to eq(403)
      end
    end
  end
end


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

module Users
  # Service for refreshing the authorized projects of a user.
  #
  # This particular service class can not be used to update data for the same
  # user concurrently. Doing so could lead to an incorrect state. To ensure this
  # doesn't happen a caller must synchronize access (e.g. using
  # `Gitlab::ExclusiveLease`).
  #
  # Usage:
  #
  #     user = User.find_by(username: 'alice')
  #     service = Users::RefreshAuthorizedProjectsService.new(some_user)
  #     service.execute
  class RefreshAuthorizedProjectsService
    attr_reader :user, :source

    LEASE_TIMEOUT = 1.minute.to_i

    # user - The User for which to refresh the authorized projects.
    def initialize(user, source: nil, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil)
      @user = user
      @source = source
      @incorrect_auth_found_callback = incorrect_auth_found_callback
      @missing_auth_found_callback = missing_auth_found_callback
    end

    def execute
      lease_key = "refresh_authorized_projects:#{user.id}"
      lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)

      until uuid = lease.try_obtain
        # Keep trying until we obtain the lease. If we don't do so we may end up
        # not updating the list of authorized projects properly. To prevent
        # hammering Redis too much we'll wait for a bit between retries.
        sleep(0.1)
      end

      begin
        # We need an up to date User object that has access to all relations that
        # may have been created earlier. The only way to ensure this is to reload
        # the User object.
        user.reset
        execute_without_lease
      ensure
        Gitlab::ExclusiveLease.cancel(lease_key, uuid)
      end
    end

    # This method returns the updated User object.
    def execute_without_lease
      remove, add = AuthorizedProjectUpdate::FindRecordsDueForRefreshService.new(
        user,
        source: source,
        incorrect_auth_found_callback: incorrect_auth_found_callback,
        missing_auth_found_callback: missing_auth_found_callback
      ).execute

      update_authorizations(remove, add)
    end

    # Updates the list of authorizations for the current user.
    #
    # remove - The project IDs of the authorization rows to remove.
    # add - Rows to insert in the form `[{ user_id: user_id, project_id: project_id, access_level: access_level}, ...]`
    def update_authorizations(remove = [], add = [])
      log_refresh_details(remove, add)

      ProjectAuthorizations::Changes.new do |changes|
        changes.add(add)
        changes.remove_projects_for_user(user, remove)
      end.apply!

      user.update!(project_authorizations_recalculated_at: Time.zone.now) if remove.any? || add.any?

      # Since we batch insert authorization rows, Rails' associations may get
      # out of sync. As such we force a reload of the User object.
      user.reset
    end

    private

    attr_reader :incorrect_auth_found_callback, :missing_auth_found_callback

    def log_refresh_details(remove, add)
      Gitlab::AppJsonLogger.info(
        event: 'authorized_projects_refresh',
        user_id: user.id,
        'authorized_projects_refresh.source': source,
        'authorized_projects_refresh.rows_deleted_count': remove.length,
        'authorized_projects_refresh.rows_added_count': add.length,
        # most often there's only a few entries in remove and add, but limit it to the first 5
        # entries to avoid flooding the logs
        'authorized_projects_refresh.rows_deleted_slice': remove.first(5),
        'authorized_projects_refresh.rows_added_slice': add.first(5).map(&:values)
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Users::RefreshAuthorizedProjectsService, feature_category: :user_management do
  include ExclusiveLeaseHelpers

  # We're using let! here so that any expectations for the service class are not
  # triggered twice.
  let!(:project) { create(:project) }

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

  describe '#execute', :clean_gitlab_redis_shared_state do
    it 'refreshes the authorizations using a lease' do
      lease_key = "refresh_authorized_projects:#{user.id}"

      expect_to_obtain_exclusive_lease(lease_key, 'uuid')
      expect_to_cancel_exclusive_lease(lease_key, 'uuid')
      expect(service).to receive(:execute_without_lease)

      service.execute
    end

    context 'callbacks' do
      let(:callback) { double('callback') }

      context 'incorrect_auth_found_callback callback' do
        let(:user) { create(:user) }
        let(:service) do
          described_class.new(user, incorrect_auth_found_callback: callback)
        end

        it 'is called' do
          access_level = Gitlab::Access::DEVELOPER
          create(:project_authorization, user: user, project: project, access_level: access_level)

          expect(callback).to receive(:call).with(project.id, access_level).once

          service.execute
        end
      end

      context 'missing_auth_found_callback callback' do
        let(:service) do
          described_class.new(user, missing_auth_found_callback: callback)
        end

        it 'is called' do
          ProjectAuthorization.delete_all

          expect(callback).to receive(:call).with(project.id, Gitlab::Access::OWNER).once

          service.execute
        end
      end
    end
  end

  describe '#execute_without_lease' do
    before do
      user.project_authorizations.delete_all
    end

    it 'updates the authorized projects of the user' do
      project2 = create(:project)
      project_authorization = user.project_authorizations
        .create!(project: project2, access_level: Gitlab::Access::MAINTAINER)

      to_be_removed = [project_authorization.project_id]

      to_be_added = [
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
      ]

      expect(service).to receive(:update_authorizations)
        .with(to_be_removed, to_be_added)

      service.execute_without_lease
    end

    it 'sets the access level of a project to the highest available level' do
      user.project_authorizations.delete_all

      project_authorization = user.project_authorizations
        .create!(project: project, access_level: Gitlab::Access::DEVELOPER)

      to_be_removed = [project_authorization.project_id]

      to_be_added = [
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
      ]

      expect(service).to receive(:update_authorizations)
        .with(to_be_removed, to_be_added)

      service.execute_without_lease
    end

    it 'updates project_authorizations_recalculated_at', :freeze_time do
      default_date = Time.zone.local('2010')
      expect do
        service.execute_without_lease
      end.to change { user.project_authorizations_recalculated_at }.from(default_date).to(Time.zone.now)
    end

    it 'returns a User' do
      expect(service.execute_without_lease).to be_an_instance_of(User)
    end
  end

  describe '#update_authorizations' do
    context 'when there are no rows to add and remove' do
      it 'does not change authorizations' do
        expect { service.update_authorizations([], []) }.to not_change { user.project_authorizations.count }
      end
    end

    it 'removes authorizations that should be removed' do
      authorization = user.project_authorizations.find_by(project_id: project.id)

      service.update_authorizations([authorization.project_id])

      expect(user.project_authorizations).to be_empty
    end

    it 'inserts authorizations that should be added' do
      user.project_authorizations.delete_all

      to_be_added = [
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
      ]

      service.update_authorizations([], to_be_added)

      authorizations = user.project_authorizations

      expect(authorizations.length).to eq(1)
      expect(authorizations[0].user_id).to eq(user.id)
      expect(authorizations[0].project_id).to eq(project.id)
      expect(authorizations[0].access_level).to eq(Gitlab::Access::MAINTAINER)
    end

    it 'logs the details of the refresh' do
      source = :foo
      service = described_class.new(user, source: source)
      user.project_authorizations.delete_all

      expect(Gitlab::AppJsonLogger).to(
        receive(:info).with(
          event: 'authorized_projects_refresh',
          user_id: user.id,
          'authorized_projects_refresh.source': source,
          'authorized_projects_refresh.rows_deleted_count': 0,
          'authorized_projects_refresh.rows_added_count': 1,
          'authorized_projects_refresh.rows_deleted_slice': [],
          'authorized_projects_refresh.rows_added_slice': [[user.id, project.id, Gitlab::Access::MAINTAINER]]
        )
      )

      to_be_added = [
        { user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
      ]

      service.update_authorizations([], to_be_added)
    end
  end
end