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

module Projects
  class CreateService < BaseService
    include ValidatesClassificationLabel

    ImportSourceDisabledError = Class.new(StandardError)
    INTERNAL_IMPORT_SOURCES = %w[gitlab_custom_project_template gitlab_project_migration].freeze

    def initialize(user, params)
      @current_user = user
      @params = params.dup
      @skip_wiki = @params.delete(:skip_wiki)
      @initialize_with_sast = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_sast))
      @initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
      @use_sha256_repository = Gitlab::Utils.to_boolean(@params.delete(:use_sha256_repository)) && Feature.enabled?(:support_sha256_repositories, user)
      @import_data = @params.delete(:import_data)
      @relations_block = @params.delete(:relations_block)
      @default_branch = @params.delete(:default_branch)
      @readme_template = @params.delete(:readme_template)

      build_topics
    end

    def execute
      params[:wiki_enabled] = params[:wiki_access_level] if params[:wiki_access_level]
      params[:builds_enabled] = params[:builds_access_level] if params[:builds_access_level]
      params[:snippets_enabled] = params[:snippets_access_level] if params[:snippets_access_level]
      params[:merge_requests_enabled] = params[:merge_requests_access_level] if params[:merge_requests_access_level]
      params[:issues_enabled] = params[:issues_access_level] if params[:issues_access_level]

      if create_from_template?
        return ::Projects::CreateFromTemplateService.new(current_user, params).execute
      end

      @project = Project.new(params.merge(creator: current_user))

      validate_import_source_enabled!

      @project.visibility_level = @project.group.visibility_level unless @project.visibility_level_allowed_by_group?

      # If a project is newly created it should have shared runners settings
      # based on its group having it enabled. This is like the "default value"
      @project.shared_runners_enabled = false if !params.key?(:shared_runners_enabled) && @project.group && @project.group.shared_runners_setting != 'enabled'

      # Make sure that the user is allowed to use the specified visibility level
      if project_visibility.restricted?
        deny_visibility_level(@project, project_visibility.visibility_level)
        return @project
      end

      set_project_name_from_path

      # get namespace id
      namespace_id = params[:namespace_id] || current_user.namespace_id
      @project.namespace_id = namespace_id.to_i

      @project.check_personal_projects_limit
      return @project if @project.errors.any?

      validate_create_permissions
      validate_import_permissions
      return @project if @project.errors.any?

      @relations_block&.call(@project)
      yield(@project) if block_given?

      validate_classification_label(@project, :external_authorization_classification_label)

      # If the block added errors, don't try to save the project
      return @project if @project.errors.any?

      @project.creator = current_user

      save_project_and_import_data

      Gitlab::ApplicationContext.with_context(project: @project) do
        after_create_actions if @project.persisted?

        import_schedule
      end

      @project
    rescue ActiveRecord::RecordInvalid => e
      message = "Unable to save #{e.inspect}: #{e.record.errors.full_messages.join(", ")}"
      fail(error: message)
    rescue ImportSourceDisabledError => e
      @project.errors.add(:import_source_disabled, e.message) if @project
      fail(error: e.message)
    rescue StandardError => e
      @project.errors.add(:base, e.message) if @project
      fail(error: e.message)
    end

    protected

    def validate_create_permissions
      return if current_user.can?(:create_projects, parent_namespace)

      @project.errors.add(:namespace, "is not valid")
    end

    def validate_import_permissions
      return unless @project.import?
      return if current_user.can?(:import_projects, parent_namespace)

      @project.errors.add(:user, 'is not allowed to import projects')
    end

    def after_create_actions
      log_info("#{current_user.name} created a new project \"#{@project.full_name}\"")

      if @project.import?
        Gitlab::Tracking.event(self.class.name, 'import_project', user: current_user)
      else
        # Skip writing the config for project imports/forks because it
        # will always fail since the Git directory doesn't exist until
        # a background job creates it (see Project#add_import_job).
        @project.set_full_path
      end

      unless @project.gitlab_project_import?
        @project.create_wiki unless skip_wiki?
      end

      @project.track_project_repository

      create_project_settings

      yield if block_given?

      event_service.create_project(@project, current_user)
      system_hook_service.execute_hooks_for(@project, :create)

      setup_authorizations

      project.invalidate_personal_projects_count_of_owner

      Projects::PostCreationWorker.perform_async(@project.id)

      create_readme if @initialize_with_readme
      create_sast_commit if @initialize_with_sast

      publish_event
    end

    def create_project_settings
      Gitlab::Pages.add_unique_domain_to(project)

      @project.project_setting.save if @project.project_setting.changed?
    end

    # Add an authorization for the current user authorizations inline
    # (so they can access the project immediately after this request
    # completes), and any other affected users in the background
    def setup_authorizations
      if @project.group
        group_access_level = @project.group.max_member_access_for_user(
          current_user,
          only_concrete_membership: true
        )

        if group_access_level > GroupMember::NO_ACCESS
          current_user.project_authorizations.safe_find_or_create_by!(
            project: @project,
            access_level: group_access_level)
        end

        AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(@project.id)
        # AuthorizedProjectsWorker uses an exclusive lease per user but
        # specialized workers might have synchronization issues. Until we
        # compare the inconsistency rates of both approaches, we still run
        # AuthorizedProjectsWorker but with some delay and lower urgency as a
        # safety net.
        @project.group.refresh_members_authorized_projects(
          priority: UserProjectAccessChangedService::LOW_PRIORITY
        )
      else
        owner_user = @project.namespace.owner
        owner_member = @project.add_owner(owner_user, current_user: current_user)

        # There is a possibility that the sidekiq job to refresh the authorizations of the owner_user in this project
        # isn't picked up (or finished) by the time the user is redirected to the newly created project's page.
        # If that happens, the user will hit a 404. To avoid that scenario, we manually create a `project_authorizations` record for the user here.
        if owner_member.persisted?
          owner_user.project_authorizations.safe_find_or_create_by(
            project: @project,
            access_level: ProjectMember::OWNER
          )
        end
        # During the process of adding a project owner, a check on permissions is made on the user which caches
        # the max member access for that user on this project.
        # Since that is `0` before the member is created - and we are still inside the request
        # cycle when we need to do other operations that might check those permissions (e.g. write a commit)
        # we need to purge that cache so that the updated permissions is fetched instead of using the outdated cached value of 0
        # from before member creation
        @project.team.purge_member_access_cache_for_user_id(owner_user.id)
      end
    end

    def create_readme
      commit_attrs = {
        branch_name: default_branch,
        commit_message: 'Initial commit',
        file_path: 'README.md',
        file_content: readme_content
      }

      Files::CreateService.new(@project, current_user, commit_attrs).execute
    end

    def create_sast_commit
      ::Security::CiConfiguration::SastCreateService.new(@project, current_user, { initialize_with_sast: true }, commit_on_default: true).execute
    end

    def repository_object_format
      @use_sha256_repository ? Repository::FORMAT_SHA256 : Repository::FORMAT_SHA1
    end

    def readme_content
      readme_attrs = {
        default_branch: default_branch
      }

      @readme_template.presence || ReadmeRendererService.new(@project, current_user, readme_attrs).execute
    end

    def skip_wiki?
      !@project.feature_available?(:wiki, current_user) || @skip_wiki
    end

    def save_project_and_import_data
      Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
        %w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424281'
      ) do
        ApplicationRecord.transaction do
          @project.build_or_assign_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data

          # Avoid project callbacks being triggered multiple times by saving the parent first.
          # See https://github.com/rails/rails/issues/41701.
          Namespaces::ProjectNamespace.create_from_project!(@project) if @project.valid?

          if @project.saved?
            Integration.create_from_active_default_integrations(@project, :project_id)

            @project.create_labels unless @project.gitlab_project_import?

            next if @project.import?

            unless @project.create_repository(default_branch: default_branch, object_format: repository_object_format)
              raise 'Failed to create repository'
            end
          end
        end
      end
    end

    def fail(error:)
      message = "Unable to save project. Error: #{error}"
      log_message = message.dup

      log_message << " Project ID: #{@project.id}" if @project&.id
      Gitlab::AppLogger.error(log_message)

      if @project && @project.persisted? && @project.import_state
        @project.import_state.mark_as_failed(message)
      end

      @project
    end

    def set_project_name_from_path
      # if both name and path set - everything is ok
      return if @project.name.present? && @project.path.present?

      if @project.path.present?
        # Set project name from path
        @project.name = @project.path.dup
      elsif @project.name.present?
        # For compatibility - set path from name
        @project.path = @project.name.dup

        # TODO: Retained for backwards compatibility. Remove in API v5.
        #       When removed, validation errors will get bubbled up automatically.
        #       See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52725
        unless @project.path.match?(Gitlab::PathRegex.project_path_format_regex)
          @project.path = @project.path.parameterize
        end
      end
    end

    def extra_attributes_for_measurement
      {
        current_user: current_user&.name,
        project_full_path: "#{parent_namespace&.full_path}/#{@params[:path]}"
      }
    end

    private

    def default_branch
      @default_branch.presence || @project.default_branch_or_main
    end

    def validate_import_source_enabled!
      return unless @params[:import_type]

      import_type = @params[:import_type].to_s

      return if INTERNAL_IMPORT_SOURCES.include?(import_type)

      # Skip validation when creating project from a built in template
      return if @params[:import_export_upload].present? && import_type == 'gitlab_project'

      unless ::Gitlab::CurrentSettings.import_sources&.include?(import_type)
        raise ImportSourceDisabledError, "#{import_type} import source is disabled"
      end
    end

    def parent_namespace
      @parent_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
    end

    def create_from_template?
      @params[:template_name].present? || @params[:template_project_id].present?
    end

    def import_schedule
      if @project.errors.empty?
        @project.import_state.schedule if @project.import? && !@project.gitlab_project_migration?
      else
        fail(error: @project.errors.full_messages.join(', '))
      end
    end

    def project_visibility
      @project_visibility ||= Gitlab::VisibilityLevelChecker
        .new(current_user, @project, project_params: { import_data: @import_data })
        .level_restricted?
    end

    def build_topics
      topics = params.delete(:topics)
      tag_list = params.delete(:tag_list)
      topic_list = topics || tag_list

      params[:topic_list] ||= topic_list if topic_list
    end

    def publish_event
      event = Projects::ProjectCreatedEvent.new(data: {
        project_id: project.id,
        namespace_id: project.namespace_id,
        root_namespace_id: project.root_namespace.id
      })

      Gitlab::EventStore.publish(event)
    end
  end
