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

require 'rubygems/package'

module Packages
  module Rubygems
    class ProcessGemService
      include Gitlab::Utils::StrongMemoize
      include ExclusiveLeaseGuard

      ExtractionError = Class.new(StandardError)
      InvalidMetadataError = Class.new(StandardError)

      DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze

      def initialize(package_file)
        @package_file = package_file
      end

      def execute
        raise ExtractionError, 'Gem was not processed - package_file is not set' unless package_file
        return success if process_gem

        error('Gem was not processed')
      rescue ActiveRecord::StatementInvalid
        # TODO: We can remove this rescue block when we fix https://gitlab.com/gitlab-org/gitlab/-/issues/415899
        raise InvalidMetadataError, 'Invalid metadata'
      end

      private

      attr_reader :package_file

      def process_gem
        try_obtain_lease do
          package.transaction do
            rename_package_and_set_version
            rename_package_file
            ::Packages::Rubygems::MetadataExtractionService.new(package, gemspec).execute
            ::Packages::Rubygems::CreateGemspecService.new(package, gemspec).execute
            ::Packages::Rubygems::CreateDependenciesService.new(package, gemspec).execute
            cleanup_temp_package
          end
        end

        true
      end

      def rename_package_and_set_version
        package.update!(
          name: gemspec.name,
          version: gemspec.version,
          status: :default
        )
      end

      def rename_package_file
        # Updating file_name updates the path where the file is stored.
        # We must pass the file again so that CarrierWave can handle the update
        package_file.update!(
          file_name: "#{gemspec.name}-#{gemspec.version}.gem",
          file: package_file.file,
          package_id: package.id
        )
      end

      def cleanup_temp_package
        temp_package.destroy if package.id != temp_package.id
      end

      def gemspec
        gem.spec
      end
      strong_memoize_attr :gemspec

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

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

      def temp_package
        package_file.package
      end
      strong_memoize_attr :temp_package

      def package
        package = temp_package.project
                                .packages
                                .rubygems
                                .with_name(gemspec.name)
                                .with_version(gemspec.version.to_s)
                                .not_pending_destruction
                                .last
        package || temp_package
      end
      strong_memoize_attr :package

      def gem
        # use_file will set an exclusive lease on the file for as long as
        # the resulting gem object is being used. This means we are not
        # able to rename the package_file while also using the gem object.
        # We need to use a separate AR object to create the gem file to allow
        # `package_file` to be free for update so we re-find the file here.
        Packages::PackageFile.find(package_file.id).file.use_file do |file_path|
          Gem::Package.new(File.open(file_path))
        end
      rescue StandardError
        raise ExtractionError, 'Unable to read gem file'
      end

      # used by ExclusiveLeaseGuard
      def lease_key
        "packages:rubygems:process_gem_service:package:#{package.id}"
      end

      # used by ExclusiveLeaseGuard
      def lease_timeout
        DEFAULT_LEASE_TIMEOUT
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Rubygems::ProcessGemService, feature_category: :package_registry do
  include ExclusiveLeaseHelpers
  include RubygemsHelpers

  let_it_be_with_reload(:package) { create(:rubygems_package, :processing, name: 'temp_name', version: '0.0.0') }

  let(:package_file) { create(:package_file, :unprocessed_gem, package: package) }
  let(:gem) { gem_from_file(package_file.file) }
  let(:gemspec) { gem.spec }
  let(:service) { described_class.new(package_file) }

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

    context 'no gem file' do
      let(:package_file) { nil }

      it 'returns an error' do
        expect { subject }.to raise_error(::Packages::Rubygems::ProcessGemService::ExtractionError, 'Gem was not processed - package_file is not set')
      end
    end

    context 'success' do
      let(:sub_service) { double }

      before do
        expect(Packages::Rubygems::MetadataExtractionService).to receive(:new).with(package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateGemspecService).to receive(:new).with(package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateDependenciesService).to receive(:new).with(package, gemspec).and_return(sub_service)

        expect(sub_service).to receive(:execute).exactly(3).times.and_return(true)
      end

      it 'returns successfully', :aggregate_failures do
        result = subject

        expect(result.success?).to be true
        expect(result.payload[:package]).to eq(package)
      end

      it 'updates the package name and version', :aggregate_failures do
        expect(package.name).to eq('temp_name')
        expect(package.version).to eq('0.0.0')
        expect(package).to be_processing

        subject

        expect(package.reload.name).to eq('package')
        expect(package.version).to eq('0.0.1')
        expect(package).to be_default
      end

      it 'updates the package file name', :aggregate_failures do
        expect(package_file.file_name).to eq('package.gem')

        subject

        expect(package_file.reload.file_name).to eq('package-0.0.1.gem')
      end
    end

    context 'when the package already exists' do
      let_it_be(:existing_package) { create(:rubygems_package, name: 'package', version: '0.0.1', project: package.project) }

      let(:sub_service) { double }

      before do
        expect(Packages::Rubygems::MetadataExtractionService).to receive(:new).with(existing_package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateGemspecService).to receive(:new).with(existing_package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateDependenciesService).to receive(:new).with(existing_package, gemspec).and_return(sub_service)

        expect(sub_service).to receive(:execute).exactly(3).times.and_return(true)
      end

      it 'assigns the package_file to the existing package and deletes the temporary package', :aggregate_failures do
        expect(package).to receive(:destroy)

        expect { subject }.to change { existing_package.package_files.count }.by(1)

        expect(package_file.reload.package).to eq(existing_package)
      end
    end

    context 'when the package already exists marked as pending_destruction' do
      let_it_be_with_reload(:existing_package) { create(:rubygems_package, name: 'package', version: '0.0.1', project: package.project) }

      let(:sub_service) { double }

      before do
        expect(Packages::Rubygems::MetadataExtractionService).to receive(:new).with(package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateGemspecService).to receive(:new).with(package, gemspec).and_return(sub_service)
        expect(Packages::Rubygems::CreateDependenciesService).to receive(:new).with(package, gemspec).and_return(sub_service)

        expect(sub_service).to receive(:execute).exactly(3).times.and_return(true)

        existing_package.pending_destruction!
      end

      it 'reuses the processing package' do
        expect { subject }
          .to not_change { package.project.packages.count }
          .and not_change { existing_package.package_files.count }
      end
    end

    context 'sub-service failure' do
      before do
        expect(Packages::Rubygems::MetadataExtractionService).to receive(:new).with(package, gemspec).and_raise(::Packages::Rubygems::ProcessGemService::ExtractionError.new('failure'))
      end

      it 'returns an error' do
        expect { subject }.to raise_error(::Packages::Rubygems::ProcessGemService::ExtractionError, 'failure')
      end
    end

    context 'bad gem file' do
      before do
        expect(Gem::Package).to receive(:new).and_raise(ArgumentError)
      end

      it 'returns an error' do
        expect { subject }.to raise_error(::Packages::Rubygems::ProcessGemService::ExtractionError, 'Unable to read gem file')
      end
    end

    context 'without obtaining an exclusive lease' do
      let(:lease_key) { "packages:rubygems:process_gem_service:package:#{package.id}" }

      before do
        stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
      end

      it 'does not perform the services', :aggregate_failures do
        # The #use_file call triggers a separate lease on the package file being opened
        # for use with the gem. We don't want to test that here, so we allow the call to proceed
        expect(Gitlab::ExclusiveLease).to receive(:new).with("object_storage_migrate:Packages::PackageFile:#{package_file.id}", anything).and_call_original

        expect(Packages::Rubygems::MetadataExtractionService).not_to receive(:new)
        expect(Packages::Rubygems::CreateGemspecService).not_to receive(:new)
        expect(Packages::Rubygems::CreateDependenciesService).not_to receive(:new)

        subject

        expect(package.reload.name).to eq('temp_name')
        expect(package.version).to eq('0.0.0')
        expect(package).to be_processing
        expect(package_file.reload.file_name).to eq('package.gem')
      end
    end

    context 'with invalid metadata' do
      include_context 'with invalid Rubygems metadata'

      it 'raises the correct error' do
        expect { subject }
          .to raise_error(::Packages::Rubygems::ProcessGemService::InvalidMetadataError, 'Invalid metadata')
      end
    end
  end
end


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

module Packages
  module Rubygems
    class MetadataExtractionService
      def initialize(package, gemspec)
        @package = package
        @gemspec = gemspec
      end

      def execute
        write_metadata
      end

      private

      attr_reader :package, :gemspec

      # rubocop:disable Metrics/AbcSize
      # rubocop:disable Metrics/PerceivedComplexity
      # rubocop:disable Metrics/CyclomaticComplexity
      def write_metadata
        metadatum.update!(
          authors: gemspec&.authors,
          files: gemspec&.files&.to_json,
          summary: gemspec&.summary,
          description: gemspec&.description,
          email: gemspec&.email,
          homepage: gemspec&.homepage,
          licenses: gemspec&.licenses&.to_json,
          metadata: gemspec&.metadata&.to_json,
          author: gemspec&.author,
          bindir: gemspec&.bindir,
          executables: gemspec&.executables&.to_json,
          extensions: gemspec&.extensions&.to_json,
          extra_rdoc_files: gemspec&.extra_rdoc_files&.to_json,
          platform: gemspec&.platform,
          post_install_message: gemspec&.post_install_message,
          rdoc_options: gemspec&.rdoc_options&.to_json,
          require_paths: gemspec&.require_paths&.to_json,
          required_ruby_version: gemspec&.required_ruby_version&.to_s,
          required_rubygems_version: gemspec&.required_rubygems_version&.to_s,
          requirements: gemspec&.requirements&.to_json,
          rubygems_version: gemspec&.rubygems_version
        )
      end
      # rubocop:enable Metrics/AbcSize
      # rubocop:enable Metrics/PerceivedComplexity
      # rubocop:enable Metrics/CyclomaticComplexity

      def metadatum
        # safe_find_or_create_by! was originally called here.
        # We merely switched to `find_or_create_by!`
        # rubocop: disable CodeReuse/ActiveRecord
        Packages::Rubygems::Metadatum.find_or_create_by!(package: package)
        # rubocop: enable CodeReuse/ActiveRecord
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'
require 'rubygems/package'

