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

require 'rubygems/package'

module Packages
  module Helm
    class ExtractFileMetadataService
      ExtractionError = Class.new(StandardError)

      # Charts must be smaller than 1M because of the storage limitations of Kubernetes objects.
      # based on https://helm.sh/docs/chart_template_guide/accessing_files/
      MAX_FILE_SIZE = 1.megabytes.freeze

      def initialize(package_file)
        @package_file = package_file
      end

      def execute
        raise ExtractionError, 'invalid package file' unless valid_package_file?

        metadata
      end

      private

      def valid_package_file?
        @package_file && @package_file.package&.helm? && !@package_file.file.empty_size?
      end

      def metadata
        YAML.safe_load(chart_yaml_content)
      rescue Psych::Exception => e
        raise ExtractionError, "Error while parsing Chart.yaml: #{e.message}"
      end

      def chart_yaml_content
        @package_file.file.use_open_file do |file|
          tar_reader = Gem::Package::TarReader.new(Zlib::GzipReader.new(file))

          chart_yaml = tar_reader.find do |entry|
            next unless entry.file?

            entry.full_name.end_with?('/Chart.yaml')
          end

          raise ExtractionError, 'Chart.yaml not found within a directory' unless chart_yaml
          raise ExtractionError, 'Chart.yaml too big' if chart_yaml.size > MAX_FILE_SIZE

          chart_yaml.read
        ensure
          tar_reader.close
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Helm::ExtractFileMetadataService, feature_category: :package_registry do
  let_it_be(:package_file) { create(:helm_package_file) }

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

  let(:expected) do
    {
      'apiVersion' => 'v2',
      'description' => 'File, Block, and Object Storage Services for your Cloud-Native Environment',
      'icon' => 'https://rook.io/images/rook-logo.svg',
      'name' => 'rook-ceph',
      'sources' => ['https://github.com/rook/rook'],
      'version' => 'v1.5.8'
    }
  end

  subject { service.execute }

  context 'with a valid file' do
    it { is_expected.to eq(expected) }
  end

  context 'without Chart.yaml' do
    before do
      expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
        expect(entry).to receive(:full_name).exactly(:once).and_wrap_original do |m, *args|
          m.call(*args) + '_suffix'
        end
      end
    end

    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Chart.yaml not found within a directory') }
  end

  context 'with Chart.yaml at root' do
    before do
      expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
        expect(entry).to receive(:full_name).exactly(:once).and_return('Chart.yaml')
      end
    end

    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Chart.yaml not found within a directory') }
  end

  context 'with an invalid YAML' do
    before do
      expect_next_instance_of(Gem::Package::TarReader::Entry) do |entry|
        expect(entry).to receive(:read).and_return('{')
      end
    end

    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Error while parsing Chart.yaml: (<unknown>): did not find expected node content while parsing a flow node at line 2 column 1') }
  end

  context 'with a corrupted Chart.yaml of incorrect size' do
    let(:helm_fixture_path) { expand_fixture_path('packages/helm/corrupted_chart.tgz') }
    let(:expected_error_message) { 'Chart.yaml too big' }

    before do
      allow(Zlib::GzipReader).to receive(:new).and_return(Zlib::GzipReader.new(File.open(helm_fixture_path)))
    end

    it 'raises an error with the expected message' do
      expect { subject }.to raise_error(::Packages::Helm::ExtractFileMetadataService::ExtractionError, expected_error_message)
    end
  end
end


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

