# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::BackgroundMigration::PopulateVulnerabilityFeedbackPipelineId, schema: 2020_10_26_182253 do
  let(:users) { table(:users) }
  let(:namespaces) { table(:namespaces) }
  let(:vulnerability_feedback) { table(:vulnerability_feedback) }
  let(:pipelines) { table(:ci_pipelines) }
  let(:builds) { table(:ci_builds) }
  let(:job_artifacts) { table(:ci_job_artifacts) }
  let(:projects) { table(:projects) }
  let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
  let(:user) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 5) }
  let(:timestamp) { Date.current }

  def create_pipeline(project_id, finished_at:, status: 'success', with_security_reports: false, with_legacy_security_report: false)
    pipeline_params = { project_id: project_id, ref: '1', sha: '1', source: 6, status: status, finished_at: finished_at }
    pipeline = pipelines.create!(pipeline_params)

    if with_security_reports
      build = builds.create!(project_id: project_id, name: 'brakeman', retried: false, commit_id: pipeline.id, type: 'Ci::Build')
      job_artifacts.create!(project_id: project_id, file_format: 'raw', file_type: 5, job_id: build.id)
    elsif with_legacy_security_report
      build = builds.create!(project_id: project_id, name: 'sast', retried: false, commit_id: pipeline.id, type: 'Ci::Build')
      job_artifacts.create!(project_id: project_id, file_format: 'raw', file_type: 101, job_id: build.id)
    end

    pipeline
  end

  def create_project_with_pipeline
    project_params = { namespace_id: namespace.id, name: 'foo' }
    project = projects.create!(project_params)

    pipeline = create_pipeline(project.id, finished_at: timestamp - 31.days)

    feedback_params = { project_id: project.id, author_id: user.id, feedback_type: 'dismissal', category: 'sast' }
    vulnerability_feedback.create!(feedback_params.merge(project_fingerprint: SecureRandom.hex, created_at: timestamp - 30.days, pipeline_id: pipeline.id))
    vulnerability_feedback.create!(feedback_params.merge(project_fingerprint: SecureRandom.hex, created_at: timestamp - 15.days))
    vulnerability_feedback.create!(feedback_params.merge(project_fingerprint: SecureRandom.hex, created_at: timestamp - 5.days))

    { project: project, pipeline: pipeline }
  end

  let(:project_with_pipeline_1) { create_project_with_pipeline }
  let(:project_with_pipeline_2) { create_project_with_pipeline }

  let(:project_1) { project_with_pipeline_1[:project] }
  let(:project_2) { project_with_pipeline_2[:project] }

  let(:pipeline_1) { project_with_pipeline_1[:pipeline] }
  let(:pipeline_2) { project_with_pipeline_2[:pipeline] }

  describe '#perform' do
    context 'when there is a succesful pipeline' do
      context 'but the pipeline has no security reports' do
        it 'does not update pipeline_id' do
          subject.perform([project_1.id, project_2.id].sort)

          updated_rows = [
            { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id, 'created_at' => timestamp - 30.days },
            { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
            { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days },
            { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id, 'created_at' => timestamp - 30.days },
            { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
            { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days }
          ]

          rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
            row.attributes.slice(*%w(project_id pipeline_id created_at))
          end

          expect(rows).to match_array(updated_rows)
        end
      end

      context 'and the pipeline has security reports' do
        context 'and is not successful' do
          let!(:pipeline_3) do
            create_pipeline(project_1.id, status: 'failed', finished_at: timestamp - 10.days, with_security_reports: true, with_legacy_security_report: false)
          end

          it 'does not update pipeline_id' do
            subject.perform([project_1.id, project_2.id].sort)

            updated_rows = [
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days }
            ]

            rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
              row.attributes.slice(*%w(project_id pipeline_id created_at))
            end

            expect(rows).to match_array(updated_rows)
          end
        end

        context 'and is successful' do
          let!(:pipeline_3) do
            create_pipeline(project_1.id, finished_at: timestamp - 10.days, with_security_reports: true, with_legacy_security_report: false)
          end

          it 'does update pipeline_id for feedback' do
            subject.perform([project_1.id, project_2.id].sort)

            updated_rows = [
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id,   'created_at' => timestamp - 30.days },
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_3.id,   'created_at' => timestamp - 15.days },
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_3.id,   'created_at' => timestamp -  5.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id,   'created_at' => timestamp - 30.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,             'created_at' => timestamp - 15.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,             'created_at' => timestamp -  5.days }
            ]

            rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
              row.attributes.slice(*%w(project_id pipeline_id created_at))
            end

            expect(rows).to match_array(updated_rows)
          end

          context 'and the pipeline has also legacy security reports' do
            let(:pipeline_4) do
              create_pipeline(project_1.id, finished_at: timestamp - 5.days, with_security_reports: false, with_legacy_security_report: true)
            end

            it 'does update pipeline_id from for feedback using non-legacy pipeline_id' do
              subject.perform([project_1.id, project_2.id].sort)

              updated_rows = [
                { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id,   'created_at' => timestamp - 30.days },
                { 'project_id' => project_1.id, 'pipeline_id' => pipeline_3.id,   'created_at' => timestamp - 15.days },
                { 'project_id' => project_1.id, 'pipeline_id' => pipeline_3.id,   'created_at' => timestamp -  5.days },
                { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id,   'created_at' => timestamp - 30.days },
                { 'project_id' => project_2.id, 'pipeline_id' => nil,             'created_at' => timestamp - 15.days },
                { 'project_id' => project_2.id, 'pipeline_id' => nil,             'created_at' => timestamp -  5.days }
              ]

              rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
                row.attributes.slice(*%w(project_id pipeline_id created_at))
              end

              expect(rows).to match_array(updated_rows)
            end
          end
        end
      end

      context 'and the pipeline has legacy security reports' do
        context 'and is not successful' do
          let!(:pipeline_4) do
            create_pipeline(project_1.id, status: 'failed', finished_at: timestamp - 10.days, with_security_reports: false, with_legacy_security_report: true)
          end

          it 'does not update pipeline_id' do
            subject.perform([project_1.id, project_2.id].sort)

            updated_rows = [
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
              { 'project_id' => project_2.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days }
            ]

            rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
              row.attributes.slice(*%w(project_id pipeline_id created_at))
            end

            expect(rows).to match_array(updated_rows)
          end
        end

        context 'and is successful' do
          let!(:pipeline_4) do
            create_pipeline(project_2.id, finished_at: timestamp - 10.days, with_security_reports: false, with_legacy_security_report: true)
          end

          it 'does update pipeline_id for feedback' do
            subject.perform([project_1.id, project_2.id].sort)

            updated_rows = [
              { 'project_id' => project_1.id, 'pipeline_id' => pipeline_1.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp - 15.days },
              { 'project_id' => project_1.id, 'pipeline_id' => nil,           'created_at' => timestamp -  5.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_2.id, 'created_at' => timestamp - 30.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_4.id, 'created_at' => timestamp - 15.days },
              { 'project_id' => project_2.id, 'pipeline_id' => pipeline_4.id, 'created_at' => timestamp -  5.days }
            ]

            rows = vulnerability_feedback.order(:project_id, :created_at).map do |row|
              row.attributes.slice(*%w(project_id pipeline_id created_at))
            end

            expect(rows).to match_array(updated_rows)
          end
        end
      end
    end
  end
end
