# frozen_string_literal: true
require 'spec_helper'

RSpec.describe Ci::CreatePipelineService do
  describe 'tags:' do
    let_it_be(:project) { create(:project, :repository) }
    let_it_be(:user)    { project.owner }

    let(:ref) { 'refs/heads/master' }
    let(:service) { described_class.new(project, user, { ref: ref }) }
    let(:pipeline) { create_pipeline }

    before do
      stub_yaml_config(config)
    end

    context 'with valid config' do
      let(:config) { { test: { script: 'ls', tags: %w[tag1 tag2] } } }

      it 'creates a pipeline', :aggregate_failures do
        expect(pipeline).to be_created_successfully
        expect(pipeline.builds.first.tag_list).to match_array(%w[tag1 tag2])
      end
    end

    context 'with too many tags' do
      let(:tags) { build_tag_list(label: 'custom', size: 50) }
      let(:config) { { test: { script: 'ls', tags: tags } } }

      it 'creates a pipeline without builds', :aggregate_failures do
        expect(pipeline).not_to be_created_successfully
        expect(pipeline.builds).to be_empty
        expect(pipeline.yaml_errors).to eq("jobs:test:tags config must be less than the limit of #{Gitlab::Ci::Config::Entry::Tags::TAGS_LIMIT} tags")
      end
    end

    context 'tags persistence' do
      let(:config) do
        {
          build:  {
            script: 'ls',
            stage: 'build',
            tags: build_tag_list(label: 'build')
          },
          test: {
            script: 'ls',
            stage: 'test',
            tags: build_tag_list(label: 'test')
          }
        }
      end

      let(:config_without_tags) do
        config.transform_values { |job| job.except(:tags) }
      end

      context 'with multiple tags' do
        context 'when the tags do not exist' do
          it 'does not execute N+1 queries' do
            stub_yaml_config(config_without_tags)

            # warm up the cached objects so we get a more accurate count
            create_pipeline

            control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
              create_pipeline
            end

            stub_yaml_config(config)

            # 2 select tags.*
            # 1 insert tags
            # 1 insert taggings
            tags_queries_size = 4

            expect { pipeline }
              .not_to exceed_all_query_limit(control)
              .with_threshold(tags_queries_size)

            expect(pipeline).to be_created_successfully
          end
        end

        context 'when the feature flag is disabled' do
          before do
            stub_feature_flags(ci_bulk_insert_tags: false)
          end

          it 'executes N+1s queries' do
            stub_yaml_config(config_without_tags)

            # warm up the cached objects so we get a more accurate count
            create_pipeline

            control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
              create_pipeline
            end

            stub_yaml_config(config)

            expect { pipeline }
              .to exceed_all_query_limit(control)
              .with_threshold(4)

            expect(pipeline).to be_created_successfully
          end
        end

        context 'when tags are already persisted' do
          it 'does not execute N+1 queries' do
            # warm up the cached objects so we get a more accurate count
            # and insert the tags
            create_pipeline

            control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
              create_pipeline
            end

            # 1 select tags.*
            # 1 insert taggings
            tags_queries_size = 2

            expect { pipeline }
              .not_to exceed_all_query_limit(control)
              .with_threshold(tags_queries_size)

            expect(pipeline).to be_created_successfully
          end
        end
      end

      context 'with bridge jobs' do
        let(:config) do
          {
            test_1: {
              script: 'ls',
              stage: 'test',
              tags: build_tag_list(label: 'test_1')
            },
            test_2: {
              script: 'ls',
              stage: 'test',
              tags: build_tag_list(label: '$CI_JOB_NAME')
            },
            test_3: {
              script: 'ls',
              stage: 'test',
              tags: build_tag_list(label: 'test_1') + build_tag_list(label: 'test_2')
            },
            test_4: {
              script: 'ls',
              stage: 'test'
            },
            deploy: {
              stage: 'deploy',
              trigger: 'my/project'
            }
          }
        end

        it do
          expect(pipeline).to be_created_successfully
          expect(pipeline.bridges.size).to eq(1)
          expect(pipeline.builds.size).to eq(4)

          expect(tags_for('test_1'))
            .to have_attributes(count: 5)
            .and all(match(/test_1-tag-\d+/))

          expect(tags_for('test_2'))
            .to have_attributes(count: 5)
            .and all(match(/test_2-tag-\d+/))

          expect(tags_for('test_3'))
            .to have_attributes(count: 10)
            .and all(match(/test_[1,2]-tag-\d+/))

          expect(tags_for('test_4')).to be_empty
        end
      end
    end
  end

  def tags_for(build_name)
    pipeline.builds.find_by_name(build_name).tag_list
  end

  def stub_yaml_config(config)
    stub_ci_pipeline_yaml_file(YAML.dump(config))
  end

  def create_pipeline
    service.execute(:push).payload
  end

  def build_tag_list(label:, size: 5)
    Array.new(size) { |index| "#{label}-tag-#{index}" }
  end
end