module Packages
  module Helm
    class ProcessFileService
      include Gitlab::Utils::StrongMemoize
      include ExclusiveLeaseGuard

      ExtractionError = Class.new(StandardError)
      DEFAULT_LEASE_TIMEOUT = 1.hour.to_i

      def initialize(channel, package_file)
        @channel = channel
        @package_file = package_file
      end

      def execute
        raise ExtractionError, 'Helm chart was not processed - package_file is not set' unless package_file

        try_obtain_lease do
          temp_package.transaction do
            rename_package_and_set_version
            rename_package_file_and_set_metadata
            cleanup_temp_package
          end
        end
      end

      private

      attr_reader :channel, :package_file

      def rename_package_and_set_version
        package.update!(
          name: metadata['name'],
          version: metadata['version'],
          status: :default
        )
      end

      def rename_package_file_and_set_metadata
        # 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: file_name,
          file: package_file.file,
          package_id: package.id,
          helm_file_metadatum_attributes: {
            channel: channel,
            metadata: metadata
          }
        )
      end

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

      def temp_package
        package_file.package
      end
      strong_memoize_attr :temp_package

      def package
        project_packages = package_file.package.project.packages
        package = project_packages.with_package_type(:helm)
                                  .with_name(metadata['name'])
                                  .with_version(metadata['version'])
                                  .not_pending_destruction
                                  .last
        package || temp_package
      end
      strong_memoize_attr :package

      def metadata
        ::Packages::Helm::ExtractFileMetadataService.new(package_file).execute
      end
      strong_memoize_attr :metadata

      def file_name
        "#{metadata['name']}-#{metadata['version']}.tgz"
      end

      # used by ExclusiveLeaseGuard
      def lease_key
        "packages:helm:process_file_service:package_file:#{package_file.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::Helm::ProcessFileService, feature_category: :package_registry do
  let(:package) { create(:helm_package, without_package_files: true, status: 'processing') }
  let!(:package_file) { create(:helm_package_file, without_loaded_metadatum: true, package: package) }
  let(:channel) { 'stable' }
  let(:service) { described_class.new(channel, package_file) }

  let(:expected) do
    {
      'apiVersion' => 'v2',
      'description' => 'File, Block, and Object Storage Services for your Cloud-Native Environment',
      'icon' => 'https://rook.io/images/rook-logo.svg',
      'name' => 'rook-ceph',
      'sources' => ['https://github.com/rook/rook'],
      'version' => 'v1.5.8'
    }
  end

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

    context 'without a file' do
      let(:package_file) { nil }

      it 'returns error', :aggregate_failures do
        expect { execute }
          .to not_change { Packages::Package.count }
          .and not_change { Packages::PackageFile.count }
          .and not_change { Packages::Helm::FileMetadatum.count }
          .and raise_error(Packages::Helm::ProcessFileService::ExtractionError, 'Helm chart was not processed - package_file is not set')
      end
    end

    context 'with existing package' do
      let!(:existing_package) { create(:helm_package, project: package.project, name: 'rook-ceph', version: 'v1.5.8') }

      it 'reuses existing package', :aggregate_failures do
        expect { execute }
          .to change { Packages::Package.count }.from(2).to(1)
          .and not_change { package.name }
          .and not_change { package.version }
          .and not_change { package.status }
          .and not_change { Packages::PackageFile.count }
          .and change { package_file.file_name }.from(package_file.file_name).to("#{expected['name']}-#{expected['version']}.tgz")
          .and change { Packages::Helm::FileMetadatum.count }.from(1).to(2)
          .and change { package_file.helm_file_metadatum }.from(nil)

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

        expect(package_file.helm_file_metadatum.channel).to eq(channel)
        expect(package_file.helm_file_metadatum.metadata).to eq(expected)
      end

      context 'marked as pending_destruction' do
        before do
          existing_package.pending_destruction!
        end

        it 'reuses the processing package' do
          expect { execute }
            .to not_change { Packages::Package.count }
            .and not_change { Packages::PackageFile.count }
            .and change { Packages::Helm::FileMetadatum.count }.by(1)
        end
      end
    end

    context 'with a valid file' do
      it 'processes file', :aggregate_failures do
        expect { execute }
          .to not_change { Packages::Package.count }
          .and change { package.name }.from(package.name).to(expected['name'])
          .and change { package.version }.from(package.version).to(expected['version'])
          .and change { package.status }.from('processing').to('default')
          .and not_change { Packages::PackageFile.count }
          .and change { package_file.file_name }.from(package_file.file_name).to("#{expected['name']}-#{expected['version']}.tgz")
          .and change { Packages::Helm::FileMetadatum.count }.by(1)
          .and change { package_file.helm_file_metadatum }.from(nil)

        expect(package_file.helm_file_metadatum.channel).to eq(channel)
        expect(package_file.helm_file_metadatum.metadata).to eq(expected)
      end
    end

    context 'without Chart.yaml' do
      before do
        expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
          expect(entry).to receive(:full_name).exactly(:once).and_wrap_original do |m, *args|
            m.call(*args) + '_suffix'
          end
        end
      end

      it { expect { execute }.to raise_error(Packages::Helm::ExtractFileMetadataService::ExtractionError, 'Chart.yaml not found within a directory') }
    end

    context 'with Chart.yaml at root' do
      before do
        expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
          expect(entry).to receive(:full_name).exactly(:once).and_return('Chart.yaml')
        end
      end

      it { expect { execute }.to raise_error(Packages::Helm::ExtractFileMetadataService::ExtractionError, 'Chart.yaml not found within a directory') }
    end

    context 'with an invalid YAML' do
      before do
        expect_next_instance_of(Gem::Package::TarReader::Entry) do |entry|
          expect(entry).to receive(:read).and_return('{')
        end
      end

      it { expect { execute }.to raise_error(Packages::Helm::ExtractFileMetadataService::ExtractionError, 'Error while parsing Chart.yaml: (<unknown>): did not find expected node content while parsing a flow node at line 2 column 1') }
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module Packages
  module Npm
    class CreatePackageService < ::Packages::CreatePackageService
      include Gitlab::Utils::StrongMemoize
      include ExclusiveLeaseGuard

      PACKAGE_JSON_NOT_ALLOWED_FIELDS = %w[readme readmeFilename licenseText contributors exports].freeze
      DEFAULT_LEASE_TIMEOUT = 1.hour.to_i

      def execute
        return error('Version is empty.', 400) if version.blank?
        return error('Attachment data is empty.', 400) if attachment['data'].blank?
        return error('Package already exists.', 403) if current_package_exists?
        return error('Package protected.', 403) if current_package_protected?
        return error('File is too large.', 400) if file_size_exceeded?

        package = try_obtain_lease do
          ApplicationRecord.transaction { create_npm_package! }
        end

        return error('Could not obtain package lease. Please try again.', 400) unless package

        package
      end

      private

      def create_npm_package!
        package = create_package!(:npm, name: name, version: version)

        ::Packages::CreatePackageFileService.new(package, file_params).execute
        ::Packages::CreateDependencyService.new(package, package_dependencies).execute
        ::Packages::Npm::CreateTagService.new(package, dist_tag).execute

        create_npm_metadatum!(package)

        package
      end

      def create_npm_metadatum!(package)
        package.create_npm_metadatum!(package_json: package_json)
      rescue ActiveRecord::RecordInvalid => e
        if package.npm_metadatum && package.npm_metadatum.errors.where(:package_json, :too_large).any? # rubocop: disable CodeReuse/ActiveRecord
          Gitlab::ErrorTracking.track_exception(e, field_sizes: field_sizes_for_error_tracking)
        end

        raise
      end

      def current_package_exists?
        project.packages
               .npm
               .with_name(name)
               .with_version(version)
               .not_pending_destruction
               .exists?
      end

      def current_package_protected?
        return false if Feature.disabled?(:packages_protected_packages, project)

        user_project_authorization_access_level = current_user.max_member_access_for_project(project.id)
        project.package_protection_rules.push_protected_from?(access_level: user_project_authorization_access_level, package_name: name, package_type: :npm)
      end

      def name
        params[:name]
      end

      def version
        params[:versions].each_key.first
      end
      strong_memoize_attr :version

      def version_data
        params[:versions][version]
      end

      def package_json
        version_data.except(*PACKAGE_JSON_NOT_ALLOWED_FIELDS)
      end

      def dist_tag
        params['dist-tags'].each_key.first
      end

      def package_file_name
        "#{name}-#{version}.tgz"
      end
      strong_memoize_attr :package_file_name

      def attachment
        params['_attachments'][package_file_name]
      end
      strong_memoize_attr :attachment

      # TODO (technical debt): Extract the package size calculation to its own component and unit test it separately.
      def calculated_package_file_size
        # This calculation is based on:
        # 1. 4 chars in a Base64 encoded string are 3 bytes in the original string. Meaning 1 char is 0.75 bytes.
        # 2. The encoded string may have 1 or 2 extra '=' chars used for padding. Each padding char means 1 byte less in the original string.
        # Reference:
        # - https://blog.aaronlenoir.com/2017/11/10/get-original-length-from-base-64-string/
        # - https://en.wikipedia.org/wiki/Base64#Decoding_Base64_with_padding
        encoded_data = attachment['data']
        ((encoded_data.length * 0.75) - encoded_data[-2..].count('=')).to_i
      end
      strong_memoize_attr :calculated_package_file_size

      def file_params
        {
          file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])),
          size: calculated_package_file_size,
          file_sha1: version_data[:dist][:shasum],
          file_name: package_file_name,
          build: params[:build]
        }
      end

      def package_dependencies
        _version, versions_data = params[:versions].first
        versions_data
      end

      def file_size_exceeded?
        project.actual_limits.exceeded?(:npm_max_file_size, calculated_package_file_size)
      end

      # used by ExclusiveLeaseGuard
      def lease_key
        "packages:npm:create_package_service:packages:#{project.id}_#{name}_#{version}"
      end

      # used by ExclusiveLeaseGuard
      def lease_timeout
        DEFAULT_LEASE_TIMEOUT
      end

      def field_sizes
        package_json.transform_values do |value|
          value.to_s.size
        end
      end
      strong_memoize_attr :field_sizes

      def filtered_field_sizes
        field_sizes.select do |_, size|
          size >= ::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING
        end
      end
      strong_memoize_attr :filtered_field_sizes

      def largest_fields
        field_sizes
            .sort_by { |a| a[1] }
            .reverse[0..::Packages::Npm::Metadatum::NUM_FIELDS_FOR_ERROR_TRACKING - 1]
            .to_h
      end
      strong_memoize_attr :largest_fields

      def field_sizes_for_error_tracking
        filtered_field_sizes.empty? ? largest_fields : filtered_field_sizes
      end
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_registry do
  let(:service) { described_class.new(project, user, params) }

  subject { service.execute }

  describe '#execute' do
    include ExclusiveLeaseHelpers

    let_it_be(:namespace) { create(:namespace) }
    let_it_be_with_reload(:project) { create(:project, namespace: namespace) }
    let_it_be(:user) { project.owner }

    let(:version) { '1.0.1' }

    let(:params) do
      Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
          .gsub('@root/npm-test', package_name)
          .gsub('1.0.1', version)).with_indifferent_access
    end

    let(:package_name) { "@#{namespace.path}/my-app" }
    let(:version_data) { params.dig('versions', version) }
    let(:lease_key) { "packages:npm:create_package_service:packages:#{project.id}_#{package_name}_#{version}" }

    shared_examples 'valid package' do
      it 'creates a package' do
        expect { subject }
          .to change { Packages::Package.count }.by(1)
          .and change { Packages::Package.npm.count }.by(1)
          .and change { Packages::Tag.count }.by(1)
          .and change { Packages::Npm::Metadatum.count }.by(1)
      end

      it_behaves_like 'assigns the package creator' do
        let(:package) { subject }
      end

      it { is_expected.to be_valid }

      it 'creates a package with name and version' do
        package = subject

        expect(package.name).to eq(package_name)
        expect(package.version).to eq(version)
      end

      it { expect(subject.npm_metadatum.package_json).to eq(version_data) }

      it { expect(subject.name).to eq(package_name) }
      it { expect(subject.version).to eq(version) }

      context 'with build info' do
        let_it_be(:job) { create(:ci_build, user: user) }
        let(:params) { super().merge(build: job) }

        it_behaves_like 'assigns build to package'
        it_behaves_like 'assigns status to package'

        it 'creates a package file build info' do
          expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
        end
      end

      context 'when the npm metadatum creation results in a size error' do
        shared_examples 'a package json structure size too large error' do
          it 'does not create the package' do
            expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
              instance_of(ActiveRecord::RecordInvalid),
              field_sizes: expected_field_sizes
            )

            expect { subject }.to raise_error(ActiveRecord::RecordInvalid, /structure is too large/)
              .and not_change { Packages::Package.count }
              .and not_change { Packages::Package.npm.count }
              .and not_change { Packages::Tag.count }
              .and not_change { Packages::Npm::Metadatum.count }
          end
        end

        context 'when some of the field sizes are above the error tracking size' do
          let(:package_json) do
            params[:versions][version].except(*::Packages::Npm::CreatePackageService::PACKAGE_JSON_NOT_ALLOWED_FIELDS)
          end

          # Only the fields that exceed the field size limit should be passed to error tracking
          let(:expected_field_sizes) do
            {
              'test' => ('test' * 10000).size,
              'field2' => ('a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING + 1)).size
            }
          end

          before do
            params[:versions][version][:test] = 'test' * 10000
            params[:versions][version][:field1] =
              'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1)
            params[:versions][version][:field2] =
              'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING + 1)
          end

          it_behaves_like 'a package json structure size too large error'
        end

        context 'when all of the field sizes are below the error tracking size' do
          let(:package_json) do
            params[:versions][version].except(*::Packages::Npm::CreatePackageService::PACKAGE_JSON_NOT_ALLOWED_FIELDS)
          end

          let(:expected_size) { ('a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1)).size }
          # Only the five largest fields should be passed to error tracking
          let(:expected_field_sizes) do
            {
              'field1' => expected_size,
              'field2' => expected_size,
              'field3' => expected_size,
              'field4' => expected_size,
              'field5' => expected_size
            }
          end

          before do
            5.times do |i|
              params[:versions][version]["field#{i + 1}"] =
                'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1)
            end
          end

          it_behaves_like 'a package json structure size too large error'
        end
      end

      context 'when the npm metadatum creation results in a different error' do
        it 'does not track the error' do
          error_message = 'boom'
          invalid_npm_metadatum_error = ActiveRecord::RecordInvalid.new(
            build(:npm_metadatum).tap do |metadatum|
              metadatum.errors.add(:base, error_message)
            end
          )

          allow_next_instance_of(::Packages::Package) do |package|
            allow(package).to receive(:create_npm_metadatum!).and_raise(invalid_npm_metadatum_error)
          end

          expect(Gitlab::ErrorTracking).not_to receive(:track_exception)

          expect { subject }.to raise_error(ActiveRecord::RecordInvalid, /#{error_message}/)
        end
      end

      described_class::PACKAGE_JSON_NOT_ALLOWED_FIELDS.each do |field|
        context "with not allowed #{field} field" do
          before do
            params[:versions][version][field] = 'test'
          end

          it 'is persisted without the field' do
            expect { subject }
              .to change { Packages::Package.count }.by(1)
              .and change { Packages::Package.npm.count }.by(1)
              .and change { Packages::Tag.count }.by(1)
              .and change { Packages::Npm::Metadatum.count }.by(1)
            expect(subject.npm_metadatum.package_json[field]).to be_blank
          end
        end
      end
    end

    context 'scoped package' do
      it_behaves_like 'valid package'
    end

    context 'when user is no project member' do
      let_it_be(:user) { create(:user) }

      it_behaves_like 'valid package'
    end

    context 'scoped package not following the naming convention' do
      let(:package_name) { '@any-scope/package' }

      it_behaves_like 'valid package'
    end

    context 'unscoped package' do
      let(:package_name) { 'unscoped-package' }

      it_behaves_like 'valid package'
    end

    context 'package already exists' do
      let(:package_name) { "@#{namespace.path}/my_package" }
      let!(:existing_package) { create(:npm_package, project: project, name: package_name, version: '1.0.1') }

      it { expect(subject[:http_status]).to eq 403 }
      it { expect(subject[:message]).to be 'Package already exists.' }

      context 'marked as pending_destruction' do
        before do
          existing_package.pending_destruction!
        end

        it 'creates a new package' do
          expect { subject }
            .to change { Packages::Package.count }.by(1)
            .and change { Packages::Package.npm.count }.by(1)
            .and change { Packages::Tag.count }.by(1)
            .and change { Packages::Npm::Metadatum.count }.by(1)
        end
      end
    end

    describe 'max file size validation' do
      let(:max_file_size) { 5.bytes }

      shared_examples_for 'max file size validation failure' do
        it 'returns a 400 error', :aggregate_failures do
          expect(subject[:http_status]).to eq 400
          expect(subject[:message]).to be 'File is too large.'
        end
      end

      before do
        project.actual_limits.update!(npm_max_file_size: max_file_size)
      end

      context 'when max file size is exceeded' do
        # NOTE: The base64 encoded package data in the fixture file is the "hello\n" string, whose byte size is 6.
        it_behaves_like 'max file size validation failure'
      end

      context 'when file size is faked by setting the attachment length param to a lower size' do
        let(:params) { super().deep_merge!({ _attachments: { "#{package_name}-#{version}.tgz" => { data: encoded_package_data, length: 1 } } }) }

        # TODO (technical debt): Extract the package size calculation outside the service and add separate specs for it.
        # Right now we have several contexts here to test the calculation's different scenarios.
        context 'when encoded package data is not padded' do
          # 'Hello!' (size = 6 bytes) => 'SGVsbG8h'
          let(:encoded_package_data) { 'SGVsbG8h' }

          it_behaves_like 'max file size validation failure'
        end

        context "when encoded package data is padded with '='" do
          let(:max_file_size) { 4.bytes }
          # 'Hello' (size = 5 bytes) => 'SGVsbG8='
          let(:encoded_package_data) { 'SGVsbG8=' }

          it_behaves_like 'max file size validation failure'
        end

        context "when encoded package data is padded with '=='" do
          let(:max_file_size) { 3.bytes }
          # 'Hell' (size = 4 bytes) => 'SGVsbA=='
          let(:encoded_package_data) { 'SGVsbA==' }

          it_behaves_like 'max file size validation failure'
        end
      end
    end

    context 'with invalid name' do
      where(:package_name) do
        [
          '@inv@lid_scope/package',
          '@scope/sub/group',
          '@scope/../../package',
          '@scope%2e%2e%2fpackage'
        ]
      end

      with_them do
        it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid) }
      end
    end

    context 'with empty versions' do
      let(:params) { super().merge!({ versions: {} }) }

      it { expect(subject[:http_status]).to eq 400 }
      it { expect(subject[:message]).to eq 'Version is empty.' }
    end

    context 'with invalid versions' do
      where(:version) do
        [
          '1',
          '1.2',
          '1./2.3',
          '../../../../../1.2.3',
          '%2e%2e%2f1.2.3'
        ]
      end

      with_them do
        it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Version #{Gitlab::Regex.semver_regex_message}") }
      end
    end

    context 'with empty attachment data' do
      let(:params) { super().merge({ _attachments: { "#{package_name}-#{version}.tgz" => { data: '' } } }) }

      it { expect(subject[:http_status]).to eq 400 }
      it { expect(subject[:message]).to eq 'Attachment data is empty.' }
    end

    it 'obtains a lease to create a new package' do
      expect_to_obtain_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)

      subject
    end

    context 'when the lease is already taken' do
      before do
        stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
      end

      it { expect(subject[:http_status]).to eq 400 }
      it { expect(subject[:message]).to eq 'Could not obtain package lease. Please try again.' }
    end

    context 'when feature flag :packages_protected_packages disabled' do
      let_it_be_with_reload(:package_protection_rule) { create(:package_protection_rule, package_type: :npm, project: project) }

      before do
        stub_feature_flags(packages_protected_packages: false)
      end

      context 'with matching package protection rule for all roles' do
        using RSpec::Parameterized::TableSyntax

        let(:package_name_pattern_no_match) { "#{package_name}_no_match" }

        where(:package_name_pattern, :push_protected_up_to_access_level) do
          ref(:package_name)                  | :developer
          ref(:package_name)                  | :owner
          ref(:package_name_pattern_no_match) | :developer
          ref(:package_name_pattern_no_match) | :owner
        end

        with_them do
          before do
            package_protection_rule.update!(package_name_pattern: package_name_pattern, push_protected_up_to_access_level: push_protected_up_to_access_level)
          end

          it_behaves_like 'valid package'
        end
      end
    end

    context 'with package protection rule for different roles and package_name_patterns' do
      using RSpec::Parameterized::TableSyntax

      let_it_be_with_reload(:package_protection_rule) { create(:package_protection_rule, package_type: :npm, project: project) }
      let_it_be(:project_developer) { create(:user).tap { |u| project.add_developer(u) } }
      let_it_be(:project_maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }

      let(:project_owner) { project.owner }
      let(:package_name_pattern_no_match) { "#{package_name}_no_match" }

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

      shared_examples 'protected package' do
        it { is_expected.to include http_status: 403, message: 'Package protected.' }

        it 'does not create any npm-related package records' do
          expect { subject }
            .to not_change { Packages::Package.count }
            .and not_change { Packages::Package.npm.count }
            .and not_change { Packages::Tag.count }
            .and not_change { Packages::Npm::Metadatum.count }
        end
      end

      where(:package_name_pattern, :push_protected_up_to_access_level, :current_user, :shared_examples_name) do
        ref(:package_name)                  | :developer  | ref(:project_developer)  | 'protected package'
        ref(:package_name)                  | :developer  | ref(:project_owner)      | 'valid package'
        ref(:package_name)                  | :maintainer | ref(:project_maintainer) | 'protected package'
        ref(:package_name)                  | :owner      | ref(:project_owner)      | 'protected package'
        ref(:package_name_pattern_no_match) | :developer  | ref(:project_owner)      | 'valid package'
        ref(:package_name_pattern_no_match) | :owner      | ref(:project_owner)      | 'valid package'
      end

      with_them do
        before do
          package_protection_rule.update!(package_name_pattern: package_name_pattern, push_protected_up_to_access_level: push_protected_up_to_access_level)
        end

        it_behaves_like params[:shared_examples_name]
      end
    end

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

      it 'returns an unique key' do
        is_expected.to eq lease_key
      end
    end
  end

  context 'when many of the same packages are created at the same time', :delete do
    let(:namespace) { create(:namespace) }
    let(:project) { create(:project, namespace: namespace) }
    let(:user) { project.owner }

    let(:version) { '1.0.1' }

    let(:params) do
      Gitlab::Json.parse(
        fixture_file('packages/npm/payload.json')
          .gsub('@root/npm-test', package_name)
          .gsub('1.0.1', version)
      ).with_indifferent_access
    end

    let(:package_name) { "@#{namespace.path}/my-app" }
    let(:service) { described_class.new(project, user, params) }

    subject { service.execute }

    it 'only creates one package' do
      expect { create_packages(project, user, params) }.to change { Packages::Package.count }.by(1)
    end

    context 'with different versions' do
      it 'creates all packages' do
        expect { create_packages_with_versions(project, user, params) }.to change { Packages::Package.count }.by(5)
      end
    end

    def create_packages(project, user, params)
      with_threads do
        described_class.new(project, user, params).execute
      end
    end

    def create_packages_with_versions(project, user, params)
      with_threads do |i|
        # Modify the package's version
        modified_params = Gitlab::Json.parse(params.to_json
          .gsub(version, "1.0.#{i}")).with_indifferent_access

        described_class.new(project, user, modified_params).execute
      end
    end

    def with_threads(count: 5, &block)
      return unless block

      # create a race condition - structure from https://blog.arkency.com/2015/09/testing-race-conditions/
      wait_for_it = true

      threads = Array.new(count) do |i|
        Thread.new do
          # A loop to make threads busy until we `join` them
          true while wait_for_it

          yield(i)
        end
      end

      wait_for_it = false
      threads.each(&:join)
    end
  end
end


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

module Packages
  module Npm
    class GenerateMetadataService
      include API::Helpers::RelatedResourcesHelpers
      include Gitlab::Utils::StrongMemoize

      # Allowed fields are those defined in the abbreviated form
      # defined here: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
      # except: name, version, dist, dependencies and xDependencies. Those are generated by this service.
      PACKAGE_JSON_ALLOWED_FIELDS = %w[deprecated bin directories dist engines _hasShrinkwrap].freeze

      def initialize(name, packages)
        @name = name
        @packages = packages
        @dependencies = {}
        @dependency_ids = Hash.new { |h, key| h[key] = {} }
      end

      def execute(only_dist_tags: false)
        ServiceResponse.success(payload: metadata(only_dist_tags))
      end

      private

      attr_reader :name, :packages, :dependencies, :dependency_ids

      def metadata(only_dist_tags)
        result = { dist_tags: dist_tags }

        unless only_dist_tags
          result[:name] = name
          result[:versions] = versions
        end

        result
      end

      def versions
        package_versions = {}

        packages.each_batch do |relation|
          load_dependencies(relation)
          load_dependency_ids(relation)

          batched_packages = relation.preload_files
                             .preload_npm_metadatum

          batched_packages.each do |package|
            package_file = package.installable_package_files.last

            next unless package_file

            package_versions[package.version] = build_package_version(package, package_file)
          end
        end

        package_versions
      end

      def dist_tags
        build_package_tags.tap { |t| t['latest'] ||= sorted_versions.last }
      end

      def build_package_tags
        package_tags.to_h { |tag| [tag.name, tag.package.version] }
      end

      def build_package_version(package, package_file)
        abbreviated_package_json(package).merge(
          name: package.name,
          version: package.version,
          dist: {
            shasum: package_file.file_sha1,
            tarball: tarball_url(package, package_file)
          }
        ).tap do |package_version|
          package_version.merge!(build_package_dependencies(package))
        end
      end

      def tarball_url(package, package_file)
        expose_url api_v4_projects_packages_npm_package_name___file_name_path(
          { id: package.project_id, package_name: package.name, file_name: package_file.file_name }, true
        )
      end

      def build_package_dependencies(package)
        dependency_ids[package.id].each_with_object(Hash.new { |h, key| h[key] = {} }) do |(type, ids), memo|
          ids.each do |id|
            memo[inverted_dependency_types[type]].merge!(dependencies[id])
          end
        end
      end

      def inverted_dependency_types
        Packages::DependencyLink.dependency_types.invert.stringify_keys
      end
      strong_memoize_attr :inverted_dependency_types

      def sorted_versions
        versions = packages.pluck_versions.compact
        VersionSorter.sort(versions)
      end

      def package_tags
        Packages::Tag.for_package_ids(packages)
                     .preload_package
      end

      def abbreviated_package_json(package)
        json = package.npm_metadatum&.package_json || {}
        json.slice(*PACKAGE_JSON_ALLOWED_FIELDS)
      end

      def load_dependencies(packages)
        Packages::Dependency
          .id_in(
            Packages::DependencyLink
              .for_packages(packages)
              .select_dependency_id
          )
          .id_not_in(dependencies.keys)
          .each_batch do |relation|
            relation.each do |dependency|
              dependencies[dependency.id] = { dependency.name => dependency.version_pattern }
            end
          end
      end

      def load_dependency_ids(packages)
        Packages::DependencyLink
          .dependency_ids_grouped_by_type(packages)
          .each_batch(column: :package_id) do |relation|
            relation.each do |dependency_link|
              dependency_ids[dependency_link.package_id] = dependency_link.dependency_ids_by_type
            end
          end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::Packages::Npm::GenerateMetadataService, feature_category: :package_registry do
  using RSpec::Parameterized::TableSyntax

  let_it_be(:project) { create(:project) }
  let_it_be(:package_name) { "@#{project.root_namespace.path}/test" }
  let_it_be(:package1) { create(:npm_package, version: '2.0.4', project: project, name: package_name) }
  let_it_be(:package2) { create(:npm_package, version: '2.0.6', project: project, name: package_name) }
  let_it_be(:latest_package) { create(:npm_package, version: '2.0.11', project: project, name: package_name) }

  let(:packages) { project.packages.npm.with_name(package_name) }
  let(:metadata) { described_class.new(package_name, packages).execute }

  describe '#versions' do
    let_it_be(:version_schema) { 'public_api/v4/packages/npm_package_version' }
    let_it_be(:package_json) do
      {
        name: package_name,
        version: '2.0.4',
        deprecated: 'warning!',
        bin: './cli.js',
        directories: ['lib'],
        engines: { npm: '^7.5.6' },
        _hasShrinkwrap: false,
        dist: {
          tarball: 'http://localhost/tarball.tgz',
          shasum: '1234567890'
        },
        custom_field: 'foo_bar'
      }
    end

    subject { metadata[:versions] }

    where(:has_dependencies, :has_metadatum) do
      true  | true
      false | true
      true  | false
      false | false
    end

    with_them do
      if params[:has_dependencies]
        ::Packages::DependencyLink.dependency_types.each_key do |dependency_type| # rubocop:disable RSpec/UselessDynamicDefinition -- `dependency_type` used in `let_it_be`
          let_it_be("package_dependency_link_for_#{dependency_type}") do
            create(:packages_dependency_link, package: package1, dependency_type: dependency_type)
          end
        end
      end

      if params[:has_metadatum]
        let_it_be(:package_metadatadum) { create(:npm_metadatum, package: package1, package_json: package_json) }
      end

      it { is_expected.to be_a(Hash) }
      it { expect(subject[package1.version].with_indifferent_access).to match_schema(version_schema) }
      it { expect(subject[package2.version].with_indifferent_access).to match_schema(version_schema) }
      it { expect(subject[package1.version]['custom_field']).to be_blank }

      context 'for dependencies' do
        ::Packages::DependencyLink.dependency_types.each_key do |dependency_type|
          if params[:has_dependencies]
            it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any }
          else
            it { expect(subject.dig(package1.version, dependency_type)).to be nil }
          end

          it { expect(subject.dig(package2.version, dependency_type)).to be nil }
        end

        context 'when generate dependencies' do
          let(:packages) { ::Packages::Package.where(id: package1.id) }

          it 'loads grouped dependency links', :aggregate_failures do
            expect(::Packages::DependencyLink).to receive(:dependency_ids_grouped_by_type).and_call_original
            expect(::Packages::Package).not_to receive(:including_dependency_links)

            subject
          end
        end
      end

      context 'for metadatum' do
        ::Packages::Npm::GenerateMetadataService::PACKAGE_JSON_ALLOWED_FIELDS.each do |metadata_field|
          if params[:has_metadatum]
            it { expect(subject.dig(package1.version, metadata_field)).not_to be nil }
          else
            it { expect(subject.dig(package1.version, metadata_field)).to be nil }
          end

          it { expect(subject.dig(package2.version, metadata_field)).to be nil }
        end
      end

      it 'avoids N+1 database queries' do
        check_n_plus_one do
          create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package|
            next unless has_dependencies

            ::Packages::DependencyLink.dependency_types.each_key do |dependency_type|
              create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type)
            end
          end
        end
      end
    end

    context 'with package files pending destruction' do
      let_it_be(:package_file_pending_destruction) do
        create(:package_file, :pending_destruction, package: package2, file_sha1: 'pending_destruction_sha1')
      end

      let(:shasums) { subject.values.map { |v| v.dig(:dist, :shasum) } }

      it 'does not return them' do
        expect(shasums).not_to include(package_file_pending_destruction.file_sha1)
      end
    end
  end

  describe '#dist_tags' do
    subject { metadata[:dist_tags] }

    context 'for packages without tags' do
      it { is_expected.to be_a(Hash) }
      it { expect(subject['latest']).to eq(latest_package.version) }

      it 'avoids N+1 database queries' do
        check_n_plus_one(only_dist_tags: true) do
          create_list(:npm_package, 5, project: project, name: package_name)
        end
      end
    end

    context 'for packages with tags' do
      let_it_be(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') }
      let_it_be(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') }
      let_it_be(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') }
      let_it_be(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') }
      let_it_be(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') }

      it { is_expected.to be_a(Hash) }
      it { expect(subject[package_tag1.name]).to eq(package1.version) }
      it { expect(subject[package_tag2.name]).to eq(package1.version) }
      it { expect(subject[package_tag3.name]).to eq(package2.version) }
      it { expect(subject[package_tag4.name]).to eq(latest_package.version) }
      it { expect(subject[package_tag5.name]).to eq(latest_package.version) }

      it 'avoids N+1 database queries' do
        check_n_plus_one(only_dist_tags: true) do
          create_list(:npm_package, 5, project: project, name: package_name).each_with_index do |npm_package, index|
            create(:packages_tag, package: npm_package, name: "tag_#{index}")
          end
        end
      end
    end
  end

  context 'when passing only_dist_tags: true' do
    subject { described_class.new(package_name, packages).execute(only_dist_tags: true) }

    it 'returns only dist tags' do
      expect(subject.payload.keys).to contain_exactly(:dist_tags)
    end
  end

  def check_n_plus_one(only_dist_tags: false)
    pkgs = project.packages.npm.with_name(package_name).preload_files
    control = ActiveRecord::QueryRecorder.new do
      described_class.new(package_name, pkgs).execute(only_dist_tags: only_dist_tags)
    end

    yield

    pkgs = project.packages.npm.with_name(package_name).preload_files

    expect do
      described_class.new(package_name, pkgs).execute(only_dist_tags: only_dist_tags)
    end.not_to exceed_query_limit(control)
  end
end


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

module Packages
  module Npm
    class DeprecatePackageService < BaseService
      Deprecated = Struct.new(:package_id, :message)
      BATCH_SIZE = 50

      def initialize(project, params)
        super(project, nil, params)
      end

      def execute(async: false)
        return ::Packages::Npm::DeprecatePackageWorker.perform_async(project.id, filtered_params) if async

        packages.select(:id, :version).each_batch(of: BATCH_SIZE) do |relation|
          deprecated_metadatum = handle_batch(relation)
          update_metadatum(deprecated_metadatum)
        end
      end

      private

      # To avoid passing the whole metadata to the worker
      def filtered_params
        {
          package_name: params[:package_name],
          versions: params[:versions].transform_values { |version| version.slice(:deprecated) }
        }
      end

      def packages
        ::Packages::Npm::PackageFinder
          .new(params['package_name'], project: project)
          .execute
      end

      def handle_batch(relation)
        relation
          .preload_npm_metadatum
          .filter_map { |package| deprecate(package) }
      end

      def deprecate(package)
        deprecation_message = params.dig('versions', package.version, 'deprecated')
        return if deprecation_message.nil?

        npm_metadatum = package.npm_metadatum
        return if identical?(npm_metadatum.package_json['deprecated'], deprecation_message)

        Deprecated.new(npm_metadatum.package_id, deprecation_message)
      end

      def identical?(package_json_deprecated, deprecation_message)
        package_json_deprecated == deprecation_message ||
          (package_json_deprecated.nil? && deprecation_message.empty?)
      end

      def update_metadatum(deprecated_metadatum)
        return if deprecated_metadatum.empty?

        deprecation_message = deprecated_metadatum.first.message

        ::Packages::Npm::Metadatum
          .package_id_in(deprecated_metadatum.map(&:package_id))
          .update_all(update_clause(deprecation_message))
      end

      def update_clause(deprecation_message)
        if deprecation_message.empty?
          "package_json = package_json - 'deprecated'"
        else
          ["package_json = jsonb_set(package_json, '{deprecated}', ?)", deprecation_message.to_json]
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Packages::Npm::DeprecatePackageService, feature_category: :package_registry do
  let_it_be(:namespace) { create(:namespace) }
  let_it_be(:project) { create(:project, namespace: namespace) }

  let_it_be(:package_name) { "@#{namespace.path}/my-app" }
  let_it_be_with_reload(:package_1) do
    create(:npm_package, project: project, name: package_name, version: '1.0.1').tap do |package|
      create(:npm_metadatum, package: package)
    end
  end

  let_it_be(:package_2) do
    create(:npm_package, project: project, name: package_name, version: '1.0.2').tap do |package|
      create(:npm_metadatum, package: package)
    end
  end

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

  subject(:execute) { service.execute }

  describe '#execute' do
    context 'when passing deprecatation message' do
      let(:params) do
        {
          'package_name' => package_name,
          'versions' => {
            '1.0.1' => {
              'name' => package_name,
              'deprecated' => 'This version is deprecated'
            },
            '1.0.2' => {
              'name' => package_name,
              'deprecated' => 'This version is deprecated'
            }
          }
        }
      end

      before do
        package_json = package_2.npm_metadatum.package_json
        package_2.npm_metadatum.update!(package_json: package_json.merge('deprecated' => 'old deprecation message'))
      end

      it 'adds or updates the deprecated field' do
        expect { execute }
          .to change { package_1.reload.npm_metadatum.package_json['deprecated'] }.to('This version is deprecated')
          .and change { package_2.reload.npm_metadatum.package_json['deprecated'] }
            .from('old deprecation message').to('This version is deprecated')
      end

      it 'executes 5 queries' do
        queries = ActiveRecord::QueryRecorder.new do
          execute
        end

        # 1. each_batch lower bound
        # 2. each_batch upper bound
        # 3. SELECT packages_packages.id, packages_packages.version FROM packages_packages
        # 4. SELECT packages_npm_metadata.* FROM packages_npm_metadata
        # 5. UPDATE packages_npm_metadata SET package_json =
        expect(queries.count).to eq(5)
      end
    end

    context 'when passing deprecated as empty string' do
      let(:params) do
        {
          'package_name' => package_name,
          'versions' => {
            '1.0.1' => {
              'name' => package_name,
              'deprecated' => ''
            }
          }
        }
      end

      before do
        package_json = package_1.npm_metadatum.package_json
        package_1.npm_metadatum.update!(package_json: package_json.merge('deprecated' => 'This version is deprecated'))
      end

      it 'removes the deprecation warning' do
        expect { execute }
          .to change { package_1.reload.npm_metadatum.package_json['deprecated'] }
            .from('This version is deprecated').to(nil)
      end
    end

    context 'when passing async: true to execute' do
      let(:params) do
        {
          package_name: package_name,
          versions: {
            '1.0.1': {
              deprecated: 'This version is deprecated'
            }
          }
        }
      end

      it 'calls the worker and return' do
        expect(::Packages::Npm::DeprecatePackageWorker).to receive(:perform_async).with(project.id, params)
        expect(service).not_to receive(:packages)

        service.execute(async: true)
      end
    end
  end
end


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

module Packages
  module Npm
    class CreateMetadataCacheService
      include Gitlab::Utils::StrongMemoize
      include ExclusiveLeaseGuard

      # used by ExclusiveLeaseGuard
      DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze

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

      def execute
        try_obtain_lease do
          Packages::Npm::MetadataCache
            .find_or_build(package_name: package_name, project_id: project.id)
            .update!(
              file: CarrierWaveStringFile.new(metadata_content),
              size: metadata_content.bytesize
            )
        end
      end

      private

      attr_reader :package_name, :project

      def metadata_content
        ::API::Entities::NpmPackage.represent(metadata.payload).to_json
      end
      strong_memoize_attr :metadata_content

      def packages
        ::Packages::Npm::PackageFinder
          .new(package_name, project: project)
          .execute
      end

      def metadata
        Packages::Npm::GenerateMetadataService.new(package_name, packages).execute
      end

      # used by ExclusiveLeaseGuard
      def lease_key
        "packages:npm:create_metadata_cache_service:metadata_caches:#{project.id}_#{package_name}"
      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::Npm::CreateMetadataCacheService, :clean_gitlab_redis_shared_state, feature_category: :package_registry do
  include ExclusiveLeaseHelpers

  let_it_be(:project) { create(:project) }
  let_it_be(:package_name) { "@#{project.root_namespace.path}/npm-test" }
  let_it_be(:package) { create(:npm_package, version: '1.0.0', project: project, name: package_name) }

  let(:lease_key) { "packages:npm:create_metadata_cache_service:metadata_caches:#{project.id}_#{package_name}" }
  let(:service) { described_class.new(project, package_name) }

  describe '#execute' do
    let(:npm_metadata_cache) { Packages::Npm::MetadataCache.last }

    subject { service.execute }

    it 'creates a new metadata cache', :aggregate_failures do
      expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(1)

      metadata = Gitlab::Json.parse(npm_metadata_cache.file.read)

      expect(npm_metadata_cache.package_name).to eq(package_name)
      expect(npm_metadata_cache.project_id).to eq(project.id)
      expect(npm_metadata_cache.size).to eq(metadata.to_json.bytesize)
      expect(metadata['name']).to eq(package_name)
      expect(metadata['versions'].keys).to contain_exactly('1.0.0')
    end

    context 'with existing metadata cache' do
      let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache, package_name: package_name, project_id: project.id) }
      let_it_be(:metadata) { Gitlab::Json.parse(npm_metadata_cache.file.read) }
      let_it_be(:metadata_size) { npm_metadata_cache.size }
      let_it_be(:tag_name) { 'new-tag' }
      let_it_be(:tag) { create(:packages_tag, package: package, name: tag_name) }

      it 'does not create a new metadata cache' do
        expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(0)
      end

      it 'updates the metadata cache', :aggregate_failures do
        subject

        new_metadata = Gitlab::Json.parse(npm_metadata_cache.file.read)

        expect(new_metadata).not_to eq(metadata)
        expect(new_metadata['dist-tags'].keys).to include(tag_name)
        expect(npm_metadata_cache.reload.size).not_to eq(metadata_size)
      end
    end

    it 'obtains a lease to create a new metadata cache' do
      expect_to_obtain_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)

      subject
    end

    context 'when the lease is already taken' do
      before do
        stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
      end

      it 'does not create a new metadata cache' do
        expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(0)
      end

      it 'returns nil' do
        expect(subject).to be_nil
      end
    end
  end

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

    it 'returns an unique key' do
      is_expected.to eq lease_key
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module Packages
  module Npm
    class CreateTagService
      include Gitlab::Utils::StrongMemoize

      attr_reader :package, :tag_name

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

      def execute
        if existing_tag.present?
          existing_tag.update_column(:package_id, package.id)
          existing_tag
        else
          package.tags.create!(name: tag_name)
        end
      end

      private

      def existing_tag
        Packages::TagsFinder
            .new(package.project, package.name, package_type: package.package_type)
            .find_by_name(tag_name)
      end
      strong_memoize_attr :existing_tag
    end
  end
end

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Packages::Npm::CreateTagService, feature_category: :package_registry do
  let(:package) { create(:npm_package) }
  let(:tag_name) { 'test-tag' }

  describe '#execute' do
    subject { described_class.new(package, tag_name).execute }

    shared_examples 'it creates the tag' do
      it { expect { subject }.to change { Packages::Tag.count }.by(1) }
      it { expect(subject.name).to eq(tag_name) }

      it 'adds tag to the package' do
        tag = subject
        expect(package.reload.tags).to match_array([tag])
      end
    end

    context 'with no existing tag name' do
      it_behaves_like 'it creates the tag'
    end

    context 'with exisiting tag name' do
      let!(:package_tag2) { create(:packages_tag, package: package2, name: tag_name) }

      context 'on package with different name' do
        let!(:package2) { create(:npm_package, project: package.project) }

        it_behaves_like 'it creates the tag'
      end

      context 'on different package type' do
        let!(:package2) { create(:conan_package, project: package.project, name: 'conan_package_name', version: package.version) }

        it_behaves_like 'it creates the tag'
      end

      context 'on same package with different version' do
        let!(:package2) { create(:npm_package, project: package.project, name: package.name, version: '5.0.0-testing') }

        it { expect { subject }.to not_change { Packages::Tag.count } }
        it { expect(subject.name).to eq(tag_name) }

        it 'adds tag to the package' do
          tag = subject
          expect(package.reload.tags).to match_array([tag])
          expect(package2.reload.tags).to be_empty
        end
      end
    end
  end
end


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

module Boards
  class DestroyService < Boards::BaseService
    def execute(board)
      board.destroy!

      ServiceResponse.success
    end

    private

    def boards
      parent.boards
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::DestroyService, feature_category: :team_planning do
  context 'with project board' do
    let_it_be(:parent) { create(:project) }

    let(:boards) { parent.boards }
    let(:board_factory) { :board }

    it_behaves_like 'board destroy service'
  end

  context 'with group board' do
    let_it_be(:parent) { create(:group) }

    let(:boards) { parent.boards }
    let(:board_factory) { :board }

    it_behaves_like 'board destroy service'
  end
end


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

module Boards
  class CreateService < Boards::BaseService
    def execute
      unless can_create_board?
        return ServiceResponse.error(message: "You don't have the permission to create a board for this resource.")
      end

      create_board!
    end

    private

    def can_create_board?
      parent_board_collection.empty? || parent.multiple_issue_boards_available?
    end

    def create_board!
      board = parent_board_collection.create(params)

      unless board.persisted?
        return ServiceResponse.error(message: "There was an error when creating a board.", payload: board)
      end

      board.tap do |created_board|
        created_board.lists.create(list_type: :backlog)
        created_board.lists.create(list_type: :closed)
      end

      ServiceResponse.success(payload: board)
    end

    def parent_board_collection
      parent.boards
    end
  end
end

Boards::CreateService.prepend_mod_with('Boards::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::CreateService, feature_category: :team_planning do
  describe '#execute' do
    context 'when board parent is a project' do
      let(:parent) { create(:project) }

      subject(:service) { described_class.new(parent, double) }

      it_behaves_like 'boards create service'
    end

    context 'when board parent is a group' do
      let(:parent) { create(:group) }

      subject(:service) { described_class.new(parent, double) }

      it_behaves_like 'boards create service'
    end
  end
end


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

module Boards
  module Lists
    class MoveService < Boards::BaseService
      def execute(list)
        @board = list.board
        @old_position = list.position
        @new_position = params[:position]

        return false unless list.movable?
        return false unless valid_move?

        list.with_lock do
          reorder_intermediate_lists
          update_list_position(list)
        end
      end

      private

      attr_reader :board, :old_position, :new_position

      def valid_move?
        new_position.present? && new_position != old_position && new_position.between?(0, max_position)
      end

      def max_position
        board.lists.movable.maximum(:position)
      end

      def reorder_intermediate_lists
        if old_position < new_position
          decrement_intermediate_lists
        else
          increment_intermediate_lists
        end
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def decrement_intermediate_lists
        board.lists.movable.where('position > ?',  old_position)
                           .where('position <= ?', new_position)
                           .update_all('position = position - 1')
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # rubocop: disable CodeReuse/ActiveRecord
      def increment_intermediate_lists
        board.lists.movable.where('position >= ?', new_position)
                           .where('position < ?',  old_position)
                           .update_all('position = position + 1')
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def update_list_position(list)
        list.update_attribute(:position, new_position)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Lists::MoveService, feature_category: :team_planning do
  describe '#execute' do
    context 'when board parent is a project' do
      let(:project) { create(:project) }
      let(:board)   { create(:board, project: project) }
      let(:user)    { create(:user) }

      let(:parent) { project }

      it_behaves_like 'lists move service'
    end

    context 'when board parent is a group' do
      let(:group) { create(:group) }
      let(:board)   { create(:board, group: group) }
      let(:user)    { create(:user) }

      let(:parent) { group }

      it_behaves_like 'lists move service'
    end
  end
end


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

module Boards
  module Lists
    # overridden in EE for board lists and also for epic board lists.
    class DestroyService < Boards::Lists::BaseDestroyService
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Lists::DestroyService, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }

  let(:list_type) { :list }

  describe '#execute' do
    context 'when board parent is a project' do
      let_it_be(:project) { create(:project) }
      let_it_be(:board) { create(:board, project: project) }
      let_it_be(:list) { create(:list, board: board) }
      let_it_be(:closed_list) { board.lists.closed.first }

      let(:params) do
        { board: board }
      end

      let(:parent) { project }

      it_behaves_like 'lists destroy service'
    end

    context 'when board parent is a group' do
      let_it_be(:group) { create(:group) }
      let_it_be(:board) { create(:board, group: group) }
      let_it_be(:list) { create(:list, board: board) }
      let_it_be(:closed_list) { board.lists.closed.first }

      let(:params) do
        { board: board }
      end

      let(:parent) { group }

      it_behaves_like 'lists destroy service'
    end
  end
end


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

module Boards
  module Lists
    class UpdateService < Boards::Lists::BaseUpdateService
      def can_read?(list)
        Ability.allowed?(current_user, :read_issue_board_list, parent)
      end

      def can_admin?(list)
        Ability.allowed?(current_user, :admin_issue_board_list, parent)
      end
    end
  end
end

Boards::Lists::UpdateService.prepend_mod_with('Boards::Lists::UpdateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Lists::UpdateService, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }

  let!(:list) { create(:list, board: board, position: 0) }
  let!(:list2) { create(:list, board: board, position: 1) }

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

    context 'when position parameter is present' do
      let(:params) { { position: 1 } }

      context 'for projects' do
        let(:project) { create(:project, :private) }
        let(:board) { create(:board, project: project) }

        it_behaves_like 'moving list'
      end

      context 'for groups' do
        let(:group) { create(:group, :private) }
        let(:board) { create(:board, group: group) }

        it_behaves_like 'moving list'
      end
    end

    context 'when collapsed parameter is present' do
      let(:params) { { collapsed: true } }

      context 'for projects' do
        let(:project) { create(:project, :private) }
        let(:board) { create(:board, project: project) }

        it_behaves_like 'updating list preferences'
      end

      context 'for groups' do
        let(:project) { create(:project, :private) }
        let(:board) { create(:board, project: project) }

        it_behaves_like 'updating list preferences'
      end
    end

    context 'when position and collapsed are both present' do
      let(:params) { { collapsed: true, position: 1 } }

      context 'for projects' do
        let(:project) { create(:project, :private) }
        let(:board) { create(:board, project: project) }

        it_behaves_like 'moving list'
        it_behaves_like 'updating list preferences'
      end

      context 'for groups' do
        let(:group) { create(:group, :private) }
        let(:board) { create(:board, group: group) }

        it_behaves_like 'moving list'
        it_behaves_like 'updating list preferences'
      end
    end
  end
end


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

module Boards
  module Lists
    class ListService < Boards::BaseService
      def execute(board, create_default_lists: true)
        if create_default_lists && !board.lists.backlog.exists?
          board.lists.create(list_type: :backlog)
        end

        lists = board.lists.preload_associated_models
        lists = lists.with_types(available_list_types_for(board))

        return lists.id_in(params[:list_id]) if params[:list_id].present?

        lists
      end

      private

      def available_list_types_for(board)
        licensed_list_types(board) + visible_lists(board)
      end

      def licensed_list_types(board)
        [List.list_types[:label]]
      end

      def visible_lists(board)
        [].tap do |visible|
          visible << ::List.list_types[:backlog] unless board.hide_backlog_list?
          visible << ::List.list_types[:closed] unless board.hide_closed_list?
        end
      end
    end
  end
end

Boards::Lists::ListService.prepend_mod_with('Boards::Lists::ListService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Lists::ListService, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }

  RSpec.shared_examples 'FOSS lists only' do
    context 'when board contains a non FOSS list' do
      # This scenario may happen when there used to be an EE license and user downgraded
      let_it_be(:backlog_list) { board.lists.backlog.first }
      let_it_be(:milestone) { create(:milestone, group: group) }
      let_it_be(:assignee_list) do
        list = build(:list, board: board, user_id: user.id, list_type: List.list_types[:assignee], position: 0)
        list.save!(validate: false)
        list
      end

      let_it_be(:milestone_list) do
        list = build(:list, board: board, milestone_id: milestone.id, list_type: List.list_types[:milestone], position: 1) # rubocop:disable Layout/LineLength
        list.save!(validate: false)
        list
      end

      it "returns only FOSS board's lists" do
        # just making sure these non FOSS lists actually exist on the board
        expect(board.lists.with_types([List.list_types[:assignee], List.list_types[:milestone]]).count).to eq 2
        # check that the FOSS lists are not returned from the service
        expect(service.execute(board)).to match_array [backlog_list, list, board.lists.closed.first]
      end
    end
  end

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

    context 'when board parent is a project' do
      let_it_be(:project) { create(:project, group: group) }
      let_it_be_with_reload(:board) { create(:board, project: project) }
      let_it_be(:label) { create(:label, project: project) }
      let_it_be(:list) { create(:list, board: board, label: label) }
      let_it_be(:unrelated_list) { create(:list) }

      let(:parent) { project }

      it_behaves_like 'lists list service'
      it_behaves_like 'FOSS lists only'
    end

    context 'when board parent is a group' do
      let_it_be_with_reload(:board) { create(:board, group: group) }
      let_it_be(:label) { create(:group_label, group: group) }
      let_it_be(:list) { create(:list, board: board, label: label) }
      let_it_be(:unrelated_list) { create(:list) }

      let(:parent) { group }

      it_behaves_like 'lists list service'
      it_behaves_like 'FOSS lists only'
    end
  end
end


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

module Boards
  module Lists
    class CreateService < Boards::Lists::BaseCreateService
    end
  end
end

Boards::Lists::CreateService.prepend_mod_with('Boards::Lists::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Lists::CreateService, feature_category: :team_planning do
  context 'when board parent is a project' do
    let_it_be(:parent) { create(:project) }
    let_it_be(:board) { create(:board, project: parent) }
    let_it_be(:label) { create(:label, project: parent, name: 'in-progress') }

    it_behaves_like 'board lists create service'
  end

  context 'when board parent is a group' do
    let_it_be(:parent) { create(:group) }
    let_it_be(:board) { create(:board, group: parent) }
    let_it_be(:label) { create(:group_label, group: parent, name: 'in-progress') }

    it_behaves_like 'board lists create service'
  end

  def create_list(params)
    create(:list, params.merge(board: board))
  end
end


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

module Boards
  module Visits
    class CreateService < Boards::BaseService
      def execute(board)
        return unless current_user && Gitlab::Database.read_write?
        return unless board

        model.visited!(current_user, board)
      end

      private

      def model
        return BoardGroupRecentVisit if parent.is_a?(Group)

        BoardProjectRecentVisit
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Visits::CreateService, feature_category: :team_planning do
  describe '#execute' do
    let(:user) { create(:user) }

    context 'when a project board' do
      let_it_be(:project) { create(:project) }
      let_it_be(:board)   { create(:board, project: project) }

      let_it_be(:model) { BoardProjectRecentVisit }

      it_behaves_like 'boards recent visit create service'
    end

    context 'when a group board' do
      let_it_be(:group) { create(:group) }
      let_it_be(:board) { create(:board, group: group) }
      let_it_be(:model) { BoardGroupRecentVisit }

      it_behaves_like 'boards recent visit create service'
    end
  end
end


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

module Boards
  module Issues
    class MoveService < Boards::BaseItemMoveService
      def execute_multiple(issues)
        return execute_multiple_empty_result if issues.empty?

        handled_issues = []
        last_inserted_issue_id = nil
        count = issues.each.inject(0) do |moved_count, issue|
          issue_modification_params = issuable_params(issue)
          next moved_count if issue_modification_params.empty?

          if last_inserted_issue_id
            issue_modification_params[:move_between_ids] = move_below(last_inserted_issue_id)
          end

          last_inserted_issue_id = issue.id
          handled_issue = move_single_issuable(issue, issue_modification_params)
          handled_issues << present_issue_entity(handled_issue) if handled_issue
          handled_issue && handled_issue.valid? ? moved_count + 1 : moved_count
        end

        {
          count: count,
          success: count == issues.size,
          issues: handled_issues
        }
      end

      private

      def present_issue_entity(issue)
        ::API::Entities::Issue.represent(issue)
      end

      def execute_multiple_empty_result
        @execute_multiple_empty_result ||= {
          count: 0,
          success: false,
          issues: []
        }
      end

      def move_below(id)
        move_between_ids({ move_after_id: nil, move_before_id: id })
      end

      def board
        @board ||= parent.boards.find(params[:board_id])
      end

      def update(issue, issue_modification_params)
        ::Issues::UpdateService.new(container: issue.project, current_user: current_user, params: issue_modification_params).execute(issue)
      end

      def moving_to_list_items_relation
        Boards::Issues::ListService.new(board.resource_parent, current_user, board_id: board.id, id: moving_to_list.id).execute
      end
    end
  end
end

Boards::Issues::MoveService.prepend_mod_with('Boards::Issues::MoveService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Issues::MoveService, feature_category: :team_planning do
  describe '#execute' do
    context 'when parent is a project' do
      let(:user) { create(:user) }
      let(:project) { create(:project) }
      let(:board1) { create(:board, project: project) }
      let(:board2) { create(:board, project: project) }

      let(:bug) { create(:label, project: project, name: 'Bug') }
      let(:development) { create(:label, project: project, name: 'Development') }
      let(:testing) { create(:label, project: project, name: 'Testing') }
      let(:regression) { create(:label, project: project, name: 'Regression') }

      let!(:list1)   { create(:list, board: board1, label: development, position: 0) }
      let!(:list2)   { create(:list, board: board1, label: testing, position: 1) }
      let!(:closed)  { create(:closed_list, board: board1) }

      let(:parent) { project }

      before do
        parent.add_developer(user)
      end

      it_behaves_like 'issues move service'
    end

    context 'when parent is a group' do
      let(:user) { create(:user) }
      let(:group) { create(:group) }
      let(:project) { create(:project, namespace: group) }
      let(:board1) { create(:board, group: group) }
      let(:board2) { create(:board, group: group) }

      let(:bug) { create(:group_label, group: group, name: 'Bug') }
      let(:development) { create(:group_label, group: group, name: 'Development') }
      let(:testing) { create(:group_label, group: group, name: 'Testing') }
      let(:regression) { create(:group_label, group: group, name: 'Regression') }

      let!(:list1)   { create(:list, board: board1, label: development, position: 0) }
      let!(:list2)   { create(:list, board: board1, label: testing, position: 1) }
      let!(:closed)  { create(:closed_list, board: board1) }

      let(:parent) { group }

      before do
        parent.add_developer(user)
      end

      it_behaves_like 'issues move service', true
    end

    describe '#execute_multiple' do
      let_it_be(:group)  { create(:group) }
      let_it_be(:user)   { create(:user) }
      let_it_be(:project) { create(:project, namespace: group) }
      let_it_be(:board1) { create(:board, group: group) }
      let_it_be(:development) { create(:group_label, group: group, name: 'Development') }
      let_it_be(:testing) { create(:group_label, group: group, name: 'Testing') }
      let_it_be(:list1) { create(:list, board: board1, label: development, position: 0) }
      let_it_be(:list2) { create(:list, board: board1, label: testing, position: 1) }

      let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }

      before do
        project.add_developer(user)
      end

      it 'returns the expected result if list of issues is empty' do
        expect(described_class.new(group, user, params).execute_multiple([])).to eq({ count: 0, success: false, issues: [] })
      end

      context 'moving multiple issues' do
        let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
        let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }

        it 'moves multiple issues from one list to another' do
          expect(described_class.new(group, user, params).execute_multiple([issue1, issue2])).to be_truthy

          expect(issue1.labels).to eq([testing])
          expect(issue2.labels).to eq([testing])
        end
      end

      context 'moving a single issue' do
        let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }

        it 'moves one issue' do
          expect(described_class.new(group, user, params).execute_multiple([issue1])).to be_truthy

          expect(issue1.labels).to eq([testing])
        end
      end

      context 'moving issues visually after an existing issue' do
        let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
        let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
        let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }

        let(:move_params) do
          params.dup.tap do |hash|
            hash[:move_before_id] = existing_issue.id
          end
        end

        it 'moves one issue' do
          expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy

          expect(issue1.labels).to eq([testing])
          expect(issue2.labels).to eq([testing])

          expect(issue1.relative_position > existing_issue.relative_position).to eq(true)
          expect(issue2.relative_position > issue1.relative_position).to eq(true)
        end
      end

      context 'moving issues visually before an existing issue' do
        let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
        let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
        let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }

        let(:move_params) do
          params.dup.tap do |hash|
            hash[:move_after_id] = existing_issue.id
          end
        end

        it 'moves one issue' do
          expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy

          expect(issue1.labels).to eq([testing])
          expect(issue2.labels).to eq([testing])

          expect(issue2.relative_position < existing_issue.relative_position).to eq(true)
          expect(issue1.relative_position < issue2.relative_position).to eq(true)
        end
      end
    end
  end
end


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

module Boards
  module Issues
    class ListService < Boards::BaseItemsListService
      include Gitlab::Utils::StrongMemoize

      def self.valid_params
        IssuesFinder.valid_params
      end

      # It is a class method because we cannot apply it
      # prior to knowing how many items should be fetched for a list.
      def self.initialize_relative_positions(board, current_user, issues)
        if Gitlab::Database.read_write? && !board.disabled_for?(current_user)
          Issue.move_nulls_to_end(issues)
        end
      end

      private

      def order(items)
        return items.order_closed_at_desc if list&.closed?

        items.order_by_relative_position
      end

      def finder
        IssuesFinder.new(current_user, filter_params)
      end

      def board
        @board ||= parent.boards.find(params[:board_id])
      end

      def filter_params
        set_scope
        set_non_archived
        set_issue_types

        super
      end

      def set_scope
        params[:include_subgroups] = board.group_board?
      end

      def set_non_archived
        params[:non_archived] = parent.is_a?(Group)
      end

      def set_issue_types
        params[:issue_types] ||= Issue::TYPES_FOR_BOARD_LIST
      end

      def item_model
        Issue
      end
    end
  end
end

Boards::Issues::ListService.prepend_mod_with('Boards::Issues::ListService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
  describe '#execute' do
    let_it_be(:user) { create(:user) }

    context 'when parent is a project' do
      let_it_be(:project) { create(:project, :empty_repo) }
      let_it_be(:board)   { create(:board, project: project) }

      let_it_be(:m1) { create(:milestone, project: project) }
      let_it_be(:m2) { create(:milestone, project: project) }

      let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
      let_it_be(:development) { create(:label, project: project, name: 'Development') }
      let_it_be(:testing) { create(:label, project: project, name: 'Testing') }
      let_it_be(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
      let_it_be(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
      let_it_be(:p3) { create(:label, title: 'P3', project: project, priority: 3) }

      let_it_be(:backlog) { board.lists.backlog.first }
      let_it_be(:list1)   { create(:list, board: board, label: development, position: 0) }
      let_it_be(:list2)   { create(:list, board: board, label: testing, position: 1) }
      let_it_be(:closed)  { board.lists.closed.first }

      let_it_be(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
      let_it_be(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) }
      let_it_be(:reopened_issue1) { create(:issue, :opened, project: project, title: 'Reopened Issue 1') }

      let_it_be(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, development]) }
      let_it_be(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
      let_it_be(:list1_issue3) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1]) }
      let_it_be(:list2_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [testing]) }

      let_it_be(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug], closed_at: 1.day.ago) }
      let_it_be(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3], closed_at: 2.days.ago) }
      let_it_be(:closed_issue3) { create(:issue, :closed, project: project, closed_at: 1.week.ago) }
      let_it_be(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1], closed_at: 1.year.ago) }
      let_it_be(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development], closed_at: 2.years.ago) }

      let(:parent) { project }

      before do
        project.add_developer(user)
      end

      it_behaves_like 'issues list service'

      context 'when project is archived' do
        before do
          project.update!(archived: true)
        end

        it_behaves_like 'issues list service'
      end

      context 'when filtering' do
        let_it_be(:incident) do
          create(
            :labeled_issue,
            :incident,
            project: project,
            milestone: m1,
            labels: [development, p1]
          )
        end

        context 'when filtering by type' do
          it 'only returns the specified type' do
            params = { board_id: board.id, id: list1.id, issue_types: 'incident' }

            expect(described_class.new(parent, user, params).execute).to eq [incident]
          end
        end

        context 'when filtering by negated type' do
          it 'only returns the specified type' do
            params = { board_id: board.id, id: list1.id, not: { issue_types: ['issue'] } }

            expect(described_class.new(parent, user, params).execute).to contain_exactly(incident)
          end
        end
      end
    end

    context 'when parent is a group' do
      let(:project) { create(:project, :empty_repo, namespace: group) }
      let(:project1) { create(:project, :empty_repo, namespace: group) }
      let(:project_archived) { create(:project, :empty_repo, :archived, namespace: group) }

      let(:m1) { create(:milestone, group: group) }
      let(:m2) { create(:milestone, group: group) }

      let(:bug) { create(:group_label, group: group, name: 'Bug') }
      let(:development) { create(:group_label, group: group, name: 'Development') }
      let(:testing) { create(:group_label, group: group, name: 'Testing') }

      let(:p1) { create(:group_label, title: 'P1', group: group) }
      let(:p2) { create(:group_label, title: 'P2', group: group) }
      let(:p3) { create(:group_label, title: 'P3', group: group) }

      let(:p1_project) { create(:label, title: 'P1_project', project: project, priority: 1) }
      let(:p2_project) { create(:label, title: 'P2_project', project: project, priority: 2) }
      let(:p3_project) { create(:label, title: 'P3_project', project: project, priority: 3) }

      let(:p1_project1) { create(:label, title: 'P1_project1', project: project1, priority: 1) }
      let(:p2_project1) { create(:label, title: 'P2_project1', project: project1, priority: 2) }
      let(:p3_project1) { create(:label, title: 'P3_project1', project: project1, priority: 3) }

      let!(:backlog) { board.lists.backlog.first }
      let!(:list1)   { create(:list, board: board, label: development, position: 0) }
      let!(:list2)   { create(:list, board: board, label: testing, position: 1) }
      let!(:closed)  { board.lists.closed.first }

      let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
      let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2, p2_project]) }
      let!(:opened_issue3) { create(:labeled_issue, project: project_archived, milestone: m1, title: 'Issue 3', labels: [bug]) }
      let!(:reopened_issue1) { create(:issue, state: 'opened', project: project, title: 'Reopened Issue 1', closed_at: Time.current) }

      let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, p2_project, development]) }
      let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
      let!(:list1_issue3) { create(:labeled_issue, project: project1, milestone: m1, labels: [development, p1, p1_project1]) }
      let!(:list2_issue1) { create(:labeled_issue, project: project1, milestone: m1, labels: [testing]) }

      let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug], closed_at: 1.day.ago) }
      let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3, p3_project], closed_at: 2.days.ago) }
      let!(:closed_issue3) { create(:issue, :closed, project: project1, closed_at: 1.week.ago) }
      let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1], closed_at: 1.year.ago) }
      let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development], closed_at: 2.years.ago) }

      before do
        group.add_developer(user)
      end

      context 'when the group has no parent' do
        let(:parent) { group }
        let(:group) { create(:group) }
        let(:board) { create(:board, group: group) }

        it_behaves_like 'issues list service'
      end

      context 'when the group is an ancestor' do
        let(:parent) { create(:group) }
        let(:group) { create(:group, parent: parent) }
        let(:board) { create(:board, group: parent) }

        before do
          parent.add_developer(user)
        end

        it_behaves_like 'issues list service'
      end
    end
  end

  describe '.initialize_relative_positions' do
    let_it_be(:user) { create(:user) }
    let_it_be(:project) { create(:project, :empty_repo) }
    let_it_be(:board) { create(:board, project: project) }

    let(:issue) { create(:issue, project: project, relative_position: nil) }

    context "when 'Gitlab::Database::read_write?' is true" do
      before do
        allow(Gitlab::Database).to receive(:read_write?).and_return(true)
      end

      context 'user cannot move issues' do
        it 'does not initialize the relative positions of issues' do
          described_class.initialize_relative_positions(board, user, [issue])

          expect(issue.relative_position).to eq nil
        end
      end

      context 'user can move issues' do
        before do
          project.add_developer(user)
        end

        it 'initializes the relative positions of issues' do
          described_class.initialize_relative_positions(board, user, [issue])

          expect(issue.relative_position).not_to eq nil
        end
      end
    end

    context "when 'Gitlab::Database::read_write?' is false" do
      before do
        allow(Gitlab::Database).to receive(:read_write?).and_return(false)
      end

      it 'does not initialize the relative positions of issues' do
        described_class.initialize_relative_positions(board, user, [issue])

        expect(issue.relative_position).to eq nil
      end
    end
  end