end

Projects::CreateService.prepend_mod_with('Projects::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_and_projects do
  include ExternalAuthorizationServiceHelpers

  let(:user) { create :user }
  let(:project_name) { 'GitLab' }
  let(:opts) do
    {
      name: project_name,
      namespace_id: user.namespace.id
    }
  end

  context 'with labels' do
    subject(:project) { create_project(user, opts) }

    before_all do
      Label.create!(title: 'bug', template: true)
    end

    it 'creates labels on project creation' do
      expect(project.labels).to include have_attributes(
        type: eq('ProjectLabel'),
        project_id: eq(project.id),
        title: eq('bug')
      )
    end

    context 'using gitlab project import' do
      before do
        opts[:import_type] = 'gitlab_project'
      end

      it 'does not creates labels on project creation' do
        expect(project.labels.size).to eq(0)
      end
    end
  end

  describe 'setting name and path' do
    subject(:project) { create_project(user, opts) }

    context 'when both are set' do
      let(:opts) { { name: 'one', path: 'two' } }

      it 'keeps them as specified' do
        expect(project.name).to eq('one')
        expect(project.path).to eq('two')
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end

    context 'when path is set' do
      let(:opts) { { path: 'one.two_three-four' } }

      it 'sets name == path' do
        expect(project.path).to eq('one.two_three-four')
        expect(project.name).to eq(project.path)
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end

    context 'when name is a valid path' do
      let(:opts) { { name: 'one.two_three-four' } }

      it 'sets path == name' do
        expect(project.name).to eq('one.two_three-four')
        expect(project.path).to eq(project.name)
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end

    context 'when name is not a valid path' do
      let(:opts) { { name: 'one.two_three-four and five' } }

      # TODO: Retained for backwards compatibility. Remove in API v5.
      #       See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52725
      it 'parameterizes the name' do
        expect(project.name).to eq('one.two_three-four and five')
        expect(project.path).to eq('one-two_three-four-and-five')
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end
  end

  describe 'topics' do
    subject(:project) { create_project(user, opts) }

    context "with 'topics' parameter" do
      let(:opts) { { name: 'topic-project', topics: 'topics' } }

      it 'keeps them as specified' do
        expect(project.topic_list).to eq(%w[topics])
      end
    end

    context "with 'topic_list' parameter" do
      let(:opts) { { name: 'topic-project', topic_list: 'topic_list' } }

      it 'keeps them as specified' do
        expect(project.topic_list).to eq(%w[topic_list])
      end
    end

    context "with 'tag_list' parameter (deprecated)" do
      let(:opts) { { name: 'topic-project', tag_list: 'tag_list' } }

      it 'keeps them as specified' do
        expect(project.topic_list).to eq(%w[tag_list])
      end
    end
  end

  context 'user namespace' do
    it 'creates a project in user namespace' do
      project = create_project(user, opts)

      expect(project).to be_valid
      expect(project.first_owner).to eq(user)
      expect(project.team.maintainers).not_to include(user)
      expect(project.team.owners).to contain_exactly(user)
      expect(project.namespace).to eq(user.namespace)
      expect(project.project_namespace).to be_in_sync_with_project(project)
    end

    context 'project_authorizations record creation' do
      context 'when the project_authrizations records are not created via the callback' do
        it 'still creates project_authrizations record for the user' do
          # stub out the callback that creates project_authorizations records on the `ProjectMember` model.
          expect_next_instance_of(ProjectMember) do |member|
            expect(member).to receive(:refresh_member_authorized_projects).and_return(nil)
          end

          project = create_project(user, opts)

          expected_record = project.project_authorizations.where(
            user: user,
            access_level: ProjectMember::OWNER
          )

          expect(expected_record).to exist
        end
      end
    end

    context 'when the passed in namespace is for a bot user' do
      let(:bot_user) { create(:user, :project_bot) }
      let(:opts) do
        { name: project_name, namespace_id: bot_user.namespace.id }
      end

      it 'raises an error' do
        project = create_project(bot_user, opts)

        expect(project.errors.errors.length).to eq 1
        expect(project.errors.messages[:namespace].first).to eq("is not valid")
      end
    end
  end

  describe 'after create actions' do
    it 'invalidate personal_projects_count caches' do
      expect(Rails.cache).to receive(:delete).with(['users', user.id, 'personal_projects_count'])

      create_project(user, opts)
    end

    it 'creates associated project settings' do
      project = create_project(user, opts)

      expect(project.project_setting).to be_persisted
    end

    it_behaves_like 'storing arguments in the application context' do
      let(:expected_params) { { project: subject.full_path } }

      subject { create_project(user, opts) }
    end

    it 'logs creation' do
      expect(Gitlab::AppLogger).to receive(:info).with(/#{user.name} created a new project/)

      create_project(user, opts)
    end

    it 'publishes a ProjectCreatedEvent' do
      group = create(:group, :nested).tap do |group|
        group.add_owner(user)
      end

      expect { create_project(user, name: 'Project', path: 'project', namespace_id: group.id) }
        .to publish_event(Projects::ProjectCreatedEvent)
        .with(
          project_id: kind_of(Numeric),
          namespace_id: group.id,
          root_namespace_id: group.parent_id
        )
    end
  end

  context "admin creates project with other user's namespace_id" do
    context 'when admin mode is enabled', :enable_admin_mode do
      it 'sets the correct permissions' do
        admin = create(:admin)
        project = create_project(admin, opts)

        expect(project).to be_persisted
        expect(project.owner).to eq(user)
        expect(project.first_owner).to eq(user)
        expect(project.team.owners).to contain_exactly(user)
        expect(project.namespace).to eq(user.namespace)
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end

    context 'when admin mode is disabled' do
      it 'is not allowed' do
        admin = create(:admin)
        project = create_project(admin, opts)

        expect(project).not_to be_persisted
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end
  end

  context 'group namespace' do
    let(:group) do
      create(:group).tap do |group|
        group.add_owner(user)
      end
    end

    before do
      user.refresh_authorized_projects # Ensure cache is warm
    end

    subject(:project) { create_project(user, opts.merge!(namespace_id: group.id)) }

    shared_examples 'has sync-ed traversal_ids' do
      specify { expect(project.reload.project_namespace.traversal_ids).to eq([project.namespace.traversal_ids, project.project_namespace.id].flatten.compact) }
    end

    it 'creates the project' do
      expect(project).to be_valid
      expect(project.owner).to eq(group)
      expect(project.namespace).to eq(group)
      expect(project.team.owners).to include(user)
      expect(user.authorized_projects).to include(project)
      expect(project.project_namespace).to be_in_sync_with_project(project)
    end

    it_behaves_like 'has sync-ed traversal_ids'

    context 'when project is an import' do
      before do
        stub_application_setting(import_sources: ['gitlab_project'])
      end

      context 'when user is not allowed to import projects' do
        let(:group) do
          create(:group).tap do |group|
            group.add_developer(user)
          end
        end

        it 'does not create the project' do
          project = create_project(user, opts.merge!(namespace_id: group.id, import_type: 'gitlab_project'))

          expect(project).not_to be_persisted
          expect(project.errors.messages[:user].first).to eq('is not allowed to import projects')
        end
      end
    end
  end

  context 'group sharing', :sidekiq_inline do
    let_it_be(:group) { create(:group) }
    let_it_be(:shared_group) { create(:group) }
    let_it_be(:shared_group_user) { create(:user) }

    let(:opts) do
      {
        name: project_name,
        namespace_id: shared_group.id
      }
    end

    before do
      create(:group_group_link, shared_group: shared_group, shared_with_group: group)

      shared_group.add_maintainer(shared_group_user)
      group.add_developer(user)
    end

    it 'updates authorization' do
      shared_group_project = create_project(shared_group_user, opts)

      expect(
        Ability.allowed?(shared_group_user, :read_project, shared_group_project)
      ).to be_truthy
      expect(
        Ability.allowed?(user, :read_project, shared_group_project)
      ).to be_truthy
    end
  end

  context 'user with project limit' do
    let_it_be(:user_with_projects_limit) { create(:user, projects_limit: 0) }

    let(:params) { opts.merge!(namespace_id: target_namespace.id) }

    subject(:project) { create_project(user_with_projects_limit, params) }

    context 'under personal namespace' do
      let(:target_namespace) { user_with_projects_limit.namespace }

      it 'cannot create a project' do
        expect(project.errors.errors.length).to eq 1
        expect(project.errors.messages[:limit_reached].first).to eq(_('You cannot create projects in your personal namespace. Contact your GitLab administrator.'))
      end
    end

    context 'under group namespace' do
      let_it_be(:group) do
        create(:group).tap do |group|
          group.add_owner(user_with_projects_limit)
        end
      end

      let(:target_namespace) { group }

      it 'can create a project' do
        expect(project).to be_valid
        expect(project).to be_saved
        expect(project.errors.errors.length).to eq 0
      end
    end
  end

  context 'membership overrides', :sidekiq_inline do
    let_it_be(:group) { create(:group, :private) }
    let_it_be(:subgroup_for_projects) { create(:group, :private, parent: group) }
    let_it_be(:subgroup_for_access) { create(:group, :private, parent: group) }
    let_it_be(:group_maintainer) { create(:user) }

    let(:group_access_level) { Gitlab::Access::REPORTER }
    let(:subgroup_access_level) { Gitlab::Access::DEVELOPER }
    let(:share_max_access_level) { Gitlab::Access::MAINTAINER }
    let(:opts) do
      {
        name: project_name,
        namespace_id: subgroup_for_projects.id
      }
    end

    before do
      group.add_maintainer(group_maintainer)

      create(
        :group_group_link,
        shared_group: subgroup_for_projects,
        shared_with_group: subgroup_for_access,
        group_access: share_max_access_level
      )
    end

    context 'membership is higher from group hierarchy' do
      let(:group_access_level) { Gitlab::Access::MAINTAINER }

      it 'updates authorization' do
        create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)
        create(:group_member, access_level: group_access_level, group: group, user: user)

        subgroup_project = create_project(group_maintainer, opts)

        project_authorization = ProjectAuthorization.where(
          project_id: subgroup_project.id,
          user_id: user.id,
          access_level: group_access_level)

        expect(project_authorization).to exist
      end
    end

    context 'membership is higher from group share' do
      let(:subgroup_access_level) { Gitlab::Access::MAINTAINER }

      context 'share max access level is not limiting' do
        it 'updates authorization' do
          create(:group_member, access_level: group_access_level, group: group, user: user)
          create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)

          subgroup_project = create_project(group_maintainer, opts)

          project_authorization = ProjectAuthorization.where(
            project_id: subgroup_project.id,
            user_id: user.id,
            access_level: subgroup_access_level)

          expect(project_authorization).to exist
        end
      end

      context 'share max access level is limiting' do
        let(:share_max_access_level) { Gitlab::Access::DEVELOPER }

        it 'updates authorization' do
          create(:group_member, access_level: group_access_level, group: group, user: user)
          create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)

          subgroup_project = create_project(group_maintainer, opts)

          project_authorization = ProjectAuthorization.where(
            project_id: subgroup_project.id,
            user_id: user.id,
            access_level: share_max_access_level)

          expect(project_authorization).to exist
        end
      end
    end
  end

  context 'error handling' do
    it 'handles invalid options' do
      opts[:invalid] = 'option'
      expect(create_project(user, opts)).to eq(nil)
    end
  end

  context 'wiki_enabled creates repository directory' do
    context 'wiki_enabled true creates wiki repository directory' do
      it do
        project = create_project(user, opts)

        expect(wiki_repo(project).exists?).to be_truthy
      end
    end

    context 'wiki_enabled false does not create wiki repository directory' do
      it do
        opts[:wiki_enabled] = false
        project = create_project(user, opts)

        expect(wiki_repo(project).exists?).to be_falsey
      end
    end

    def wiki_repo(project)
      relative_path = ProjectWiki.new(project).disk_path + '.git'
      Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar', project.full_path)
    end
  end

  context 'import data' do
    let(:import_data) { { data: { 'test' => 'some data' } } }
    let(:imported_project) { create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data }) }

    it 'does not write repository config' do
      expect_next_instance_of(Project) do |project|
        expect(project).not_to receive(:set_full_path)
      end

      imported_project
      expect(imported_project.project_namespace).to be_in_sync_with_project(imported_project)
    end

    it 'stores import data and URL' do
      expect(imported_project.import_data).to be_persisted
      expect(imported_project.import_data.data).to eq(import_data[:data])
      expect(imported_project.import_url).to eq('http://import-url')
    end

    it 'tracks for imported project' do
      imported_project

      expect_snowplow_event(category: described_class.name, action: 'import_project', user: user)
    end

    describe 'import scheduling' do
      context 'when project import type is gitlab project migration' do
        it 'does not schedule project import' do
          opts[:import_type] = 'gitlab_project_migration'

          project = create_project(user, opts)

          expect(project.import_state.status).to eq('none')
        end
      end
    end
  end

  context 'builds_enabled global setting' do
    let(:project) { create_project(user, opts) }

    subject { project.builds_enabled? }

    context 'global builds_enabled false does not enable CI by default' do
      before do
        project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
      end

      it { is_expected.to be_falsey }
    end

    context 'global builds_enabled true does enable CI by default' do
      it { is_expected.to be_truthy }
    end
  end

  context 'default visibility level' do
    let(:group) { create(:group, :private) }

    using RSpec::Parameterized::TableSyntax

    where(:case_name, :group_level, :project_level) do
      [
        ['in public group',   Gitlab::VisibilityLevel::PUBLIC,   Gitlab::VisibilityLevel::INTERNAL],
        ['in internal group', Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::INTERNAL],
        ['in private group',  Gitlab::VisibilityLevel::PRIVATE,  Gitlab::VisibilityLevel::PRIVATE]
      ]
    end

    with_them do
      before do
        stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
        group.add_developer(user)
        group.update!(visibility_level: group_level)

        opts.merge!(
          name: 'test',
          namespace: group,
          path: 'foo'
        )
      end

      it 'creates project with correct visibility level', :aggregate_failures do
        project = create_project(user, opts)

        expect(project).to respond_to(:errors)
        expect(project.errors).to be_blank
        expect(project.visibility_level).to eq(project_level)
        expect(project).to be_saved
        expect(project).to be_valid
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end
    end
  end

  context 'restricted visibility level' do
    before do
      stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
    end

    shared_examples 'restricted visibility' do
      it 'does not allow a restricted visibility level for non-admins' do
        project = create_project(user, opts)

        expect(project).to respond_to(:errors)
        expect(project.errors.messages).to have_key(:visibility_level)
        expect(project.errors.messages[:visibility_level].first).to(
          match('restricted by your GitLab administrator')
        )
        expect(project.project_namespace).to be_in_sync_with_project(project)
      end

      it 'does not allow a restricted visibility level for admins when admin mode is disabled' do
        admin = create(:admin)
        project = create_project(admin, opts)

        expect(project.errors.any?).to be(true)
        expect(project.saved?).to be_falsey
      end

      it 'allows a restricted visibility level for admins when admin mode is enabled', :enable_admin_mode do
        admin = create(:admin)
        project = create_project(admin, opts)

        expect(project.errors.any?).to be(false)
        expect(project.saved?).to be(true)
      end
    end

    context 'when visibility is project based' do
      before do
        opts.merge!(
          visibility_level: Gitlab::VisibilityLevel::PUBLIC
        )
      end

      include_examples 'restricted visibility'
    end

    context 'when visibility is overridden' do
      let(:visibility) { 'public' }

      before do
        opts.merge!(
          import_data: {
            data: {
              override_params: {
                visibility: visibility
              }
            }
          }
        )
      end

      include_examples 'restricted visibility'

      context 'when visibility is misspelled' do
        let(:visibility) { 'publik' }

        it 'does not restrict project creation' do
          project = create_project(user, opts)

          expect(project.errors.any?).to be(false)
          expect(project.saved?).to be(true)
        end
      end
    end
  end

  context 'repository creation' do
    it 'synchronously creates the repository' do
      expect_next_instance_of(Project) do |instance|
        expect(instance).to receive(:create_repository).and_return(true)
      end

      project = create_project(user, opts)

      expect(project).to be_valid
      expect(project).to be_persisted
      expect(project.owner).to eq(user)
      expect(project.namespace).to eq(user.namespace)
      expect(project.project_namespace).to be_in_sync_with_project(project)
    end

    it 'raises when repository fails to create' do
      expect_next_instance_of(Project) do |instance|
        expect(instance).to receive(:create_repository).and_return(false)
      end

      project = create_project(user, opts)
      expect(project).not_to be_persisted
      expect(project.errors.messages).to have_key(:base)
      expect(project.errors.messages[:base].first).to match('Failed to create repository')
    end

    context 'when another repository already exists on disk' do
      let(:opts) do
        {
          name: 'existing',
          namespace_id: user.namespace.id
        }
      end

      context 'with legacy storage' do
        let(:raw_fake_repo) { Gitlab::Git::Repository.new('default', File.join(user.namespace.full_path, 'existing.git'), nil, nil) }

        before do
          stub_application_setting(hashed_storage_enabled: false)
          raw_fake_repo.create_repository
        end

        after do
          raw_fake_repo.remove
        end

        it 'does not allow to create a project when path matches existing repository on disk' do
          project = create_project(user, opts)

          expect(project).not_to be_persisted
          expect(project).to respond_to(:errors)
          expect(project.errors.messages).to have_key(:base)
          expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
          expect(project.project_namespace).to be_in_sync_with_project(project)
        end

        it 'does not allow to import project when path matches existing repository on disk' do
          project = create_project(user, opts.merge({ import_url: 'https://gitlab.com/gitlab-org/gitlab-test.git' }))

          expect(project).not_to be_persisted
          expect(project).to respond_to(:errors)
          expect(project.errors.messages).to have_key(:base)
          expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
          expect(project.project_namespace).to be_in_sync_with_project(project)
        end
      end

      context 'with hashed storage' do
        let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
        let(:hashed_path) { '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
        let(:raw_fake_repo) { Gitlab::Git::Repository.new('default', "#{hashed_path}.git", nil, nil) }

        before do
          allow(Digest::SHA2).to receive(:hexdigest) { hash }
          raw_fake_repo.create_repository
        end

        after do
          raw_fake_repo.remove
        end

        it 'does not allow to create a project when path matches existing repository on disk' do
          project = create_project(user, opts)

          expect(project).not_to be_persisted
          expect(project).to respond_to(:errors)
          expect(project.errors.messages).to have_key(:base)
          expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
          expect(project.project_namespace).to be_in_sync_with_project(project)
        end
      end
    end
  end

  context 'when readme initialization is requested' do
    let(:project) { create_project(user, opts) }

    before do
      opts[:initialize_with_readme] = '1'
    end

    shared_examples 'a repo with a README.md' do
      it { expect(project.repository.commit_count).to be(1) }
      it { expect(project.repository.readme.name).to eql('README.md') }
      it { expect(project.repository.readme.data).to include(expected_content) }
    end

    it_behaves_like 'a repo with a README.md' do
      let(:expected_content) do
        <<~MARKDOWN
          cd existing_repo
          git remote add origin #{project.http_url_to_repo}
          git branch -M master
          git push -uf origin master
        MARKDOWN
      end
    end

    context 'and a readme_template is specified' do
      before do
        opts[:readme_template] = "# GitLab\nThis is customized readme."
      end

      it_behaves_like 'a repo with a README.md' do
        let(:expected_content) { "# GitLab\nThis is customized readme." }
      end
    end

    context 'and default_branch is specified' do
      before do
        opts[:default_branch] = 'example_branch'
      end

      it 'creates the correct branch' do
        expect(project.repository.branch_names).to contain_exactly('example_branch')
      end

      it_behaves_like 'a repo with a README.md' do
        let(:expected_content) do
          <<~MARKDOWN
            cd existing_repo
            git remote add origin #{project.http_url_to_repo}
            git branch -M example_branch
            git push -uf origin example_branch
          MARKDOWN
        end
      end
    end

    context 'and the default branch setting is configured' do
      before do
        allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('example_branch')
      end

      it 'creates the correct branch' do
        expect(project.repository.branch_names).to contain_exactly('example_branch')
      end

      it_behaves_like 'a repo with a README.md' do
        let(:expected_content) do
          <<~MARKDOWN
            cd existing_repo
            git remote add origin #{project.http_url_to_repo}
            git branch -M example_branch
            git push -uf origin example_branch
          MARKDOWN
        end
      end
    end
  end

  context 'when SAST initialization is requested' do
    let(:project) { create_project(user, opts) }

    before do
      opts[:initialize_with_sast] = '1'
      allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('main')
    end

    it 'creates a commit for SAST', :aggregate_failures do
      expect(project.repository.commit_count).to be(1)
      expect(project.repository.commit.message).to eq(
        'Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist'
      )
    end
  end

  context 'when SHA256 format is requested' do
    let(:project) { create_project(user, opts) }
    let(:opts) { super().merge(initialize_with_readme: true, use_sha256_repository: true) }

    before do
      allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('main')
    end

    it 'creates a repository with SHA256 commit hashes', :aggregate_failures do
      expect(project.repository.commit_count).to be(1)
      expect(project.commit.id.size).to eq 64
    end

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

      it 'creates a repository with default SHA1 commit hash' do
        expect(project.repository.commit_count).to be(1)
        expect(project.commit.id.size).to eq 40
      end
    end
  end

  describe 'create integration for the project' do
    subject(:project) { create_project(user, opts) }

    context 'with an active instance-level integration' do
      let!(:instance_integration) { create(:prometheus_integration, :instance, api_url: 'https://prometheus.instance.com/') }

      it 'creates an integration from the instance-level integration' do
        expect(project.integrations.count).to eq(1)
        expect(project.integrations.first.api_url).to eq(instance_integration.api_url)
        expect(project.integrations.first.inherit_from_id).to eq(instance_integration.id)
      end

      context 'with an active group-level integration' do
        let!(:group_integration) { create(:prometheus_integration, :group, group: group, api_url: 'https://prometheus.group.com/') }
        let!(:group) do
          create(:group).tap do |group|
            group.add_owner(user)
          end
        end

        let(:opts) do
          {
            name: project_name,
            namespace_id: group.id
          }
        end

        it 'creates an integration from the group-level integration' do
          expect(project.integrations.count).to eq(1)
          expect(project.integrations.first.api_url).to eq(group_integration.api_url)
          expect(project.integrations.first.inherit_from_id).to eq(group_integration.id)
        end

        context 'with an active subgroup' do
          let!(:subgroup_integration) { create(:prometheus_integration, :group, group: subgroup, api_url: 'https://prometheus.subgroup.com/') }
          let!(:subgroup) do
            create(:group, parent: group).tap do |subgroup|
              subgroup.add_owner(user)
            end
          end

          let(:opts) do
            {
              name: project_name,
              namespace_id: subgroup.id
            }
          end

          it 'creates an integration from the subgroup-level integration' do
            expect(project.integrations.count).to eq(1)
            expect(project.integrations.first.api_url).to eq(subgroup_integration.api_url)
            expect(project.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
          end
        end
      end
    end
  end

  context 'when skip_disk_validation is used' do
    it 'sets the project attribute' do
      opts[:skip_disk_validation] = true
      project = create_project(user, opts)

      expect(project.skip_disk_validation).to be_truthy
    end
  end

  it 'calls the passed block' do
    fake_block = double('block')
    opts[:relations_block] = fake_block

    expect_next_instance_of(Project) do |project|
      expect(fake_block).to receive(:call).with(project)
    end

    create_project(user, opts)
  end

  it 'writes project full path to gitaly' do
    project = create_project(user, opts)

    expect(project.repository.full_path).to eq project.full_path
  end

  it 'triggers PostCreationWorker' do
    expect(Projects::PostCreationWorker).to receive(:perform_async).with(a_kind_of(Integer))

    create_project(user, opts)
  end

  context 'when import source is enabled' do
    before do
      stub_application_setting(import_sources: ['github'])
    end

    it 'does not raise an error when import_source is string' do
      opts[:import_type] = 'github'

      project = create_project(user, opts)

      expect(project).to be_persisted
      expect(project.errors).to be_blank
    end

    it 'does not raise an error when import_source is symbol' do
      opts[:import_type] = :github

      project = create_project(user, opts)

      expect(project).to be_persisted
      expect(project.errors).to be_blank
    end
  end

  context 'when import source is disabled' do
    before do
      stub_application_setting(import_sources: [])
      opts[:import_type] = 'git'
    end

    it 'raises an error' do
      project = create_project(user, opts)

      expect(project).to respond_to(:errors)
      expect(project.errors).to have_key(:import_source_disabled)
      expect(project.saved?).to be_falsey
    end
  end

  context 'with external authorization enabled' do
    before do
      enable_external_authorization_service_check
    end

    it 'does not save the project with an error if the service denies access' do
      expect(::Gitlab::ExternalAuthorization)
        .to receive(:access_allowed?).with(user, 'new-label', any_args) { false }

      project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' }))

      expect(project.errors[:external_authorization_classification_label]).to be_present
      expect(project).not_to be_persisted
    end

    it 'saves the project when the user has access to the label' do
      expect(::Gitlab::ExternalAuthorization)
        .to receive(:access_allowed?).with(user, 'new-label', any_args) { true }.at_least(1).time

      project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' }))

      expect(project).to be_persisted
      expect(project.external_authorization_classification_label).to eq('new-label')
    end

    it 'does not save the project when the user has no access to the default label and no label is provided' do
      expect(::Gitlab::ExternalAuthorization)
        .to receive(:access_allowed?).with(user, 'default_label', any_args) { false }

      project = create_project(user, opts)

      expect(project.errors[:external_authorization_classification_label]).to be_present
      expect(project).not_to be_persisted
    end
  end

  context 'with specialized project_authorization workers' do
    let_it_be(:other_user) { create(:user) }
    let_it_be(:group) { create(:group) }

    let(:opts) do
      {
        name: project_name,
        namespace_id: group.id
      }
    end

    before do
      group.add_maintainer(user)
      group.add_developer(other_user)
    end

    it 'updates authorization for current_user' do
      project = create_project(user, opts)

      expect(
        Ability.allowed?(user, :read_project, project)
      ).to be_truthy
    end

    it 'schedules authorization update for users with access to group', :sidekiq_inline do
      stub_feature_flags(do_not_run_safety_net_auth_refresh_jobs: false)

      expect(AuthorizedProjectsWorker).not_to(
        receive(:bulk_perform_async)
      )
      expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to(
        receive(:perform_async).and_call_original
      )
      expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
        receive(:bulk_perform_in).with(
          1.hour,
          array_including([user.id], [other_user.id]),
          batch_delay: 30.seconds, batch_size: 100
        ).and_call_original
      )

      project = create_project(user, opts)

      expect(
        Ability.allowed?(other_user, :developer_access, project)
      ).to be_truthy
    end
  end

  def create_project(user, opts)
    Projects::CreateService.new(user, opts).execute
  end

  context 'shared Runners config' do
    using RSpec::Parameterized::TableSyntax

    let_it_be(:user) { create :user }

    context 'when parent group is present' do
      let_it_be(:group, reload: true) do
        create(:group) do |group|
          group.add_owner(user)
        end
      end

      before do
        group.update!(shared_runners_enabled: shared_runners_enabled,
          allow_descendants_override_disabled_shared_runners: allow_to_override)

        user.refresh_authorized_projects # Ensure cache is warm
      end

      context 'default value based on parent group setting' do
        where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project, :expected_result_for_project) do
          true  | false | nil | true
          false | true  | nil | false
          false | false | nil | false
        end

        with_them do
          it 'creates project following the parent config' do
            params = opts.merge(namespace_id: group.id)
            params = params.merge(shared_runners_enabled: desired_config_for_new_project) unless desired_config_for_new_project.nil?
            project = create_project(user, params)

            expect(project).to be_valid
            expect(project.shared_runners_enabled).to eq(expected_result_for_project)
            expect(project.project_namespace).to be_in_sync_with_project(project)
          end
        end
      end

      context 'parent group is present and allows desired config' do
        where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project, :expected_result_for_project) do
          true  | false | true  | true
          true  | false | false | false
          false | true  | false | false
          false | true  | true  | true
          false | false | false | false
        end

        with_them do
          it 'creates project following the parent config' do
            params = opts.merge(namespace_id: group.id, shared_runners_enabled: desired_config_for_new_project)
            project = create_project(user, params)

            expect(project).to be_valid
            expect(project.shared_runners_enabled).to eq(expected_result_for_project)
            expect(project.project_namespace).to be_in_sync_with_project(project)
          end
        end
      end

      context 'parent group is present and disallows desired config' do
        where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project) do
          false | false | true
        end

        with_them do
          it 'does not create project' do
            params = opts.merge(namespace_id: group.id, shared_runners_enabled: desired_config_for_new_project)
            project = create_project(user, params)

            expect(project.persisted?).to eq(false)
            expect(project).to be_invalid
            expect(project.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group does not allow it')
            expect(project.project_namespace).to be_in_sync_with_project(project)
          end
        end
      end
    end

    context 'parent group is not present' do
      where(:desired_config, :expected_result) do
        true  | true
        false | false
        nil   | true
      end

      with_them do
        it 'follows desired config' do
          opts[:shared_runners_enabled] = desired_config unless desired_config.nil?
          project = create_project(user, opts)

          expect(project).to be_valid
          expect(project.shared_runners_enabled).to eq(expected_result)
          expect(project.project_namespace).to be_in_sync_with_project(project)
        end
      end
    end
  end

  context 'when using access_level params' do
    def expect_not_disabled_features(project, exclude: [])
      ProjectFeature::FEATURES.excluding(exclude)
        .excluding(project.project_feature.send(:feature_validation_exclusion))
        .each do |feature|
          expect(project.project_feature.public_send(ProjectFeature.access_level_attribute(feature))).not_to eq(Featurable::DISABLED)
        end
    end

    # repository is tested on its own below because it requires other features to be set as well
    # package_registry has different behaviour and is modified from the model based on other attributes
    ProjectFeature::FEATURES.excluding(:repository, :package_registry).each do |feature|
      it "when using #{feature}", :aggregate_failures do
        feature_attribute = ProjectFeature.access_level_attribute(feature)
        opts[feature_attribute] = ProjectFeature.str_from_access_level(Featurable::DISABLED)
        project = create_project(user, opts)

        expect(project).to be_valid
        expect(project.project_feature.public_send(feature_attribute)).to eq(Featurable::DISABLED)

        expect_not_disabled_features(project, exclude: [feature])
      end
    end

    it 'when using repository', :aggregate_failures do
      # model validation will fail if builds or merge_requests have higher visibility than repository
      disabled = ProjectFeature.str_from_access_level(Featurable::DISABLED)
      opts[:repository_access_level] = disabled
      opts[:builds_access_level] = disabled
      opts[:merge_requests_access_level] = disabled
      project = create_project(user, opts)

      expect(project).to be_valid
      expect(project.project_feature.repository_access_level).to eq(Featurable::DISABLED)
      expect(project.project_feature.builds_access_level).to eq(Featurable::DISABLED)
      expect(project.project_feature.merge_requests_access_level).to eq(Featurable::DISABLED)

      expect_not_disabled_features(project, exclude: [:repository, :builds, :merge_requests])
    end
  end

  it 'adds pages unique domain', feature_category: :pages do
    stub_pages_setting(enabled: true)

    expect(Gitlab::Pages)
    .to receive(:add_unique_domain_to)
    .and_call_original

    project = create_project(user, opts)

    expect(project.project_setting.pages_unique_domain_enabled).to eq(true)
    expect(project.project_setting.pages_unique_domain).to be_present
  end
end


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

module Projects
  # Used by project imports, it removes any potential paths
  # included in an error message that could be stored in the DB
  class ImportErrorFilter
    ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/
    FILTER_MESSAGE = '[FILTERED]'

    def self.filter_message(message)
      message.gsub(ERROR_MESSAGE_FILTER, FILTER_MESSAGE)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ImportErrorFilter, feature_category: :importers do
  it 'filters any full paths' do
    message = 'Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file'

    expect(described_class.filter_message(message)).to eq('Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]')
  end

  it 'filters any relative paths ignoring single slash ones' do
    message = 'Error importing into my/project Permission denied @ unlink_internal - ../file/ and folder/../file'

    expect(described_class.filter_message(message)).to eq('Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED] and [FILTERED]')
  end
end


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

module Projects
  module Alerting
    class NotifyService < ::BaseProjectService
      extend ::Gitlab::Utils::Override
      include ::AlertManagement::AlertProcessing
      include ::AlertManagement::Responses

      def initialize(project, params)
        super(project: project, params: params.to_h)
      end

      def execute(token, integration = nil)
        @integration = integration

        return bad_request unless valid_payload_size?
        return forbidden unless active_integration?
        return unauthorized unless valid_token?(token)

        process_alert
        return bad_request unless alert.persisted?

        complete_post_processing_tasks

        success(alert)
      end

      private

      attr_reader :integration
      alias_method :payload, :params

      def valid_payload_size?
        Gitlab::Utils::DeepSize.new(params).valid?
      end

      def active_integration?
        integration&.active?
      end

      def valid_token?(token)
        token == integration.token
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::Alerting::NotifyService, feature_category: :groups_and_projects do
  let_it_be_with_reload(:project) { create(:project) }

  let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
  let(:payload_raw) { {} }

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

  before do
    stub_licensed_features(oncall_schedules: false, generic_alert_fingerprinting: false)
  end

  describe '#execute' do
    include_context 'incident management settings enabled'

    subject { service.execute(token, integration) }

    context 'with HTTP integration' do
      let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) }

      context 'with valid token' do
        let(:token) { integration.token }

        context 'with valid payload' do
          let_it_be(:environment) { create(:environment, project: project) }
          let_it_be(:fingerprint) { 'testing' }
          let_it_be(:source) { 'GitLab RSpec' }
          let_it_be(:starts_at) { Time.current.change(usec: 0) }

          let(:ended_at) { nil }
          let(:domain) { 'operations' }
          let(:payload_raw) do
            {
              title: 'alert title',
              start_time: starts_at.rfc3339,
              end_time: ended_at&.rfc3339,
              severity: 'low',
              monitoring_tool: source,
              service: 'GitLab Test Suite',
              description: 'Very detailed description',
              hosts: ['1.1.1.1', '2.2.2.2'],
              fingerprint: fingerprint,
              gitlab_environment_name: environment.name
            }.with_indifferent_access
          end

          let(:last_alert_attributes) do
            AlertManagement::Alert.last.attributes
              .except('id', 'iid', 'created_at', 'updated_at')
              .with_indifferent_access
          end

          it_behaves_like 'processes new firing alert'
          it_behaves_like 'properly assigns the alert properties'
          include_examples 'handles race condition in alert creation'

          it 'passes the integration to alert processing' do
            expect(Gitlab::AlertManagement::Payload)
              .to receive(:parse)
              .with(project, payload.to_h, integration: integration)
              .and_call_original

            subject
          end

          context 'with partial payload' do
            let_it_be(:source) { integration.name }
            let_it_be(:payload_raw) do
              {
                title: 'alert title',
                start_time: starts_at.rfc3339
              }
            end

            include_examples 'processes never-before-seen alert'

            it 'assigns the alert properties' do
              subject

              expect(last_alert_attributes).to match(
                project_id: project.id,
                title: payload_raw.fetch(:title),
                started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
                severity: 'critical',
                status: AlertManagement::Alert.status_value(:triggered),
                events: 1,
                hosts: [],
                domain: 'operations',
                payload: payload_raw.with_indifferent_access,
                issue_id: nil,
                description: nil,
                monitoring_tool: nil,
                service: nil,
                fingerprint: nil,
                ended_at: nil,
                prometheus_alert_id: nil,
                environment_id: nil
              )
            end

            context 'with existing alert with matching payload' do
              let_it_be(:fingerprint) { payload_raw.except(:start_time).stringify_keys }
              let_it_be(:gitlab_fingerprint) { Gitlab::AlertManagement::Fingerprint.generate(fingerprint) }
              let_it_be(:alert) { create(:alert_management_alert, project: project, fingerprint: gitlab_fingerprint) }

              include_examples 'processes never-before-seen alert'
            end
          end

          context 'with resolving payload' do
            let(:ended_at) { Time.current.change(usec: 0) }

            it_behaves_like 'processes recovery alert'
          end
        end

        context 'with overlong payload' do
          let(:payload_raw) { { 'the-payload-is-too-big' => true } }

          before do
            stub_const('::Gitlab::Utils::DeepSize::DEFAULT_MAX_DEPTH', 0)
          end

          it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
        end

        context 'with inactive integration' do
          before do
            integration.update!(active: false)
          end

          it_behaves_like 'alerts service responds with an error and takes no actions', :forbidden
        end
      end

      context 'with invalid token' do
        let(:token) { 'invalid-token' }

        it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
      end
    end

    context 'without HTTP integration' do
      let(:integration) { nil }
      let(:token) { nil }

      it_behaves_like 'alerts service responds with an error and takes no actions', :forbidden
    end
  end