RSpec.describe Packages::Rubygems::MetadataExtractionService, feature_category: :package_registry do
  include RubygemsHelpers

  let_it_be(:package) { create(:rubygems_package) }
  let_it_be(:package_file) { create(:package_file, :gem) }
  let_it_be(:gem) { gem_from_file(package_file.file) }
  let_it_be(:gemspec) { gem.spec }

  let(:service) { described_class.new(package, gemspec) }

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

    it 'creates the metadata' do
      expect { subject }.to change { Packages::Rubygems::Metadatum.count }.by(1)
    end

    it 'stores the metadata', :aggregate_failures do
      subject

      metadata = package.rubygems_metadatum

      expect(metadata.authors).to eq(gemspec.authors.to_json)
      expect(metadata.files).to eq(gemspec.files.to_json)
      expect(metadata.summary).to eq(gemspec.summary)
      expect(metadata.description).to eq(gemspec.description)
      expect(metadata.email).to eq(gemspec.email)
      expect(metadata.homepage).to eq(gemspec.homepage)
      expect(metadata.licenses).to eq(gemspec.licenses.to_json)
      expect(metadata.metadata).to eq(gemspec.metadata.to_json)
      expect(metadata.author).to eq(gemspec.author)
      expect(metadata.bindir).to eq(gemspec.bindir)
      expect(metadata.executables).to eq(gemspec.executables.to_json)
      expect(metadata.extensions).to eq(gemspec.extensions.to_json)
      expect(metadata.extra_rdoc_files).to eq(gemspec.extra_rdoc_files.to_json)
      expect(metadata.platform).to eq(gemspec.platform)
      expect(metadata.post_install_message).to eq(gemspec.post_install_message)
      expect(metadata.rdoc_options).to eq(gemspec.rdoc_options.to_json)
      expect(metadata.require_paths).to eq(gemspec.require_paths.to_json)
      expect(metadata.required_ruby_version).to eq(gemspec.required_ruby_version.to_s)
      expect(metadata.required_rubygems_version).to eq(gemspec.required_rubygems_version.to_s)
      expect(metadata.requirements).to eq(gemspec.requirements.to_json)
      expect(metadata.rubygems_version).to eq(gemspec.rubygems_version)
    end

    context 'with an existing metadatum' do
      let_it_be(:metadatum) { create(:rubygems_metadatum, package: package) }

      it 'updates it' do
        expect { subject }.not_to change { Packages::Rubygems::Metadatum.count }
      end
    end
  end
end


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

module Packages
  module Rubygems
    class CreateDependenciesService
      def initialize(package, gemspec)
        @package = package
        @gemspec = gemspec
      end

      def execute
        set_dependencies
      end

      private

      attr_reader :package, :gemspec

      def set_dependencies
        Packages::Dependency.transaction do
          dependency_type_rows = gemspec.dependencies.map do |dependency|
            dependency = Packages::Dependency.safe_find_or_create_by!(
              name: dependency.name,
              version_pattern: dependency.requirement.to_s
            )

            {
              dependency_id: dependency.id,
              package_id: package.id,
              dependency_type: :dependencies
            }
          end

          package.dependency_links.upsert_all(
            dependency_type_rows,
            unique_by: %i[package_id dependency_id dependency_type]
          )
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Rubygems::CreateDependenciesService, feature_category: :package_registry do
  include RubygemsHelpers

  let_it_be(:package) { create(:rubygems_package) }
  let_it_be(:package_file) { create(:package_file, :gem) }
  let_it_be(:gem) { gem_from_file(package_file.file) }
  let_it_be(:gemspec) { gem.spec }

  let(:service) { described_class.new(package, gemspec) }

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

    it 'creates dependencies', :aggregate_failures do
      expect { subject }.to change { Packages::Dependency.count }.by(4)

      gemspec.dependencies.each do |dependency|
        persisted_dependency = Packages::Dependency.find_by(name: dependency.name)

        expect(persisted_dependency.version_pattern).to eq dependency.requirement.to_s
      end
    end

    it 'links dependencies to the package' do
      expect { subject }.to change { package.dependency_links.count }.by(4)

      expect(package.dependency_links.first).to be_dependencies
    end
  end
end


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

module Packages
  module Rubygems
    class CreateGemspecService
      def initialize(package, gemspec)
        @package = package
        @gemspec = gemspec
      end

      def execute
        write_gemspec_to_file
      end

      private

      attr_reader :package, :gemspec

      def write_gemspec_to_file
        file = Tempfile.new

        begin
          content = gemspec.to_ruby
          file.write(content)
          file.flush

          md5 = Gitlab::FIPS.enabled? ? nil : Digest::MD5.hexdigest(content)

          package.package_files.create!(
            file: file,
            size: file.size,
            file_name: "#{gemspec.name}.gemspec",
            file_sha1: Digest::SHA1.hexdigest(content),
            file_md5: md5,
            file_sha256: Digest::SHA256.hexdigest(content)
          )
        ensure
          file.close
          file.unlink
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Rubygems::CreateGemspecService, feature_category: :package_registry do
  include RubygemsHelpers

  let_it_be(:package_file) { create(:package_file, :gem) }
  let_it_be(:gem) { gem_from_file(package_file.file) }
  let_it_be(:gemspec) { gem.spec }
  let_it_be(:package) { package_file.package }

  let(:service) { described_class.new(package, gemspec) }

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

    it 'creates a new package file', :aggregate_failures do
      expect { subject }.to change { package.package_files.count }.by(1)

      gemspec_file = package.package_files.find_by(file_name: "#{gemspec.name}.gemspec")
      expect(gemspec_file.file).not_to be_nil
      expect(gemspec_file.size).not_to be_nil
      expect(gemspec_file.file_md5).not_to be_nil
      expect(gemspec_file.file_sha1).not_to be_nil
      expect(gemspec_file.file_sha256).not_to be_nil
    end

    context 'with FIPS mode', :fips_mode do
      it 'does not generate file_md5' do
        expect { subject }.to change { package.package_files.count }.by(1)

        gemspec_file = package.package_files.find_by(file_name: "#{gemspec.name}.gemspec")
        expect(gemspec_file.file).not_to be_nil
        expect(gemspec_file.size).not_to be_nil
        expect(gemspec_file.file_md5).to be_nil
        expect(gemspec_file.file_sha1).not_to be_nil
        expect(gemspec_file.file_sha256).not_to be_nil
      end
    end
  end
end


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

module Packages
  module Cleanup
    class UpdatePolicyService < BaseProjectService
      ALLOWED_ATTRIBUTES = %i[keep_n_duplicated_package_files].freeze

      def execute
        return ServiceResponse.error(message: 'Access denied') unless allowed?

        if policy.update(policy_params)
          ServiceResponse.success(payload: { packages_cleanup_policy: policy })
        else
          ServiceResponse.error(message: policy.errors.full_messages.to_sentence || 'Bad request')
        end
      end

      private

      def policy
        project.packages_cleanup_policy
      end
      strong_memoize_attr :policy

      def allowed?
        can?(current_user, :admin_package, project)
      end

      def policy_params
        params.slice(*ALLOWED_ATTRIBUTES)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Cleanup::UpdatePolicyService, feature_category: :package_registry do
  using RSpec::Parameterized::TableSyntax

  let_it_be_with_reload(:project) { create(:project) }
  let_it_be(:current_user) { create(:user) }

  let(:params) { { keep_n_duplicated_package_files: 50 } }

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

    shared_examples 'creating the policy' do
      it 'creates a new one' do
        expect { subject }.to change { ::Packages::Cleanup::Policy.count }.from(0).to(1)

        expect(subject.payload[:packages_cleanup_policy]).to be_present
        expect(subject.success?).to be_truthy
        expect(project.packages_cleanup_policy).to be_persisted
        expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
      end

      context 'with invalid parameters' do
        let(:params) { { keep_n_duplicated_package_files: 100 } }

        it 'does not create one' do
          expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }

          expect(subject.status).to eq(:error)
          expect(subject.message).to eq('Keep n duplicated package files is invalid')
        end
      end
    end

    shared_examples 'updating the policy' do
      it 'updates the existing one' do
        expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }

        expect(subject.payload[:packages_cleanup_policy]).to be_present
        expect(subject.success?).to be_truthy
        expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
      end

      context 'with invalid parameters' do
        let(:params) { { keep_n_duplicated_package_files: 100 } }

        it 'does not update one' do
          expect { subject }.not_to change { policy.keep_n_duplicated_package_files }

          expect(subject.status).to eq(:error)
          expect(subject.message).to eq('Keep n duplicated package files is invalid')
        end
      end
    end

    shared_examples 'denying access' do
      it 'returns an error' do
        subject

        expect(subject.message).to eq('Access denied')
        expect(subject.status).to eq(:error)
      end
    end

    context 'with existing container expiration policy' do
      let_it_be(:policy) { create(:packages_cleanup_policy, project: project) }

      where(:user_role, :shared_examples_name) do
        :maintainer | 'updating the policy'
        :developer  | 'denying access'
        :reporter   | 'denying access'
        :guest      | 'denying access'
        :anonymous  | 'denying access'
      end

      with_them do
        before do
          project.send("add_#{user_role}", current_user) unless user_role == :anonymous
        end

        it_behaves_like params[:shared_examples_name]
      end
    end

    context 'without existing container expiration policy' do
      where(:user_role, :shared_examples_name) do
        :maintainer | 'creating the policy'
        :developer  | 'denying access'
        :reporter   | 'denying access'
        :guest      | 'denying access'
        :anonymous  | 'denying access'
      end

      with_them do
        before do
          project.send("add_#{user_role}", current_user) unless user_role == :anonymous
        end

        it_behaves_like params[:shared_examples_name]
      end
    end
  end
