# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::BackgroundMigration::MigrateEvidencesForVulnerabilityFindings, migration: :gitlab_sec,
  feature_category: :vulnerability_management do
  let(:vulnerability_occurrences) { table(:vulnerability_occurrences) }
  let(:vulnerability_finding_evidences) { table(:vulnerability_finding_evidences) }
  let(:evidence_hash) { { url: 'http://test.com' } }
  let(:project_id) { 1 }
  let(:user_id) { 1 }

  let(:scanner1) do
    table(:vulnerability_scanners).create!(project_id: project_id, external_id: 'test 1', name: 'test scanner 1')
  end

  let(:stating_id) { vulnerability_occurrences.pluck(:id).min }
  let(:end_id) { vulnerability_occurrences.pluck(:id).max }

  let(:migration) do
    described_class.new(
      start_id: stating_id,
      end_id: end_id,
      batch_table: :vulnerability_occurrences,
      batch_column: :id,
      sub_batch_size: 2,
      pause_ms: 2,
      connection: SecApplicationRecord.connection
    )
  end

  subject(:perform_migration) { migration.perform }

  context 'without the presence of evidence key' do
    before do
      create_finding!(project_id, scanner1.id, { other_keys: 'test' })
    end

    it 'does not create any evidence' do
      expect { perform_migration }.not_to change { vulnerability_finding_evidences.count }
    end
  end

  context 'with evidence equals to nil' do
    before do
      create_finding!(project_id, scanner1.id, { evidence: nil })
    end

    it 'does not create any evidence' do
      expect { perform_migration }.not_to change { vulnerability_finding_evidences.count }
    end
  end

  context 'with existing evidence within raw_metadata' do
    let!(:finding1) { create_finding!(project_id, scanner1.id, { evidence: evidence_hash }) }
    let!(:finding2) { create_finding!(project_id, scanner1.id, { evidence: evidence_hash }) }

    it 'creates new evidence for each finding' do
      expect { perform_migration }.to change { vulnerability_finding_evidences.count }.by(2)
    end

    context 'when parse throws exception JSON::ParserError' do
      before do
        allow(Gitlab::Json).to receive(:parse).and_raise(JSON::ParserError)
      end

      it 'does not create new records' do
        expect { perform_migration }.not_to change { vulnerability_finding_evidences.count }
      end
    end
  end

  context 'with unsupported Unicode escape sequence' do
    let!(:finding1) { create_finding!(project_id, scanner1.id, { evidence: { 'summary' => "\u0000" } }) }

    it 'does not create new evidence' do
      expect { perform_migration }.not_to change { vulnerability_finding_evidences.count }
    end
  end

  context 'with existing evidence records' do
    let!(:finding) { create_finding!(project_id, scanner1.id, { evidence: evidence_hash }) }

    before do
      vulnerability_finding_evidences.create!(vulnerability_occurrence_id: finding.id, data: evidence_hash)
    end

    it 'does not create new evidence' do
      expect { perform_migration }.not_to change { vulnerability_finding_evidences.count }
    end

    context 'with non-existing evidence' do
      let!(:finding3) { create_finding!(project_id, scanner1.id, { evidence: { url: 'http://secondary.com' } }) }

      it 'creates a new evidence only to the non-existing evidence' do
        expect { perform_migration }.to change { vulnerability_finding_evidences.count }.by(1)
      end
    end
  end

  private

  def create_finding!(project_id, scanner_id, raw_metadata)
    identifier = table(:vulnerability_identifiers).create!(project_id: project_id, external_type: 'uuid-v5',
      external_id: 'uuid-v5', fingerprint: OpenSSL::Digest::SHA256.hexdigest(SecureRandom.uuid),
      name: 'Identifier for UUIDv5 2 2')
    finding = table(:vulnerability_occurrences).create!(
      project_id: project_id, scanner_id: scanner_id,
      primary_identifier_id: identifier.id, name: 'test', severity: 4, report_type: 0,
      uuid: SecureRandom.uuid, location: { "image" => "alpine:3.4" },
      location_fingerprint: 'test', metadata_version: 'test',
      raw_metadata: raw_metadata.to_json)
    vulnerability = table(:vulnerabilities).create!(project_id: project_id, author_id: user_id, title: 'test',
      severity: 4, report_type: 0, finding_id: finding.id)
    finding.update!(vulnerability_id: vulnerability.id)
    finding
  end
end