end


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

module Projects
  module ContainerRepository
    class DeleteTagsService < BaseService
      LOG_DATA_BASE = { service_class: self.to_s }.freeze

      def execute(container_repository)
        @container_repository = container_repository

        unless container_expiration_policy?
          return error('access denied') unless can?(current_user, :destroy_container_image, project)
        end

        @tag_names = params[:tags]
        return error('not tags specified') if @tag_names.blank?
        return error('repository importing') if cancel_while_importing?

        delete_tags
      end

      private

      def delete_tags
        delete_service
          .execute
          .tap { |response| log_response(response) }
      end

      def delete_service
        if @container_repository.client.supports_tag_delete?
          ::Projects::ContainerRepository::Gitlab::DeleteTagsService.new(@container_repository, @tag_names)
        else
          ::Projects::ContainerRepository::ThirdParty::DeleteTagsService.new(@container_repository, @tag_names)
        end
      end

      def log_response(response)
        log_data = LOG_DATA_BASE.merge(
          container_repository_id: @container_repository.id,
          project_id: @container_repository.project_id,
          message: 'deleted tags',
          deleted_tags_count: response[:deleted]&.size
        ).compact

        if response[:status] == :success
          log_info(log_data)
        else
          log_data[:message] = response[:message]
          log_error(log_data)
        end
      end

      def cancel_while_importing?
        return true if @container_repository.importing?

        if container_expiration_policy?
          return @container_repository.pre_importing? || @container_repository.pre_import_done?
        end

        false
      end

      def container_expiration_policy?
        params[:container_expiration_policy].present?
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::DeleteTagsService, feature_category: :container_registry do
  using RSpec::Parameterized::TableSyntax
  include_context 'container repository delete tags service shared context'

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

  let_it_be(:available_service_classes) do
    [
      ::Projects::ContainerRepository::Gitlab::DeleteTagsService,
      ::Projects::ContainerRepository::ThirdParty::DeleteTagsService
    ]
  end

  shared_examples 'logging a success response' do
    it 'logs an info message' do
      expect(service).to receive(:log_info).with(
        {
          service_class: 'Projects::ContainerRepository::DeleteTagsService',
          message: 'deleted tags',
          container_repository_id: repository.id,
          project_id: repository.project_id,
          deleted_tags_count: tags.size
        }
      )

      subject
    end
  end

  shared_examples 'logging an error response' do |message: 'could not delete tags', extra_log: {}|
    it 'logs an error message' do
      log_data = {
          service_class: 'Projects::ContainerRepository::DeleteTagsService',
          message: message,
          container_repository_id: repository.id,
          project_id: repository.project_id
      }

      log_data.merge!(extra_log) if extra_log.any?

      expect(service).to receive(:log_error).with(log_data)

      subject
    end
  end

  shared_examples 'calling the correct delete tags service' do |expected_service_class|
    let(:service_response) { { status: :success, deleted: tags } }
    let(:excluded_service_class) { available_service_classes.excluding(expected_service_class).first }

    before do
      service_double = double
      expect(expected_service_class).to receive(:new).with(repository, tags).and_return(service_double)
      expect(excluded_service_class).not_to receive(:new)
      expect(service_double).to receive(:execute).and_return(service_response)
    end

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

    it_behaves_like 'logging a success response'

    context 'with an error service response' do
      let(:service_response) { { status: :error, message: 'could not delete tags' } }

      it { is_expected.to include(status: :error) }

      it_behaves_like 'logging an error response'
    end
  end

  shared_examples 'handling invalid params' do
    context 'with invalid params' do
      before do
        expect(::Projects::ContainerRepository::Gitlab::DeleteTagsService).not_to receive(:new)
        expect(::Projects::ContainerRepository::ThirdParty::DeleteTagsService).not_to receive(:new)
        expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest)
      end

      context 'when no params are specified' do
        let_it_be(:params) { {} }

        it { is_expected.to include(status: :error) }
      end

      context 'with empty tags' do
        let_it_be(:tags) { [] }

        it { is_expected.to include(status: :error) }
      end
    end
  end

  shared_examples 'supporting fast delete' do
    context 'when the registry supports fast delete' do
      before do
        allow(repository.client).to receive(:supports_tag_delete?).and_return(true)
      end

      it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::Gitlab::DeleteTagsService

      it_behaves_like 'handling invalid params'

      context 'with the real service' do
        before do
          stub_delete_reference_requests(tags)
          expect_delete_tags(tags)
        end

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

        it_behaves_like 'logging a success response'
      end

      context 'with a timeout error' do
        before do
          expect_next_instance_of(::Projects::ContainerRepository::Gitlab::DeleteTagsService) do |delete_service|
            expect(delete_service).to receive(:delete_tags).and_raise(::Projects::ContainerRepository::Gitlab::DeleteTagsService::TimeoutError)
          end
        end

        it { is_expected.to include(status: :error, message: 'error while deleting tags') }

        it_behaves_like 'logging an error response', message: 'error while deleting tags', extra_log: { deleted_tags_count: 0 }
      end
    end
  end

  describe '#execute' do
    let(:tags) { %w[A Ba] }

    subject { service.execute(repository) }

    context 'without permissions' do
      it { is_expected.to include(status: :error) }
    end

    context 'with permissions' do
      before do
        project.add_developer(user)
      end

      it_behaves_like 'supporting fast delete'

      context 'when the registry does not support fast delete' do
        before do
          allow(repository.client).to receive(:supports_tag_delete?).and_return(false)
        end

        it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService

        it_behaves_like 'handling invalid params'
      end

      context 'when the repository is importing' do
        where(:migration_state, :called_by_policy, :error_expected) do
          'default'         | false | false
          'default'         | true  | false
          'pre_importing'   | false | false
          'pre_importing'   | true  | true
          'pre_import_done' | false | false
          'pre_import_done' | true  | true
          'importing'       | false | true
          'importing'       | true  | true
          'import_done'     | false | false
          'import_done'     | true  | false
          'import_aborted'  | false | false
          'import_aborted'  | true  | false
          'import_skipped'  | false | false
          'import_skipped'  | true  | false
        end

        with_them do
          let(:params) { { tags: tags, container_expiration_policy: called_by_policy ? true : nil } }

          before do
            repository.update_columns(migration_state: migration_state, migration_import_started_at: Time.zone.now, migration_pre_import_started_at: Time.zone.now, migration_pre_import_done_at: Time.zone.now)
          end

          it 'returns an error response if expected' do
            if error_expected
              expect(subject).to include(status: :error, message: 'repository importing')
            else
              expect(service).to receive(:delete_tags).and_return(status: :success)
              expect(subject).not_to include(status: :error)
            end
          end
        end
      end
    end

    context 'without user' do
      let_it_be(:user) { nil }

      context 'when not run by a cleanup policy' do
        it { is_expected.to include(status: :error) }
      end

      context 'when run by a cleanup policy' do
        let(:params) { { tags: tags, container_expiration_policy: true } }

        it_behaves_like 'supporting fast delete'
      end
    end
  end