end


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

module Packages
  module Cleanup
    class ExecutePolicyService
      include Gitlab::Utils::StrongMemoize

      MAX_EXECUTION_TIME = 250.seconds

      DUPLICATED_FILES_BATCH_SIZE = 10_000
      MARK_PACKAGE_FILES_FOR_DESTRUCTION_SERVICE_BATCH_SIZE = 200

      def initialize(policy)
        @policy = policy
        @counts = {
          marked_package_files_total_count: 0,
          unique_package_id_and_file_name_total_count: 0
        }
      end

      def execute
        cleanup_duplicated_files
      end

      private

      def cleanup_duplicated_files
        return if @policy.keep_n_duplicated_package_files_disabled?

        result = installable_package_files.each_batch(of: DUPLICATED_FILES_BATCH_SIZE) do |package_files|
          break :timeout if cleanup_duplicated_files_on(package_files) == :timeout
        end

        response_success(timeout: result == :timeout)
      end

      def cleanup_duplicated_files_on(package_files)
        unique_package_id_and_file_name_from(package_files).each do |package_id, file_name|
          result = remove_duplicated_files_for(package_id: package_id, file_name: file_name)
          @counts[:marked_package_files_total_count] += result.payload[:marked_package_files_count]
          @counts[:unique_package_id_and_file_name_total_count] += 1

          break :timeout unless result.success?
        end
      end

      def unique_package_id_and_file_name_from(package_files)
        # This is a highly custom query for this service, that's why it's not in the model.
        # rubocop: disable CodeReuse/ActiveRecord
        package_files.group(:package_id, :file_name)
          .having("COUNT(*) > #{@policy.keep_n_duplicated_package_files}")
          .pluck(:package_id, :file_name)
        # rubocop: enable CodeReuse/ActiveRecord
      end

      def remove_duplicated_files_for(package_id:, file_name:)
        base = ::Packages::PackageFile.for_package_ids(package_id)
                 .installable
                 .with_file_name(file_name)
        ids_to_keep = base.recent
                        .limit(@policy.keep_n_duplicated_package_files)
                        .pluck_primary_key

        duplicated_package_files = base.id_not_in(ids_to_keep)
        ::Packages::MarkPackageFilesForDestructionService.new(duplicated_package_files)
          .execute(batch_deadline: batch_deadline, batch_size: MARK_PACKAGE_FILES_FOR_DESTRUCTION_SERVICE_BATCH_SIZE)
      end

      def project
        @policy.project
      end

      def installable_package_files
        ::Packages::PackageFile.installable
          .for_package_ids(
            ::Packages::Package.installable
              .for_projects(project.id)
          )
      end

      def batch_deadline
        MAX_EXECUTION_TIME.from_now
      end
      strong_memoize_attr :batch_deadline

      def response_success(timeout:)
        ServiceResponse.success(
          message: "Packages cleanup policy executed for project #{project.id}",
          payload: {
            timeout: timeout,
            counts: @counts
          }
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Cleanup::ExecutePolicyService, feature_category: :package_registry do
  let_it_be(:project) { create(:project) }
  let_it_be_with_reload(:policy) { create(:packages_cleanup_policy, project: project) }

  let(:service) { described_class.new(policy) }

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

    context 'with the keep_n_duplicated_files parameter' do
      let_it_be(:package1) { create(:package, project: project) }
      let_it_be(:package2) { create(:package, project: project) }
      let_it_be(:package3) { create(:package, project: project) }
      let_it_be(:package4) { create(:package, :pending_destruction, project: project) }

      let_it_be(:package_file1_1) { create(:package_file, package: package1, file_name: 'file_name1') }
      let_it_be(:package_file1_2) { create(:package_file, package: package1, file_name: 'file_name1') }
      let_it_be(:package_file1_3) { create(:package_file, package: package1, file_name: 'file_name1') }

      let_it_be(:package_file1_4) { create(:package_file, package: package1, file_name: 'file_name2') }
      let_it_be(:package_file1_5) { create(:package_file, package: package1, file_name: 'file_name2') }
      let_it_be(:package_file1_6) { create(:package_file, package: package1, file_name: 'file_name2') }
      let_it_be(:package_file1_7) do
        create(:package_file, :pending_destruction, package: package1, file_name: 'file_name2')
      end

      let_it_be(:package_file2_1) { create(:package_file, package: package2, file_name: 'file_name1') }
      let_it_be(:package_file2_2) { create(:package_file, package: package2, file_name: 'file_name1') }
      let_it_be(:package_file2_3) { create(:package_file, package: package2, file_name: 'file_name1') }
      let_it_be(:package_file2_4) { create(:package_file, package: package2, file_name: 'file_name1') }

      let_it_be(:package_file3_1) { create(:package_file, package: package3, file_name: 'file_name_test') }

      let_it_be(:package_file4_1) { create(:package_file, package: package4, file_name: 'file_name1') }
      let_it_be(:package_file4_2) { create(:package_file, package: package4, file_name: 'file_name1') }

      let(:package_files_1) { package1.package_files.installable }
      let(:package_files_2) { package2.package_files.installable }
      let(:package_files_3) { package3.package_files.installable }

      context 'set to less than the total number of duplicated files' do
        before do
          # for each package file duplicate, we keep only the most recent one
          policy.update!(keep_n_duplicated_package_files: '1')
        end

        shared_examples 'keeping the most recent package files' do
          let(:response_payload) do
            {
              counts: {
                marked_package_files_total_count: 7,
                unique_package_id_and_file_name_total_count: 3
              },
              timeout: false
            }
          end

          it 'only keeps the most recent package files' do
            expect { execute }.to change { ::Packages::PackageFile.installable.count }.by(-7)

            expect(package_files_1).to contain_exactly(package_file1_3, package_file1_6)
            expect(package_files_2).to contain_exactly(package_file2_4)
            expect(package_files_3).to contain_exactly(package_file3_1)

            expect(execute).to be_success
            expect(execute.message).to eq("Packages cleanup policy executed for project #{project.id}")
            expect(execute.payload).to eq(response_payload)
          end
        end

        it_behaves_like 'keeping the most recent package files'

        context 'when the service needs to loop' do
          before do
            stub_const("#{described_class.name}::DUPLICATED_FILES_BATCH_SIZE", 2)
          end

          it_behaves_like 'keeping the most recent package files' do
            before do
              expect(::Packages::MarkPackageFilesForDestructionService)
                .to receive(:new).exactly(3).times.and_call_original
            end
          end

          context 'when a timeout is hit' do
            let(:response_payload) do
              {
                counts: {
                  marked_package_files_total_count: 4,
                  unique_package_id_and_file_name_total_count: 3
                },
                timeout: true
              }
            end

            let(:service_timeout_response) do
              ServiceResponse.error(
                message: 'Timeout while marking package files as pending destruction',
                payload: { marked_package_files_count: 0 }
              )
            end

            before do
              mock_service_timeout(on_iteration: 3)
            end

            it 'keeps part of the most recent package files' do
              expect { execute }
                .to change { ::Packages::PackageFile.installable.count }.by(-4)
                .and not_change { package_files_2.count } # untouched because of the timeout
                .and not_change { package_files_3.count } # untouched because of the timeout

              expect(package_files_1).to contain_exactly(package_file1_3, package_file1_6)
              expect(execute).to be_success
              expect(execute.message).to eq("Packages cleanup policy executed for project #{project.id}")
              expect(execute.payload).to eq(response_payload)
            end

            def mock_service_timeout(on_iteration:)
              execute_call_count = 1
              expect_next_instances_of(::Packages::MarkPackageFilesForDestructionService, 3) do |service|
                expect(service).to receive(:execute).and_wrap_original do |m, *args, **kwargs|
                  # timeout if we are on the right iteration
                  if execute_call_count == on_iteration
                    service_timeout_response
                  else
                    execute_call_count += 1
                    m.call(*args, **kwargs)
                  end
                end
              end
            end
          end
        end
      end

      context 'set to more than the total number of duplicated files' do
        before do
          # using the biggest value for keep_n_duplicated_package_files
          policy.update!(keep_n_duplicated_package_files: '50')
        end

        it 'keeps all package files' do
          expect { execute }.not_to change { ::Packages::PackageFile.installable.count }
        end
      end

      context 'set to all' do
        before do
          policy.update!(keep_n_duplicated_package_files: 'all')
        end

        it 'skips the policy' do
          expect(::Packages::MarkPackageFilesForDestructionService).not_to receive(:new)
          expect { execute }.not_to change { ::Packages::PackageFile.installable.count }
        end
      end
    end
  end
end


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

module Packages
  module Protection
    class UpdateRuleService
      include Gitlab::Allowable

      ALLOWED_ATTRIBUTES = %i[
        package_name_pattern
        package_type
        push_protected_up_to_access_level
      ].freeze

      def initialize(package_protection_rule, current_user:, params:)
        if package_protection_rule.blank? || current_user.blank?
          raise ArgumentError,
            'package_protection_rule and current_user must be set'
        end

        @package_protection_rule = package_protection_rule
        @current_user = current_user
        @params = params || {}
      end

      def execute
        unless can?(current_user, :admin_package, package_protection_rule.project)
          error_message = _('Unauthorized to update a package protection rule')
          return service_response_error(message: error_message)
        end

        package_protection_rule.update(params.slice(*ALLOWED_ATTRIBUTES))

        if package_protection_rule.errors.present?
          return service_response_error(message: package_protection_rule.errors.full_messages)
        end

        ServiceResponse.success(payload: { package_protection_rule: package_protection_rule })
      rescue StandardError => e
        service_response_error(message: e.message)
      end

      private

      attr_reader :package_protection_rule, :current_user, :params

      def service_response_error(message:)
        ServiceResponse.error(
          message: message,
          payload: { package_protection_rule: nil }
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Protection::UpdateRuleService, '#execute', feature_category: :environment_management do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
  let_it_be_with_reload(:package_protection_rule) { create(:package_protection_rule, project: project) }

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

  let(:params) do
    attributes_for(
      :package_protection_rule,
      package_name_pattern: "#{package_protection_rule.package_name_pattern}-updated",
      package_type: 'npm',
      push_protected_up_to_access_level: 'owner'
    )
  end

  subject(:service_execute) { service.execute }

  shared_examples 'a successful service response' do
    let(:expected_attributes) { params }

    it { is_expected.to be_success }

    it do
      is_expected.to have_attributes(
        errors: be_blank,
        message: be_blank,
        payload: { package_protection_rule: be_a(Packages::Protection::Rule).and(have_attributes(expected_attributes)) }
      )
    end

    it { expect { subject }.not_to change { Packages::Protection::Rule.count } }

    it { subject.tap { expect(package_protection_rule.reload).to have_attributes expected_attributes } }
  end

  shared_examples 'an erroneous service response' do
    it { is_expected.to be_error }

    it do
      is_expected.to have_attributes(
        errors: be_present,
        message: be_present,
        payload: { package_protection_rule: nil }
      )
    end

    it { expect { subject }.not_to change { Packages::Protection::Rule.count } }
    it { expect { subject }.not_to change { package_protection_rule.reload.updated_at } }
  end

  it_behaves_like 'a successful service response'

  context 'with disallowed params' do
    let(:params) { super().merge!(project_id: 1, unsupported_param: 'unsupported_param_value') }

    it_behaves_like 'a successful service response' do
      let(:expected_attributes) { params.except(:project_id, :unsupported_param) }
    end
  end

  context 'when fields are invalid' do
    let(:params) do
      { package_name_pattern: '', package_type: 'unknown_package_type',
        push_protected_up_to_access_level: 1000 }
    end

    it_behaves_like 'an erroneous service response'

    it { is_expected.to have_attributes message: /'unknown_package_type' is not a valid package_type/ }
  end

  context 'with empty params' do
    let(:params) { {} }

    it_behaves_like 'a successful service response' do
      let(:expected_attributes) { package_protection_rule.attributes }
    end

    it { expect { service_execute }.not_to change { package_protection_rule.reload.updated_at } }
  end

  context 'with nil params' do
    let(:params) { nil }

    it_behaves_like 'a successful service response' do
      let(:expected_attributes) { package_protection_rule.attributes }
    end

    it { expect { service_execute }.not_to change { package_protection_rule.reload.updated_at } }
  end

  context 'when updated field `package_name_pattern` is already taken' do
    let_it_be_with_reload(:other_existing_package_protection_rule) do
      create(:package_protection_rule, project: project,
        package_name_pattern: "#{package_protection_rule.package_name_pattern}-other")
    end

    let(:params) { { package_name_pattern: other_existing_package_protection_rule.package_name_pattern } }

    it_behaves_like 'an erroneous service response'

    it do
      expect { service_execute }.not_to(
        change { other_existing_package_protection_rule.reload.package_name_pattern }
      )
    end

    it do
      is_expected.to have_attributes(
        errors: match_array([/Package name pattern has already been taken/]),
        message: match_array([/Package name pattern has already been taken/])
      )
    end
  end

  context 'when current_user does not have permission' do
    let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
    let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
    let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
    let_it_be(:anonymous) { create(:user) }

    where(:current_user) do
      [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
    end

    with_them do
      it_behaves_like 'an erroneous service response'

      it { is_expected.to have_attributes errors: match_array(/Unauthorized/), message: /Unauthorized/ }
    end
  end

  context 'without package protection rule' do
    let(:package_protection_rule) { nil }
    let(:params) { {} }

    it { expect { service_execute }.to raise_error(ArgumentError) }
  end

  context 'without current_user' do
    let(:current_user) { nil }

    it { expect { service_execute }.to raise_error(ArgumentError) }
  end
end


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

module Packages
  module Protection
    class CreateRuleService < BaseProjectService
      ALLOWED_ATTRIBUTES = %i[
        package_name_pattern
        package_type
        push_protected_up_to_access_level
      ].freeze

      def execute
        unless can?(current_user, :admin_package, project)
          error_message = _('Unauthorized to create a package protection rule')
          return service_response_error(message: error_message)
        end

        package_protection_rule = project.package_protection_rules.create(params.slice(*ALLOWED_ATTRIBUTES))

        unless package_protection_rule.persisted?
          return service_response_error(message: package_protection_rule.errors.full_messages)
        end

        ServiceResponse.success(payload: { package_protection_rule: package_protection_rule })
      rescue StandardError => e
        service_response_error(message: e.message)
      end

      private

      def service_response_error(message:)
        ServiceResponse.error(
          message: message,
          payload: { package_protection_rule: nil }
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Protection::CreateRuleService, '#execute', feature_category: :package_registry do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }

  let(:service) { described_class.new(project: project, current_user: current_user, params: params) }
  let(:current_user) { maintainer }
  let(:params) { attributes_for(:package_protection_rule) }

  subject { service.execute }

  shared_examples 'a successful service response' do
    let(:package_protection_rule_count_expected) { 1 }
    it { is_expected.to be_success }

    it do
      is_expected.to have_attributes(
        payload: include(
          package_protection_rule: be_a(Packages::Protection::Rule)
        )
      )
    end

    it { expect(subject.payload).to include(package_protection_rule: be_a(Packages::Protection::Rule)) }

    it do
      expect { subject }.to change { Packages::Protection::Rule.count }.by(1)

      expect(Packages::Protection::Rule.where(project: project).count).to eq package_protection_rule_count_expected
      expect(Packages::Protection::Rule.where(project: project,
        package_name_pattern: params[:package_name_pattern])).to exist
    end
  end

  shared_examples 'an erroneous service response' do
    let(:package_protection_rule_count_expected) { 0 }
    it { is_expected.to be_error }
    it { is_expected.to have_attributes(payload: include(package_protection_rule: nil)) }

    it do
      expect { subject }.to change { Packages::Protection::Rule.count }.by(0)

      expect(Packages::Protection::Rule.where(project: project).count).to eq package_protection_rule_count_expected
      expect(Packages::Protection::Rule.where(project: project,
        package_name_pattern: params[:package_name_pattern])).not_to exist
    end
  end

  context 'without existing PackageProtectionRules' do
    context 'when fields are valid' do
      it_behaves_like 'a successful service response'
    end

    context 'when fields are invalid' do
      let(:params) do
        {
          package_name_pattern: '',
          package_type: 'unknown_package_type',
          push_protected_up_to_access_level: 1000
        }
      end

      it_behaves_like 'an erroneous service response'
    end
  end

  context 'with existing PackageProtectionRule' do
    let_it_be(:existing_package_protection_rule) { create(:package_protection_rule, project: project) }

    context 'when package name pattern is slightly different' do
      let(:params) do
        attributes_for(
          :package_protection_rule,
          # The field `package_name_pattern` is unique; this is why we change the value in a minimum way
          package_name_pattern: "#{existing_package_protection_rule.package_name_pattern}-unique",
          package_type: existing_package_protection_rule.package_type,
          push_protected_up_to_access_level: existing_package_protection_rule.push_protected_up_to_access_level
        )
      end

      it_behaves_like 'a successful service response' do
        let(:package_protection_rule_count_expected) { 2 }
      end
    end

    context 'when field `package_name_pattern` is taken' do
      let(:params) do
        attributes_for(
          :package_protection_rule,
          package_name_pattern: existing_package_protection_rule.package_name_pattern,
          package_type: existing_package_protection_rule.package_type,
          push_protected_up_to_access_level: existing_package_protection_rule.push_protected_up_to_access_level
        )
      end

      it { is_expected.to be_error }

      it do
        expect { subject }.to change { Packages::Protection::Rule.count }.by(0)

        expect(Packages::Protection::Rule.where(project: project).count).to eq 1
        expect(
          Packages::Protection::Rule.where(
            project: project,
            package_name_pattern: params[:package_name_pattern]
          )
        ).to exist
      end
    end
  end

  context 'when disallowed params are passed' do
    let(:params) do
      attributes_for(:package_protection_rule)
        .merge(
          project_id: 1,
          unsupported_param: 'unsupported_param_value'
        )
    end

    it_behaves_like 'a successful service response'
  end

  context 'with forbidden user access level (project developer role)' do
    # Because of the access level hierarchy, we can assume that
    # other access levels below developer role will also not be able to
    # create package protection rules.
    let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }

    let(:current_user) { developer }

    it_behaves_like 'an erroneous service response'

    it { is_expected.to have_attributes(message: match(/Unauthorized/)) }
  end
end


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

module Packages
  module Protection
    class DeleteRuleService
      include Gitlab::Allowable

      def initialize(package_protection_rule, current_user:)
        if package_protection_rule.blank? || current_user.blank?
          raise ArgumentError,
            'package_protection_rule and current_user must be set'
        end

        @package_protection_rule = package_protection_rule
        @current_user = current_user
      end

      def execute
        unless can?(current_user, :admin_package, package_protection_rule.project)
          error_message = _('Unauthorized to delete a package protection rule')
          return service_response_error(message: error_message)
        end

        deleted_package_protection_rule = package_protection_rule.destroy!

        ServiceResponse.success(payload: { package_protection_rule: deleted_package_protection_rule })
      rescue StandardError => e
        service_response_error(message: e.message)
      end

      private

      attr_reader :package_protection_rule, :current_user

      def service_response_error(message:)
        ServiceResponse.error(
          message: message,
          payload: { package_protection_rule: nil }
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Protection::DeleteRuleService, '#execute', feature_category: :package_registry do
  let_it_be(:project) { create(:project) }
  let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
  let_it_be_with_refind(:package_protection_rule) { create(:package_protection_rule, project: project) }

  subject { described_class.new(package_protection_rule, current_user: current_user).execute }

  shared_examples 'a successful service response' do
    it { is_expected.to be_success }

    it {
      is_expected.to have_attributes(
        errors: be_blank,
        message: be_blank,
        payload: { package_protection_rule: package_protection_rule }
      )
    }

    it { subject.tap { expect { package_protection_rule.reload }.to raise_error ActiveRecord::RecordNotFound } }
  end

  shared_examples 'an erroneous service response' do
    it { is_expected.to be_error }
    it { is_expected.to have_attributes(message: be_present, payload: { package_protection_rule: be_blank }) }

    it do
      expect { subject }.not_to change { Packages::Protection::Rule.count }

      expect { package_protection_rule.reload }.not_to raise_error
    end
  end

  it_behaves_like 'a successful service response'

  it 'deletes the package protection rule in the database' do
    expect { subject }
    .to change { project.reload.package_protection_rules }.from([package_protection_rule]).to([])
    .and change { ::Packages::Protection::Rule.count }.from(1).to(0)
  end

  context 'with deleted package protection rule' do
    let!(:package_protection_rule) do
      create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy!
    end

    it_behaves_like 'a successful service response'
  end

  context 'when error occurs during delete operation' do
    before do
      allow(package_protection_rule).to receive(:destroy!).and_raise(StandardError.new('Some error'))
    end

    it_behaves_like 'an erroneous service response'

    it { is_expected.to have_attributes message: /Some error/ }
  end

  context 'when current_user does not have permission' do
    let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
    let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
    let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
    let_it_be(:anonymous) { create(:user) }

    where(:current_user) do
      [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
    end

    with_them do
      it_behaves_like 'an erroneous service response'

      it { is_expected.to have_attributes message: /Unauthorized to delete a package protection rule/ }
    end
  end

  context 'without package protection rule' do
    let(:package_protection_rule) { nil }

    it { expect { subject }.to raise_error(ArgumentError) }
  end

  context 'without current_user' do
    let(:current_user) { nil }
    let(:package_protection_rule) { build_stubbed(:package_protection_rule, project: project) }

    it { expect { subject }.to raise_error(ArgumentError) }
  end
end


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

module Packages
  module Nuget
    class ExtractRemoteMetadataFileService
      include Gitlab::Utils::StrongMemoize

      ExtractionError = Class.new(StandardError)

      MAX_FILE_SIZE = 4.megabytes.freeze
      METADATA_FILE_EXTENSION = '.nuspec'
      MAX_FRAGMENTS = 5 # nuspec file is usually in the first 2 fragments but we buffer 5 max

      def initialize(remote_url)
        @remote_url = remote_url
      end

      def execute
        raise ExtractionError, 'invalid file url' if remote_url.blank?

        if nuspec_file_content.blank? || !nuspec_file_content.instance_of?(String)
          raise ExtractionError, 'nuspec file not found'
        end

        ServiceResponse.success(payload: nuspec_file_content)
      end

      private

      attr_reader :remote_url

      def nuspec_file_content
        fragments = []

        Gitlab::HTTP.get(remote_url, stream_body: true, allow_object_storage: true) do |fragment|
          break if fragments.size >= MAX_FRAGMENTS

          fragments << fragment
          joined_fragments = fragments.join

          next if joined_fragments.exclude?(METADATA_FILE_EXTENSION)

          nuspec_content = extract_nuspec_file(joined_fragments)

          break nuspec_content if nuspec_content.present?
        end
      end
      strong_memoize_attr :nuspec_file_content

      def extract_nuspec_file(fragments)
        StringIO.open(fragments) do |io|
          Zip::InputStream.open(io) do |zip|
            process_zip_entries(zip)
          end
        rescue Zip::Error => e
          raise ExtractionError, "Error opening zip stream: #{e.message}"
        end
      end

      def process_zip_entries(zip)
        while (entry = zip.get_next_entry) # rubocop:disable Lint/AssignmentInCondition
          next unless entry.name.end_with?(METADATA_FILE_EXTENSION)

          raise ExtractionError, 'nuspec file too big' if entry.size > MAX_FILE_SIZE

          return extract_file_content(entry)
        end
      end

      def extract_file_content(entry)
        Tempfile.create('extract_remote_metadata_file_service') do |file|
          entry.extract(file.path) { true } # allow #extract to overwrite the file
          file.read
        end
      rescue Zip::DecompressionError
        '' # Ignore decompression errors and continue reading the next fragment
      rescue Zip::EntrySizeError => e
        raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::ExtractRemoteMetadataFileService, feature_category: :package_registry do
  let_it_be(:remote_url) { 'http://example.com/package.nupkg' }
  let_it_be(:nupkg_filepath) { 'packages/nuget/package.nupkg' }

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

    context 'when the remote URL is blank' do
      let(:remote_url) { '' }

      it { expect { service.execute }.to raise_error(described_class::ExtractionError, 'invalid file url') }
    end

    context 'when the package file is corrupted' do
      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield('corrupted data')
      end

      it { expect { service.execute }.to raise_error(described_class::ExtractionError, 'nuspec file not found') }
    end

    context 'when reaching the maximum received fragments' do
      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield('Fragment 1').and_yield('Fragment 2').and_yield('Fragment 3').and_yield('Fragment 4')
          .and_yield('Fragment 5').and_yield(fixture_file(nupkg_filepath))
      end

      it { expect { service.execute }.to raise_error(described_class::ExtractionError, 'nuspec file not found') }
    end

    context 'when nuspec file is too big' do
      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield(fixture_file(nupkg_filepath))
        allow_next_instance_of(Zip::Entry) do |instance|
          allow(instance).to receive(:size).and_return(6.megabytes)
        end
      end

      it { expect { service.execute }.to raise_error(described_class::ExtractionError, 'nuspec file too big') }
    end

    context 'when nuspec file is fragmented' do
      let_it_be(:nuspec_path) { expand_fixture_path('packages/nuget/with_metadata.nuspec') }
      let_it_be(:tmp_zip) { Tempfile.new('nuget_zip') }
      let_it_be(:zipped_nuspec) { zip_nuspec_file(nuspec_path, tmp_zip.path).get_raw_input_stream.read }
      let_it_be(:fragments) { zipped_nuspec.chars.each_slice(zipped_nuspec.size / 2).map(&:join) }

      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield(fragments[0]).and_yield(fragments[1])
      end

      after do
        tmp_zip.unlink
      end

      it 'ignores the Zip::DecompressionError and constructs the nuspec file from the fragments' do
        response = service.execute

        expect(response).to be_success
        expect(response.payload).to include('<id>DummyProject.WithMetadata</id>')
          .and include('<version>1.2.3</version>')
      end
    end

    context 'when the remote URL is valid' do
      let(:fragments) { fixture_file(nupkg_filepath).chars.each_slice(1.kilobyte).map(&:join) }

      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield(fragments[0]).and_yield(fragments[1]).and_yield(fragments[2]).and_yield(fragments[3])
      end

      it 'returns a success response with the nuspec file content' do
        response = service.execute

        expect(response).to be_success
        expect(response.payload).to include('<id>DummyProject.DummyPackage</id>')
          .and include('<version>1.0.0</version>')
      end
    end

    context 'with a corrupted nupkg file with a wrong entry size' do
      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield(fixture_file(nupkg_filepath))
        allow_next_instance_of(Zip::Entry) do |instance|
          allow(instance).to receive(:extract).and_raise(Zip::EntrySizeError)
        end
      end

      it {
        expect do
          service.execute
        end.to raise_error(described_class::ExtractionError, /nuspec file has the wrong entry size/)
      }
    end

    context 'with a Zip::Error exception' do
      before do
        allow(Gitlab::HTTP).to receive(:get).with(remote_url, stream_body: true, allow_object_storage: true)
          .and_yield(fixture_file(nupkg_filepath))
        allow(Zip::InputStream).to receive(:open).and_raise(Zip::Error)
      end

      it {
        expect do
          service.execute
        end.to raise_error(described_class::ExtractionError, /Error opening zip stream/)
      }
    end
  end

  def zip_nuspec_file(nuspec_path, zip_path)
    Zip::File.open(zip_path, Zip::File::CREATE) do |zipfile|
      zipfile.add('package.nuspec', nuspec_path)
    end
  end
end


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

module Packages
  module Nuget
    class MetadataExtractionService
      def initialize(package_zip_file)
        @package_zip_file = package_zip_file
      end

      def execute
        ServiceResponse.success(payload: metadata)
      end

      private

      attr_reader :package_zip_file

      def metadata
        ::Packages::Nuget::ExtractMetadataContentService
          .new(nuspec_file_content)
          .execute
          .payload
      end

      def nuspec_file_content
        ::Packages::Nuget::ExtractMetadataFileService
          .new(package_zip_file)
          .execute
          .payload
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::MetadataExtractionService, feature_category: :package_registry do
  let_it_be(:package_file) { build(:package_file, :nuget) }
  let(:package_zip_file) { Zip::File.new(package_file.file) }
  let(:service) { described_class.new(package_zip_file) }

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

    let(:nuspec_file_content) do
      <<~XML
        <?xml version="1.0" encoding="utf-8"?>
        <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
          <metadata>
            <id>DummyProject.DummyPackage</id>
            <version>1.0.0</version>
            <title>Dummy package</title>
            <authors>Test</authors>
            <owners>Test</owners>
            <requireLicenseAcceptance>false</requireLicenseAcceptance>
            <description>This is a dummy project</description>
            <dependencies>
              <group targetFramework=".NETCoreApp3.0">
                <dependency id="Newtonsoft.Json" version="12.0.3" exclude="Build,Analyzers" />
              </group>
            </dependencies>
          </metadata>
        </package>
      XML
    end

    let(:expected_metadata) do
      {
        package_name: 'DummyProject.DummyPackage',
        package_version: '1.0.0',
        authors: 'Test',
        description: 'This is a dummy project',
        package_dependencies: [
          {
            name: 'Newtonsoft.Json',
            target_framework: '.NETCoreApp3.0',
            version: '12.0.3'
          }
        ],
        package_tags: [],
        package_types: []
      }
    end

    it 'calls the necessary services and executes the metadata extraction' do
      expect_next_instance_of(Packages::Nuget::ExtractMetadataFileService, package_zip_file) do |service|
        expect(service).to receive(:execute).and_return(ServiceResponse.success(payload: nuspec_file_content))
      end

      expect_next_instance_of(Packages::Nuget::ExtractMetadataContentService, nuspec_file_content) do |service|
        expect(service).to receive(:execute).and_call_original
      end

      expect(subject.payload).to eq(expected_metadata)
    end
  end
end


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

module Packages
  module Nuget
    class ExtractMetadataFileService
      ExtractionError = Class.new(StandardError)

      MAX_FILE_SIZE = 4.megabytes.freeze

      def initialize(package_zip_file)
        @package_zip_file = package_zip_file
      end

      def execute
        ServiceResponse.success(payload: nuspec_file_content)
      end

      private

      attr_reader :package_zip_file

      def nuspec_file_content
        entry = extract_nuspec_file

        raise ExtractionError, 'nuspec file not found' unless entry
        raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size

        Tempfile.create('nuget_extraction_package_file') do |file|
          entry.extract(file.path) { true } # allow #extract to overwrite the file
          file.read
        end
      rescue Zip::EntrySizeError => e
        raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
      end

      def extract_nuspec_file
        if package_zip_file.is_a?(Zip::InputStream)
          while (entry = package_zip_file.get_next_entry) # rubocop:disable Lint/AssignmentInCondition -- Following https://github.com/rubyzip/rubyzip#notes-on-zipinputstream and that's why the disable rubocop rule
            break entry if entry.name.end_with?('.nuspec')
          end
        else
          package_zip_file.glob('*.nuspec').first
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::ExtractMetadataFileService, feature_category: :package_registry do
  include PackagesManagerApiSpecHelpers

  let_it_be(:package_file) { build(:package_file, :nuget) }
  let_it_be(:package_zip_file) { Zip::File.new(package_file.file) }

  let(:service) { described_class.new(package_zip_file) }

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

    shared_examples 'raises an error' do |error_message|
      it { expect { subject }.to raise_error(described_class::ExtractionError, error_message) }
    end

    context 'with valid package file' do
      expected_metadata = <<~XML.squish
        <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
          <metadata>
            <id>DummyProject.DummyPackage</id>
            <version>1.0.0</version>
            <title>Dummy package</title>
            <authors>Test</authors>
            <owners>Test</owners>
            <requireLicenseAcceptance>false</requireLicenseAcceptance>
            <description>This is a dummy project</description>
            <dependencies>
              <group targetFramework=".NETCoreApp3.0">
                <dependency id="Newtonsoft.Json" version="12.0.3" exclude="Build,Analyzers" />
              </group>
            </dependencies>
          </metadata>
        </package>
      XML

      it 'returns the nuspec file content' do
        expect(subject.payload.squish).to include(expected_metadata)
      end

      context 'with InputStream zip' do
        let(:package_zip_file) do
          Zip::InputStream.open(
            temp_file('package.nupkg', content: File.open(package_file.file.path))
          )
        end

        it 'returns the nuspec file content' do
          expect(subject.payload.squish).to include(expected_metadata)
        end
      end
    end

    context 'without the nuspec file' do
      before do
        allow(package_zip_file).to receive(:glob).and_return([])
      end

      it_behaves_like 'raises an error', 'nuspec file not found'
    end

    context 'with a too big nuspec file' do
      before do
        allow(package_zip_file).to receive(:glob).and_return(
          [instance_double(File, size: described_class::MAX_FILE_SIZE + 1)]
        )
      end

      it_behaves_like 'raises an error', 'nuspec file too big'
    end

    context 'with a corrupted nupkg file with a wrong entry size' do
      let(:nupkg_fixture_path) { expand_fixture_path('packages/nuget/corrupted_package.nupkg') }
      let(:package_zip_file) { Zip::File.new(nupkg_fixture_path) }

      it_behaves_like 'raises an error',
        <<~ERROR.squish
          nuspec file has the wrong entry size: entry 'DummyProject.DummyPackage.nuspec' should be 255B,
          but is larger when inflated.
        ERROR
    end
  end
end


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

module Packages
  module Nuget
    class ExtractMetadataContentService
      ROOT_XPATH = '//xmlns:package/xmlns:metadata/xmlns'

      XPATHS = {
        package_name: "#{ROOT_XPATH}:id",
        package_version: "#{ROOT_XPATH}:version",
        authors: "#{ROOT_XPATH}:authors",
        description: "#{ROOT_XPATH}:description",
        license_url: "#{ROOT_XPATH}:licenseUrl",
        project_url: "#{ROOT_XPATH}:projectUrl",
        icon_url: "#{ROOT_XPATH}:iconUrl"
      }.freeze

      XPATH_DEPENDENCIES = "#{ROOT_XPATH}:dependencies/xmlns:dependency".freeze
      XPATH_DEPENDENCY_GROUPS = "#{ROOT_XPATH}:dependencies/xmlns:group".freeze
      XPATH_TAGS = "#{ROOT_XPATH}:tags".freeze
      XPATH_PACKAGE_TYPES = "#{ROOT_XPATH}:packageTypes/xmlns:packageType".freeze

      def initialize(nuspec_file_content)
        @nuspec_file_content = nuspec_file_content
      end

      def execute
        ServiceResponse.success(payload: extract_metadata(nuspec_file_content))
      end

      private

      attr_reader :nuspec_file_content

      def extract_metadata(file)
        doc = Nokogiri::XML(file)

        XPATHS.transform_values { |query| doc.xpath(query).text.presence }
              .compact
              .tap do |metadata|
                metadata[:package_dependencies] = extract_dependencies(doc)
                metadata[:package_tags] = extract_tags(doc)
                metadata[:package_types] = extract_package_types(doc)
              end
      end

      def extract_dependencies(doc)
        dependencies = []

        doc.xpath(XPATH_DEPENDENCIES).each do |node|
          dependencies << extract_dependency(node)
        end

        doc.xpath(XPATH_DEPENDENCY_GROUPS).each do |group_node|
          target_framework = group_node.attr('targetFramework')

          group_node.xpath('xmlns:dependency').each do |node|
            dependencies << extract_dependency(node).merge(target_framework: target_framework)
          end
        end

        dependencies
      end

      def extract_dependency(node)
        {
          name: node.attr('id'),
          version: node.attr('version')
        }.compact
      end

      def extract_tags(doc)
        tags = doc.xpath(XPATH_TAGS).text

        return [] if tags.blank?

        tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
      end

      def extract_package_types(doc)
        doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::ExtractMetadataContentService, feature_category: :package_registry do
  let(:nuspec_file_content) { fixture_file(nuspec_filepath) }

  let(:service) { described_class.new(nuspec_file_content) }

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

    context 'with nuspec file content' do
      context 'with dependencies' do
        let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }

        it { is_expected.to have_key(:package_dependencies) }

        it 'extracts dependencies' do
          dependencies = subject[:package_dependencies]

          expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
          expect(dependencies).to include(name: 'Castle.Core')
          expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7',
            target_framework: '.NETStandard2.0')
          expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3',
            target_framework: '.NETStandard2.0')
        end
      end

      context 'with package types' do
        let(:nuspec_filepath) { 'packages/nuget/with_package_types.nuspec' }

        it { is_expected.to have_key(:package_types) }

        it 'extracts package types' do
          expect(subject[:package_types]).to include('SymbolsPackage')
        end
      end

      context 'with a nuspec file with metadata' do
        let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }

        it { expect(subject[:package_tags].sort).to eq(%w[foo bar test tag1 tag2 tag3 tag4 tag5].sort) }
      end
    end

    context 'with a nuspec file content with metadata' do
      let_it_be(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }

      it 'returns the correct metadata' do
        expected_metadata = {
          authors: 'Author Test',
          description: 'Description Test',
          license_url: 'https://opensource.org/licenses/MIT',
          project_url: 'https://gitlab.com/gitlab-org/gitlab',
          icon_url: 'https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png'
        }

        expect(subject.slice(*expected_metadata.keys)).to eq(expected_metadata)
      end
    end
  end