end


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

module Boards
  module Issues
    class CreateService < Boards::BaseService
      attr_accessor :project

      def initialize(parent, project, user, params = {})
        @project = project

        super(parent, user, params)
      end

      def execute
        create_issue(params.merge(issue_params))
      end

      private

      def issue_params
        { label_ids: [list.label_id] }
      end

      def board
        @board ||= parent.boards.find(params.delete(:board_id))
      end

      def list
        @list ||= board.lists.find(params.delete(:list_id))
      end

      def create_issue(params)
        # NOTE: We are intentionally not doing a spam/CAPTCHA check for issues created via boards.
        # See https://gitlab.com/gitlab-org/gitlab/-/issues/29400#note_598479184 for more context.
        ::Issues::CreateService.new(container: project, current_user: current_user, params: params, perform_spam_check: false).execute
      end
    end
  end
end

Boards::Issues::CreateService.prepend_mod_with('Boards::Issues::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Boards::Issues::CreateService, feature_category: :team_planning do
  describe '#execute' do
    let(:project) { create(:project) }
    let(:board)   { create(:board, project: project) }
    let(:user)    { create(:user) }
    let(:label)   { create(:label, project: project, name: 'in-progress') }
    let!(:list)   { create(:list, board: board, label: label, position: 0) }

    subject(:service) { described_class.new(board.resource_parent, project, user, board_id: board.id, list_id: list.id, title: 'New issue') }

    before do
      project.add_developer(user)
    end

    it 'delegates the create proceedings to Issues::CreateService' do
      expect_next_instance_of(Issues::CreateService) do |instance|
        expect(instance).to receive(:execute).once
      end

      service.execute
    end

    it 'creates a new issue' do
      expect { service.execute }.to change(project.issues, :count).by(1)
    end

    it 'adds the label of the list to the issue' do
      result = service.execute

      expect(result).to be_success
      expect(result[:issue].labels).to contain_exactly(label)
    end
  end
end