end


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

module Projects
  module ContainerRepository
    class CleanupTagsService < BaseContainerRepositoryService
      def execute
        return error('access denied') unless can_destroy?
        return error('invalid regex') unless valid_regex?

        cleanup_tags_service_class.new(container_repository: container_repository, current_user: current_user, params: params)
                                  .execute
      end

      private

      def cleanup_tags_service_class
        log_data = {
          container_repository_id: container_repository.id,
          container_repository_path: container_repository.path,
          project_id: project.id
        }

        if use_gitlab_service?
          log_info(log_data.merge(gitlab_cleanup_tags_service: true))
          ::Projects::ContainerRepository::Gitlab::CleanupTagsService
        else
          log_info(log_data.merge(third_party_cleanup_tags_service: true))
          ::Projects::ContainerRepository::ThirdParty::CleanupTagsService
        end
      end

      def use_gitlab_service?
        container_repository.migrated? &&
          container_repository.gitlab_api_client.supports_gitlab_api?
      end

      def can_destroy?
        return true if container_expiration_policy

        can?(current_user, :destroy_container_image, project)
      end

      def valid_regex?
        %w[name_regex_delete name_regex name_regex_keep].each do |param_name|
          regex = params[param_name]
          ::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
        end
        true
      rescue RegexpError => e
        ::Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
        false
      end

      def container_expiration_policy
        params['container_expiration_policy']
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::CleanupTagsService, feature_category: :container_registry do
  let_it_be_with_reload(:container_repository) { create(:container_repository) }
  let_it_be(:user) { container_repository.project.owner }

  let(:params) { {} }
  let(:extra_params) { {} }
  let(:service) { described_class.new(container_repository: container_repository, current_user: user, params: params.merge(extra_params)) }

  before do
    stub_container_registry_config(enabled: true)
  end

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

    shared_examples 'returning error message' do |message|
      it "returns error #{message}" do
        expect(::Projects::ContainerRepository::Gitlab::CleanupTagsService).not_to receive(:new)
        expect(::Projects::ContainerRepository::ThirdParty::CleanupTagsService).not_to receive(:new)
        expect(service).not_to receive(:log_info)

        expect(subject).to eq(status: :error, message: message)
      end
    end

    shared_examples 'handling invalid regular expressions' do
      shared_examples 'handling invalid regex' do
        it_behaves_like 'returning error message', 'invalid regex'

        it 'calls error tracking service' do
          expect(::Gitlab::ErrorTracking).to receive(:log_exception).and_call_original

          subject
        end
      end

      context 'when name_regex_delete is invalid' do
        let(:extra_params) { { 'name_regex_delete' => '*test*' } }

        it_behaves_like 'handling invalid regex'
      end

      context 'when name_regex is invalid' do
        let(:extra_params) { { 'name_regex' => '*test*' } }

        it_behaves_like 'handling invalid regex'
      end

      context 'when name_regex_keep is invalid' do
        let(:extra_params) { { 'name_regex_keep' => '*test*' } }

        it_behaves_like 'handling invalid regex'
      end
    end

    shared_examples 'handling all types of container repositories' do
      shared_examples 'calling service' do |service_class, extra_log_data: {}|
        let(:service_double) { instance_double(service_class.to_s) }

        it "uses cleanup tags service #{service_class}" do
          expect(service_class).to receive(:new).with(container_repository: container_repository, current_user: user, params: params).and_return(service_double)
          expect(service_double).to receive(:execute).and_return('return value')
          expect(service).to receive(:log_info)
                               .with(
                                 {
                                   container_repository_id: container_repository.id,
                                   container_repository_path: container_repository.path,
                                   project_id: container_repository.project.id
                                 }.merge(extra_log_data))
          expect(subject).to eq('return value')
        end
      end

      context 'with a migrated repository' do
        before do
          allow(container_repository).to receive(:migrated?).and_return(true)
        end

        context 'supporting the gitlab api' do
          before do
            allow(container_repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
          end

          it_behaves_like 'calling service', ::Projects::ContainerRepository::Gitlab::CleanupTagsService, extra_log_data: { gitlab_cleanup_tags_service: true }
        end

        context 'not supporting the gitlab api' do
          before do
            allow(container_repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(false)
          end

          it_behaves_like 'calling service', ::Projects::ContainerRepository::ThirdParty::CleanupTagsService, extra_log_data: { third_party_cleanup_tags_service: true }
        end
      end

      context 'with a non migrated repository' do
        before do
          allow(container_repository).to receive(:migrated?).and_return(false)
        end

        it_behaves_like 'calling service', ::Projects::ContainerRepository::ThirdParty::CleanupTagsService, extra_log_data: { third_party_cleanup_tags_service: true }
      end
    end

    context 'with valid user' do
      it_behaves_like 'handling invalid regular expressions'
      it_behaves_like 'handling all types of container repositories'
    end

    context 'for container expiration policy' do
      let(:user) { nil }
      let(:params) { { 'container_expiration_policy' => true } }

      it_behaves_like 'handling invalid regular expressions'
      it_behaves_like 'handling all types of container repositories'
    end

    context 'with not allowed user' do
      let_it_be(:user) { create(:user) }

      it_behaves_like 'returning error message', 'access denied'
    end

    context 'with no user' do
      let(:user) { nil }

      it_behaves_like 'returning error message', 'access denied'
    end
  end
end


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

module Projects
  module ContainerRepository
    class DestroyService < BaseService
      CLEANUP_TAGS_SERVICE_PARAMS = {
        'name_regex_delete' => '.*',
        'container_expiration_policy' => true, # to avoid permissions checks
        'keep_latest' => false
      }.freeze

      def execute(container_repository, disable_timeout: true)
        return error('Unauthorized access') unless can_destroy?

        # Delete tags outside of the transaction to avoid hitting an idle-in-transaction timeout
        if delete_tags(container_repository, disable_timeout) &&
            destroy_container_repository(container_repository)
          success
        else
          container_repository.delete_failed!
          error('Deletion failed for container repository')
        end
      end

      private

      def delete_tags(container_repository, disable_timeout)
        service = Projects::ContainerRepository::CleanupTagsService.new(
          container_repository: container_repository,
          params: CLEANUP_TAGS_SERVICE_PARAMS.merge('disable_timeout' => disable_timeout)
        )
        result = service.execute
        return true if result[:status] == :success

        log_error(error_message(container_repository, 'error in deleting tags'))
        false
      end

      def destroy_container_repository(container_repository)
        return true if container_repository.destroy

        log_error(error_message(container_repository, container_repository.errors.full_messages.join('. ')))
        false
      end

      def can_destroy?
        return true if skip_permission_check?

        can?(current_user, :destroy_container_image, project)
      end

      def error_message(container_repository, message)
        "Container repository with ID: #{container_repository.id} and path: #{container_repository.path} " \
          "failed with message: #{message}"
      end

      def skip_permission_check?
        !!params[:skip_permission_check]
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::DestroyService, feature_category: :container_registry do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project, :private) }
  let_it_be(:params) { {} }

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

  before do
    stub_container_registry_config(enabled: true)
  end

  shared_examples 'returning an error status with message' do |error_message|
    it 'returns an error status' do
      response = subject.execute(repository)

      expect(response).to include(status: :error, message: error_message)
    end
  end

  shared_examples 'executing with permissions' do
    let_it_be_with_refind(:repository) { create(:container_repository, :root, project: project) }

    before do
      stub_container_registry_tags(repository: :any, tags: %w[latest stable])
    end

    it 'deletes the repository' do
      expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
      expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
    end

    it 'sends disable_timeout = true as part of the params as default' do
      expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: true)
      expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
    end

    it 'sends disable_timeout = false as part of the params if it is set to false' do
      expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: false)
      expect { subject.execute(repository, disable_timeout: false) }.to change { ContainerRepository.count }.by(-1)
    end

    context 'when deleting the tags fails' do
      before do
        expect_cleanup_tags_service_with(container_repository: repository, return_status: :error)
        allow(Gitlab::AppLogger).to receive(:error).and_call_original
      end

      it 'sets status as deleted_failed' do
        subject.execute(repository)

        expect(repository).to be_delete_failed
      end

      it 'logs the error' do
        subject.execute(repository)

        expect(Gitlab::AppLogger).to have_received(:error)
        .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: error in deleting tags")
      end

      it_behaves_like 'returning an error status with message', 'Deletion failed for container repository'
    end

    context 'when destroying the repository fails' do
      before do
        expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
        allow(repository).to receive(:destroy).and_return(false)
        allow(repository.errors).to receive(:full_messages).and_return(['Error 1', 'Error 2'])
        allow(Gitlab::AppLogger).to receive(:error).and_call_original
      end

      it 'sets status as deleted_failed' do
        subject.execute(repository)

        expect(repository).to be_delete_failed
      end

      it 'logs the error' do
        subject.execute(repository)

        expect(Gitlab::AppLogger).to have_received(:error)
        .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: Error 1. Error 2")
      end

      it_behaves_like 'returning an error status with message', 'Deletion failed for container repository'
    end
  end

  context 'when user has access to registry' do
    before do
      project.add_developer(user)
    end

    it_behaves_like 'executing with permissions'
  end

  context 'when user does not have access to registry' do
    let_it_be(:repository) { create(:container_repository, :root, project: project) }

    it 'does not delete a repository' do
      expect { subject.execute(repository) }.not_to change { ContainerRepository.count }
    end

    it_behaves_like 'returning an error status with message', 'Unauthorized access'
  end

  context 'when called during project deletion' do
    let(:user) { nil }
    let(:params) { { skip_permission_check: true } }

    it_behaves_like 'executing with permissions'
  end

  context 'when there is no user' do
    let(:user) { nil }
    let(:repository) { create(:container_repository, :root, project: project) }

    it_behaves_like 'returning an error status with message', 'Unauthorized access'
  end

  def expect_cleanup_tags_service_with(container_repository:, return_status:, disable_timeout: true)
    delete_tags_service = instance_double(Projects::ContainerRepository::CleanupTagsService)

    expect(Projects::ContainerRepository::CleanupTagsService).to receive(:new).with(
      container_repository: container_repository,
      params: described_class::CLEANUP_TAGS_SERVICE_PARAMS.merge('disable_timeout' => disable_timeout)
    ).and_return(delete_tags_service)

    expect(delete_tags_service).to receive(:execute).and_return(status: return_status)
  end