end


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

module Packages
  module Nuget
    class SearchService < BaseService
      include ::Packages::FinderHelper
      include Gitlab::Utils::StrongMemoize
      include ActiveRecord::ConnectionAdapters::Quoting

      MAX_PER_PAGE = 30
      MAX_VERSIONS_PER_PACKAGE = 10
      PRE_RELEASE_VERSION_MATCHING_TERM = '%-%'

      DEFAULT_OPTIONS = {
        include_prerelease_versions: true,
        per_page: Kaminari.config.default_per_page,
        padding: 0
      }.freeze

      def initialize(current_user, project_or_group, search_term, options = {})
        @current_user = current_user
        @project_or_group = project_or_group
        @search_term = search_term
        @options = DEFAULT_OPTIONS.merge(options)

        raise ArgumentError, 'negative per_page' if per_page < 0
        raise ArgumentError, 'negative padding' if padding < 0
      end

      def execute
        Result.new(
          total_count: non_paginated_matching_package_names.count,
          results: search_packages
        )
      end

      private

      def search_packages
        # custom query to get package names and versions as expected from the nuget search api
        # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24182#technical-notes
        # and https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
        subquery_name = :partition_subquery
        arel_table = Arel::Table.new(subquery_name)
        column_names = Packages::Package.column_names.map do |cn|
          "#{subquery_name}.#{quote_column_name(cn)}"
        end

        # rubocop: disable CodeReuse/ActiveRecord
        pkgs = Packages::Package
        pkgs = pkgs.with(project_ids_cte.to_arel) if use_project_ids_cte?
        pkgs = pkgs.select(column_names.join(','))
                   .from(package_names_partition, subquery_name)
                   .where(arel_table[:row_number].lteq(MAX_VERSIONS_PER_PACKAGE))

        return pkgs if include_prerelease_versions?

        # we can't use pkgs.without_version_like since we have a custom from
        pkgs.where.not(arel_table[:version].matches(PRE_RELEASE_VERSION_MATCHING_TERM))
        # rubocop: enable CodeReuse/ActiveRecord
      end

      def package_names_partition
        # rubocop: disable CodeReuse/ActiveRecord
        table_name = quote_table_name(Packages::Package.table_name)
        name_column = "#{table_name}.#{quote_column_name('name')}"
        created_at_column = "#{table_name}.#{quote_column_name('created_at')}"
        select_sql = "ROW_NUMBER() OVER (PARTITION BY #{name_column} ORDER BY #{created_at_column} DESC) AS row_number, #{table_name}.*"

        nuget_packages.select(select_sql)
                      .with_name(paginated_matching_package_names)
                      .where(project_id: project_ids)
        # rubocop: enable CodeReuse/ActiveRecord
      end

      def paginated_matching_package_names
        pkgs = base_matching_package_names
        pkgs.page(0) # we're using a padding
            .per(per_page)
            .padding(padding)
      end

      def non_paginated_matching_package_names
        # rubocop: disable CodeReuse/ActiveRecord
        pkgs = base_matching_package_names
        pkgs = pkgs.with(project_ids_cte.to_arel) if use_project_ids_cte?
        pkgs
        # rubocop: enable CodeReuse/ActiveRecord
      end

      def base_matching_package_names
        # rubocop: disable CodeReuse/ActiveRecord
        pkgs = nuget_packages.order_name
                               .select_distinct_name
                               .where(project_id: project_ids)
        pkgs = pkgs.without_version_like(PRE_RELEASE_VERSION_MATCHING_TERM) unless include_prerelease_versions?
        pkgs = pkgs.search_by_name(@search_term) if @search_term.present?
        pkgs
        # rubocop: enable CodeReuse/ActiveRecord
      end
      strong_memoize_attr :base_matching_package_names

      def nuget_packages
        Packages::Package.nuget
                         .displayable
                         .has_version
                         .without_nuget_temporary_name
      end

      def project_ids_cte
        return unless use_project_ids_cte?

        query = projects_visible_to_user(@current_user, within_group: @project_or_group)
        Gitlab::SQL::CTE.new(:project_ids, query.select(:id))
      end
      strong_memoize_attr :project_ids_cte

      def project_ids
        return @project_or_group.id if project?

        if use_project_ids_cte?
          # rubocop: disable CodeReuse/ActiveRecord
          Project.select(:id)
                 .from(project_ids_cte.table)
          # rubocop: enable CodeReuse/ActiveRecord
        end
      end

      def use_project_ids_cte?
        group?
      end

      def project?
        @project_or_group.is_a?(::Project)
      end

      def group?
        @project_or_group.is_a?(::Group)
      end

      def include_prerelease_versions?
        @options[:include_prerelease_versions]
      end

      def padding
        @options[:padding]
      end

      def per_page
        [@options[:per_page], MAX_PER_PAGE].min
      end

      class Result
        include ActiveModel::Model

        attr_accessor :results, :total_count
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::SearchService, feature_category: :package_registry do
  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }
  let_it_be(:subgroup) { create(:group, parent: group) }
  let_it_be(:project) { create(:project, namespace: subgroup) }
  let_it_be_with_refind(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
  let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
  let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
  let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
  let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageA') }
  let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageB') }

  let(:search_term) { 'ummy' }
  let(:per_page) { 5 }
  let(:padding) { 0 }
  let(:include_prerelease_versions) { true }
  let(:options) { { include_prerelease_versions: include_prerelease_versions, per_page: per_page, padding: padding } }

  describe '#execute' do
    subject { described_class.new(user, target, search_term, options).execute }

    shared_examples 'handling all the conditions' do
      it { expect_search_results 3, package_a, packages_b, packages_c }

      context 'with a smaller per page count' do
        let(:per_page) { 2 }

        it { expect_search_results 3, package_a, packages_b }
      end

      context 'with 0 per page count' do
        let(:per_page) { 0 }

        it { expect_search_results 3, [] }
      end

      context 'with a negative per page count' do
        let(:per_page) { -1 }

        it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') }
      end

      context 'with a padding' do
        let(:padding) { 2 }

        it { expect_search_results 3, packages_c }
      end

      context 'with a too big padding' do
        let(:padding) { 5 }

        it { expect_search_results 3, [] }
      end

      context 'with a negative padding' do
        let(:padding) { -1 }

        it { expect { subject }.to raise_error(ArgumentError, 'negative padding') }
      end

      context 'with search term' do
        let(:search_term) { 'umm' }

        it { expect_search_results 3, package_a, packages_b, packages_c }
      end

      context 'with nil search term' do
        let(:search_term) { nil }

        it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
      end

      context 'with empty search term' do
        let(:search_term) { '' }

        it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
      end

      context 'with non-displayable packages' do
        let(:search_term) { '' }

        before do
          package_a.update_column(:status, 1)
        end

        it { expect_search_results 3, packages_b, packages_c, package_d }
      end

      context 'with prefix search term' do
        let(:search_term) { 'dummy' }

        it { expect_search_results 3, package_a, packages_b, packages_c }
      end

      context 'with suffix search term' do
        let(:search_term) { 'packagec' }

        it { expect_search_results 1, packages_c }
      end

      context 'with pre release packages' do
        let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }

        context 'including them' do
          it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
        end

        context 'excluding them' do
          let(:include_prerelease_versions) { false }

          it { expect_search_results 3, package_a, packages_b, packages_c }

          context 'when mixed with release versions' do
            let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') }

            it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
          end
        end
      end
    end

    context 'with project' do
      let(:target) { project }

      before do
        project.add_developer(user)
      end

      it_behaves_like 'handling all the conditions'
    end

    context 'with subgroup' do
      let(:target) { subgroup }

      before do
        subgroup.add_developer(user)
      end

      it_behaves_like 'handling all the conditions'
    end

    context 'with group' do
      let(:target) { group }

      before do
        group.add_developer(user)
      end

      it_behaves_like 'handling all the conditions'
    end

    def expect_search_results(total_count, *results)
      search = subject

      expect(search.total_count).to eq total_count
      expect(search.results).to match_array(Array.wrap(results).flatten)
    end
  end