end


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

module Projects
  module ContainerRepository
    module Gitlab
      class DeleteTagsService
        include BaseServiceUtility
        include ::Gitlab::Utils::StrongMemoize
        include ::Projects::ContainerRepository::Gitlab::Timeoutable

        def initialize(container_repository, tag_names)
          @container_repository = container_repository
          @tag_names = tag_names
          @deleted_tags = []
        end

        # Delete tags by name with a single DELETE request. This is only supported
        # by the GitLab Container Registry fork. See
        # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23325 for details.
        def execute
          return success(deleted: []) if @tag_names.empty?

          delete_tags
        rescue TimeoutError, ::Faraday::Error => e
          ::Gitlab::ErrorTracking.track_exception(e, tags_count: @tag_names&.size, container_repository_id: @container_repository&.id)
          error('error while deleting tags', nil, pass_back: { deleted: @deleted_tags, exception_class_name: e.class.name })
        end

        private

        def delete_tags
          start_time = Time.zone.now

          @tag_names.each do |name|
            raise TimeoutError if timeout?(start_time)

            if @container_repository.delete_tag(name)
              @deleted_tags.append(name)
            end
          end

          @deleted_tags.any? ? success(deleted: @deleted_tags) : error("could not delete tags: #{@tag_names.join(', ')}".truncate(1000))
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService, feature_category: :container_registry do
  include_context 'container repository delete tags service shared context'

  let(:service) { described_class.new(repository, tags) }

  describe '#execute' do
    let(:tags) { %w[A Ba] }

    subject { service.execute }

    RSpec.shared_examples 'deleting tags' do
      it 'deletes the tags by name' do
        stub_delete_reference_requests(tags)
        expect_delete_tags(tags)

        is_expected.to eq(status: :success, deleted: tags)
      end
    end

    context 'with tags to delete' do
      let(:timeout) { 10 }

      before do
        stub_application_setting(container_registry_delete_tags_service_timeout: timeout)
      end

      it_behaves_like 'deleting tags'

      it 'succeeds when tag delete returns 404' do
        stub_delete_reference_requests('A' => 200, 'Ba' => 404)

        is_expected.to eq(status: :success, deleted: tags)
      end

      it 'succeeds when a tag delete returns 500' do
        stub_delete_reference_requests('A' => 200, 'Ba' => 500)

        is_expected.to eq(status: :success, deleted: ['A'])
      end

      context 'with failures' do
        context 'when the delete request fails' do
          before do
            stub_delete_reference_requests('A' => 500, 'Ba' => 500)
          end

          it { is_expected.to eq(status: :error, message: "could not delete tags: #{tags.join(', ')}") }

          context 'when a large list of tag delete fails' do
            let(:tags) { Array.new(135) { |i| "tag#{i}" } }
            let(:container_repository) { instance_double(ContainerRepository) }

            before do
              allow(ContainerRepository).to receive(:find).with(repository).and_return(container_repository)
              tags.each do |tag|
                stub_delete_reference_requests(tag => 500)
              end
              allow(container_repository).to receive(:delete_tag).and_return(false)
            end

            it 'truncates the log message' do
              expect(subject).to eq(status: :error, message: "could not delete tags: #{tags.join(', ')}".truncate(1000))
            end
          end
        end
      end

      context 'with timeout' do
        context 'set to a valid value' do
          before do
            allow(service).to receive(:timeout?).and_return(false, true)
            stub_delete_reference_requests('A' => 200)
          end

          it { is_expected.to eq(status: :error, message: 'error while deleting tags', deleted: ['A'], exception_class_name: Projects::ContainerRepository::Gitlab::DeleteTagsService::TimeoutError.name) }

          it 'tracks the exception' do
            expect(::Gitlab::ErrorTracking)
              .to receive(:track_exception).with(::Projects::ContainerRepository::Gitlab::DeleteTagsService::TimeoutError, tags_count: tags.size, container_repository_id: repository.id)

            subject
          end
        end

        context 'set to 0' do
          let(:timeout) { 0 }

          it_behaves_like 'deleting tags'
        end

        context 'set to nil' do
          let(:timeout) { nil }

          it_behaves_like 'deleting tags'
        end
      end

      context 'with a network error' do
        before do
          expect(service).to receive(:delete_tags).and_raise(::Faraday::TimeoutError)
        end

        it { is_expected.to eq(status: :error, message: 'error while deleting tags', deleted: [], exception_class_name: ::Faraday::TimeoutError.name) }

        it 'tracks the exception' do
          expect(::Gitlab::ErrorTracking)
            .to receive(:track_exception).with(::Faraday::TimeoutError, tags_count: tags.size, container_repository_id: repository.id)

          subject
        end
      end
    end

    context 'with empty tags' do
      let_it_be(:tags) { [] }

      it 'does not remove anything' do
        expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest)

        is_expected.to eq(status: :success, deleted: [])
      end
    end
  end
end


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

module Projects
  module ContainerRepository
    module Gitlab
      class CleanupTagsService < CleanupTagsBaseService
        include ::Projects::ContainerRepository::Gitlab::Timeoutable

        TAGS_PAGE_SIZE = 1000

        def initialize(container_repository:, current_user: nil, params: {})
          super
          @params = params.dup
        end

        def execute
          with_timeout do |start_time, result|
            container_repository.each_tags_page(page_size: TAGS_PAGE_SIZE) do |tags|
              execute_for_tags(tags, result)

              raise TimeoutError if !timeout_disabled? && timeout?(start_time)
            end
          end
        end

        private

        def execute_for_tags(tags, overall_result)
          original_size = tags.size

          filter_out_latest!(tags)
          filter_by_name!(tags)

          tags = filter_by_keep_n(tags)
          tags = filter_by_older_than(tags)

          overall_result[:before_delete_size] += tags.size
          overall_result[:original_size] += original_size

          result = delete_tags(tags)

          overall_result[:deleted_size] += result[:deleted]&.size
          overall_result[:deleted] += result[:deleted]
          overall_result[:status] = result[:status] unless overall_result[:status] == :error
        end

        def with_timeout
          result = success(
            original_size: 0,
            before_delete_size: 0,
            deleted_size: 0,
            deleted: []
          )

          yield Time.zone.now, result

          result
        rescue TimeoutError
          result[:status] = :error

          result
        end

        def filter_by_keep_n(tags)
          partition_by_keep_n(tags).first
        end

        def filter_by_older_than(tags)
          partition_by_older_than(tags).first
        end

        def pushed_at(tag)
          tag.updated_at || tag.created_at
        end

        def timeout_disabled?
          params['disable_timeout'] || false
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService, feature_category: :container_registry do
  using RSpec::Parameterized::TableSyntax

  include_context 'for a cleanup tags service'

  let_it_be(:user) { create(:user) }
  let_it_be(:user) { create(:user) }
  let_it_be(:project, reload: true) { create(:project, :private) }

  let(:repository) { create(:container_repository, :root, project: project) }
  let(:service) { described_class.new(container_repository: repository, current_user: user, params: params) }
  let(:tags) { %w[latest A Ba Bb C D E] }

  before do
    allow(repository).to receive(:migrated?).and_return(true)

    project.add_maintainer(user) if user

    stub_container_registry_config(enabled: true)

    stub_const("#{described_class}::TAGS_PAGE_SIZE", tags_page_size)

    one_hour_ago = 1.hour.ago
    five_days_ago = 5.days.ago
    six_days_ago = 6.days.ago
    one_month_ago = 1.month.ago

    stub_tags(
      {
        'latest' => one_hour_ago,
        'A' => one_hour_ago,
        'Ba' => five_days_ago,
        'Bb' => six_days_ago,
        'C' => one_month_ago,
        'D' => nil,
        'E' => nil
      }
    )
  end

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

    context 'with several tags pages' do
      let(:tags_page_size) { 2 }

      it_behaves_like 'when regex matching everything is specified',
        delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]]

      it_behaves_like 'when regex matching everything is specified and latest is not kept',
        delete_expectations: [%w[latest A], %w[Ba Bb], %w[C D], %w[E]]

      it_behaves_like 'when delete regex matching specific tags is used'

      it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex'

      it_behaves_like 'with allow regex value',
        delete_expectations: [%w[A], %w[C D], %w[E]]

      it_behaves_like 'when keeping only N tags',
        delete_expectations: [%w[Bb]]

      it_behaves_like 'when not keeping N tags',
        delete_expectations: [%w[A], %w[Ba Bb], %w[C]]

      context 'when removing keeping only 3' do
        let(:params) do
          {
            'name_regex_delete' => '.*',
            'keep_n' => 3
          }
        end

        it_behaves_like 'not removing anything'
      end

      it_behaves_like 'when removing older than 1 day',
        delete_expectations: [%w[Ba Bb], %w[C]]

      it_behaves_like 'when combining all parameters',
        delete_expectations: [%w[Bb], %w[C]]

      it_behaves_like 'when running a container_expiration_policy',
        delete_expectations: [%w[Bb], %w[C]]

      context 'with a timeout' do
        let(:params) do
          { 'name_regex_delete' => '.*' }
        end

        it 'removes the first few pages' do
          expect(service).to receive(:timeout?).and_return(false, true)

          expect_delete(%w[A])
          expect_delete(%w[Ba Bb])

          response = expected_service_response(status: :error, deleted: %w[A Ba Bb], original_size: 4)

          is_expected.to eq(response)
        end

        context 'when disable_timeout is set to true' do
          let(:params) do
            { 'name_regex_delete' => '.*', 'disable_timeout' => true }
          end

          it 'does not check if it timed out' do
            expect(service).not_to receive(:timeout?)
          end

          it_behaves_like 'when regex matching everything is specified',
            delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]]
        end
      end
    end

    context 'with a single tags page' do
      let(:tags_page_size) { 1000 }

      it_behaves_like 'when regex matching everything is specified',
        delete_expectations: [%w[A Ba Bb C D E]]

      it_behaves_like 'when delete regex matching specific tags is used'

      it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex'

      it_behaves_like 'with allow regex value',
        delete_expectations: [%w[A C D E]]

      it_behaves_like 'when keeping only N tags',
        delete_expectations: [%w[Ba Bb C]]

      it_behaves_like 'when not keeping N tags',
        delete_expectations: [%w[A Ba Bb C]]

      it_behaves_like 'when removing keeping only 3',
        delete_expectations: [%w[Ba Bb C]]

      it_behaves_like 'when removing older than 1 day',
        delete_expectations: [%w[Ba Bb C]]

      it_behaves_like 'when combining all parameters',
        delete_expectations: [%w[Ba Bb C]]

      it_behaves_like 'when running a container_expiration_policy',
        delete_expectations: [%w[Ba Bb C]]
    end

    context 'with no tags page' do
      let(:tags_page_size) { 1000 }
      let(:deleted) { [] }
      let(:params) { {} }

      before do
        allow(repository.gitlab_api_client)
          .to receive(:tags)
          .and_return({})
      end

      it { is_expected.to eq(expected_service_response(status: :success, deleted: [], original_size: 0)) }
    end
  end

  private

  def stub_tags(tags)
    chunked = tags_page_size < tags.size
    previous_last = nil
    max_chunk_index = tags.size / tags_page_size

    tags.keys.in_groups_of(tags_page_size, false).each_with_index do |chunked_tag_names, index|
      last = index == max_chunk_index
      pagination_needed = chunked && !last

      response = {
        pagination: pagination_needed ? pagination_with(last: chunked_tag_names.last) : {},
        response_body: chunked_tag_names.map do |name|
          tag_raw_response(name, tags[name])
        end
      }

      allow(repository.gitlab_api_client)
        .to receive(:tags)
        .with(repository.path, page_size: described_class::TAGS_PAGE_SIZE, last: previous_last)
        .and_return(response)
      previous_last = chunked_tag_names.last
    end
  end

  def pagination_with(last:)
    {
      next: {
        uri: URI("http://test.org?last=#{last}")
      }
    }
  end

  def tag_raw_response(name, timestamp)
    timestamp_field = name.start_with?('B') ? 'updated_at' : 'created_at'
    {
      'name' => name,
      'digest' => 'sha256:1234567890',
      'media_type' => 'application/vnd.oci.image.manifest.v1+json',
      timestamp_field => timestamp&.iso8601
    }
  end
end