end


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

module Packages
  module Nuget
    class SyncMetadatumService
      include Gitlab::Utils::StrongMemoize

      def initialize(package, metadata)
        @package = package
        @metadata = metadata
      end

      def execute
        if blank_metadata?
          metadatum.destroy! if metadatum.persisted?
        else
          metadatum.update!(
            authors: authors,
            description: description,
            license_url: license_url,
            project_url: project_url,
            icon_url: icon_url
          )
        end
      end

      private

      attr_reader :package, :metadata

      def metadatum
        package.nuget_metadatum || package.build_nuget_metadatum
      end
      strong_memoize_attr :metadatum

      def blank_metadata?
        [authors, description, project_url, license_url, icon_url].all?(&:blank?)
      end

      def authors
        truncate_value(:authors, ::Packages::Nuget::Metadatum::MAX_AUTHORS_LENGTH)
      end
      strong_memoize_attr :authors

      def description
        truncate_value(:description, ::Packages::Nuget::Metadatum::MAX_DESCRIPTION_LENGTH)
      end
      strong_memoize_attr :description

      def project_url
        metadata[:project_url]
      end

      def license_url
        metadata[:license_url]
      end

      def icon_url
        metadata[:icon_url]
      end

      def truncate_value(field, max_length)
        return unless metadata[field]

        if metadata[field].size > max_length
          log_info("#{field.capitalize} is too long (maximum is #{max_length} characters)", field)
        end

        metadata[field].truncate(max_length)
      end

      def log_info(message, field)
        Gitlab::AppLogger.info(
          class: self.class.name,
          message: message,
          package_id: package.id,
          project_id: package.project_id,
          field => metadata[field]
        )
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::SyncMetadatumService, feature_category: :package_registry do
  let_it_be(:package, reload: true) { create(:nuget_package) }
  let_it_be(:metadata) do
    {
      authors: 'Package authors',
      description: 'Package description',
      project_url: 'https://test.org/test',
      license_url: 'https://test.org/MIT',
      icon_url: 'https://test.org/icon.png'
    }
  end

  let(:service) { described_class.new(package, metadata) }
  let(:nuget_metadatum) { package.nuget_metadatum }

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

    RSpec.shared_examples 'saving metadatum attributes' do
      it 'saves nuget metadatum' do
        subject

        metadata.each do |attribute, expected_value|
          expect(nuget_metadatum.send(attribute)).to eq(expected_value)
        end
      end
    end

    it 'creates a nuget metadatum' do
      expect { subject }
        .to change { package.nuget_metadatum.present? }.from(false).to(true)
    end

    it_behaves_like 'saving metadatum attributes'

    context 'with exisiting nuget metadatum' do
      let_it_be(:package) { create(:nuget_package, :with_metadatum) }

      it 'does not create a nuget metadatum' do
        expect { subject }.to change { ::Packages::Nuget::Metadatum.count }.by(0)
      end

      it_behaves_like 'saving metadatum attributes'

      context 'with empty metadata' do
        let_it_be(:metadata) { {} }

        it 'destroys the nuget metadatum' do
          expect { subject }
            .to change { package.reload.nuget_metadatum.present? }.from(true).to(false)
        end
      end
    end

    context 'with metadata containing only authors and description' do
      let_it_be(:metadata) { { authors: 'Package authors 2', description: 'Package description 2' } }

      it 'updates the nuget metadatum' do
        subject

        expect(nuget_metadatum.authors).to eq('Package authors 2')
        expect(nuget_metadatum.description).to eq('Package description 2')
      end
    end

    context 'with too long metadata' do
      let(:metadata) { super().merge(authors: 'a' * 260, description: 'a' * 4010) }
      let(:max_authors_length) { ::Packages::Nuget::Metadatum::MAX_AUTHORS_LENGTH }
      let(:max_description_length) { ::Packages::Nuget::Metadatum::MAX_DESCRIPTION_LENGTH }

      it 'truncates authors and description to the maximum length and logs its info' do
        %i[authors description].each do |field|
          expect(Gitlab::AppLogger).to receive(:info).with(
            class: described_class.name,
            package_id: package.id,
            project_id: package.project_id,
            message: "#{field.capitalize} is too long (maximum is #{send("max_#{field}_length")} characters)",
            field => metadata[field]
          )
        end

        subject

        expect(nuget_metadatum.authors.size).to eq(max_authors_length)
        expect(nuget_metadatum.description.size).to eq(max_description_length)
      end
    end
  end
end


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

module Packages
  module Nuget
    class OdataPackageEntryService
      include API::Helpers::RelatedResourcesHelpers

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

      def execute
        ServiceResponse.success(payload: package_entry)
      end

      private

      attr_reader :project, :params

      def package_entry
        <<-XML.squish
          <entry xmlns='http://www.w3.org/2005/Atom' xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices' xmlns:georss='http://www.georss.org/georss' xmlns:gml='http://www.opengis.net/gml' xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' xml:base="#{xml_base}">
            <id>#{id_url}</id>
            <category term='V2FeedPackage' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme'/>
            <title type='text'>#{params[:package_name]}</title>
            <content type='application/zip' src="#{download_url}"/>
            <m:properties>
              <d:Version>#{params[:package_version]}</d:Version>
            </m:properties>
          </entry>
        XML
      end

      def id_url
        expose_url "#{api_v4_projects_packages_nuget_v2_path(id: project.id)}" \
                   "/Packages(Id='#{params[:package_name]}',Version='#{params[:package_version]}')"
      end

      def download_url
        if params[:package_version].present?
          expose_url api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
            {
              id: project.id,
              package_name: params[:package_name],
              package_version: params[:package_version],
              package_filename: file_name
            },
            true
          )
        else
          xml_base
        end
      end

      def xml_base
        expose_url api_v4_projects_packages_nuget_v2_path(id: project.id)
      end

      def file_name
        "#{params[:package_name]}.#{params[:package_version]}.nupkg"
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Nuget::OdataPackageEntryService, feature_category: :package_registry do
  include GrapePathHelpers::NamedRouteMatcher

  let_it_be(:project) { build_stubbed(:project) }
  let_it_be(:params) { { package_name: 'dummy', package_version: '1.0.0' } }
  let(:doc) { Nokogiri::XML(subject.payload) }

  subject { described_class.new(project, params).execute }

  describe '#execute' do
    shared_examples 'returning a package entry with the correct attributes' do |pkg_version = ''|
      it 'returns a package entry with the correct attributes' do
        expect(doc.root.name).to eq('entry')
        expect(doc_node('id').text).to include(
          id_url(project.id, params[:package_name], pkg_version)
        )
        expect(doc_node('title').text).to eq(params[:package_name])
        expect(doc_node('content').attr('src')).to include(
          content_url(project.id, params[:package_name], pkg_version)
        )
        expect(doc_node('Version').text).to eq(pkg_version)
      end
    end

    context 'when package_version is present' do
      it 'returns a success ServiceResponse' do
        expect(subject).to be_success
      end

      it_behaves_like 'returning a package entry with the correct attributes', '1.0.0'
    end

    context 'when package_version is not present' do
      let(:params) { { package_name: 'dummy', package_version: nil } }

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

      it_behaves_like 'returning a package entry with the correct attributes'
    end
  end

  def doc_node(name)
    doc.css('*').detect { |el| el.name == name }
  end

  def id_url(id, package_name, package_version)
    "api/v4/projects/#{id}/packages/nuget/v2/Packages(Id='#{package_name}',Version='#{package_version}')"
  end

  def content_url(id, package_name, package_version)
    if package_version.present?
      filename = "#{package_name}.#{package_version}.nupkg"
      api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
        { id: id, package_name: package_name, package_version: package_version, package_filename: filename }, true
      )
    else
      api_v4_projects_packages_nuget_v2_path(id: id)
    end
  end
end