# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::PatchRelease::SecurityIssue do
  let(:crawler) do
    instance_spy(
      ReleaseTools::Security::IssueCrawler,
      related_security_issues: [create(:issue)]
    )
  end

  let(:versions) do
    [
      ReleaseTools::Version.new('12.7.4'),
      ReleaseTools::Version.new('12.6.6'),
      ReleaseTools::Version.new('12.5.9')
    ]
  end

  let(:helm_version_finder) { instance_double(ReleaseTools::Helm::HelmVersionFinder) }

  before do
    coordinator = instance_double(
      ReleaseTools::PatchRelease::Coordinator,
      versions: versions
    )

    allow(ReleaseTools::PatchRelease::Coordinator)
      .to receive(:new)
      .and_return(coordinator)

    allow(ReleaseTools::Security::IssueCrawler)
      .to receive(:new)
      .and_return(crawler)

    allow(ReleaseTools::Helm::HelmVersionFinder)
      .to receive(:new)
      .and_return(helm_version_finder)

    allow(helm_version_finder).to receive(:execute).with('12.7.4').and_return('1.3.4')
    allow(helm_version_finder).to receive(:execute).with('12.6.6').and_return('1.4.5')
    allow(helm_version_finder).to receive(:execute).with('12.5.9').and_return('1.5.6')
  end

  subject(:issue) { described_class.new }

  it_behaves_like 'issuable #initialize'

  describe '#title' do
    context 'with a regular patch release' do
      it 'includes all the versions' do
        expect(issue.title).to eq('Patch release: 12.7.4, 12.6.6, 12.5.9')
      end
    end

    context 'with a critical patch release' do
      it 'includes the critical part' do
        allow(ReleaseTools::SharedStatus)
          .to receive(:critical_patch_release?)
          .and_return(true)

        expect(issue.title).to eq('Critical patch release: 12.7.4, 12.6.6, 12.5.9')
      end
    end
  end

  describe '#confidential?' do
    it 'is always confidential' do
      expect(issue).to be_confidential
    end
  end

  describe '#labels' do
    it 'includes the "security" label' do
      expect(issue.labels).to eq 'Monthly Release,security'
    end
  end

  describe '#description' do
    let(:tracking_issue) { build(:issue, web_url: 'http://example.com') }
    let(:security_release_pipeline) { build(:issue, web_url: 'http://example.com') }

    before do
      allow(issue).to receive_messages(security_release_tracking_issue: tracking_issue, release_pipeline: security_release_pipeline)
    end

    it 'includes a step to perform a patch release' do
      content = issue.description

      expect(content).to include 'Start the `security_release_release_preparation:start` job of the security pipeline'
    end

    it 'includes a step to publish the packages' do
      content = issue.description

      expect(content).to include 'Start the `security_release_publish:start` job of the security pipeline'
    end

    it 'includes the correct instance for packages' do
      content = issue.description

      expect(content).to include 'release.gitlab.net'
    end

    it 'returns Helm versions' do
      content = issue.description

      expect(content).to include 'https://dev.gitlab.org/gitlab/charts/gitlab/-/pipelines/?ref=v1.3.4'
      expect(content).to include 'https://dev.gitlab.org/gitlab/charts/gitlab/-/pipelines/?ref=v1.4.5'
      expect(content).to include 'https://dev.gitlab.org/gitlab/charts/gitlab/-/pipelines/?ref=v1.5.6'
    end
  end

  describe '#version' do
    it 'returns the highest version' do
      expect(issue.version).to eq('12.7.4')
    end

    context 'with unsorted versions' do
      let(:versions) do
        [
          ReleaseTools::Version.new('12.6.6'),
          ReleaseTools::Version.new('12.7.4'),
          ReleaseTools::Version.new('12.5.9')
        ]
      end

      it 'returns the highest version' do
        expect(issue.version).to eq('12.7.4')
      end
    end
  end

  describe '#critical?' do
    context 'with a regular patch release' do
      it { is_expected.not_to be_critical }
    end

    context 'with a critical patch release' do
      before do
        allow(ReleaseTools::SharedStatus)
          .to receive(:critical_patch_release?)
          .and_return(true)
      end

      it { is_expected.to be_critical }
    end
  end

  describe '#regular?' do
    context 'with a regular patch release' do
      it { is_expected.to be_regular }
    end

    context 'with a critical patch release' do
      before do
        allow(ReleaseTools::SharedStatus)
          .to receive(:critical_patch_release?)
          .and_return(true)
      end

      it { is_expected.not_to be_regular }
    end
  end

  describe '#create' do
    it 'creates a security pipeline' do
      expect(issue).to receive(:release_pipeline)
      expect(ReleaseTools::GitlabClient).to receive(:create_issue)
        .with(issue, issue.project).and_return(true)

      issue.create
    end
  end

  describe '#release_pipeline' do
    let(:web_url) { "https://gitlab.example.com/pipelines/123" }
    let(:dry_run_url) { "https://example.com/foo/bar/-/pipelines/1" }

    it "creates a security pipeline on the ops instance" do
      expect(ReleaseTools::GitlabOpsClient).to receive(:create_pipeline).with(
        ReleaseTools::Project::ReleaseTools,
        {
          SECURITY_RELEASE_PIPELINE: 'true',
          SECURITY: nil
        }
      ).and_return(double(web_url: web_url))

      without_dry_run do
        expect(issue.release_pipeline.web_url).to eq(web_url)
      end
    end

    context 'critical patch release' do
      it "creates a patch release injecting the SECURITY variable" do
        expect(ReleaseTools::GitlabOpsClient).to receive(:create_pipeline).with(
          ReleaseTools::Project::ReleaseTools,
          {
            SECURITY_RELEASE_PIPELINE: 'true',
            SECURITY: 'critical'
          }
        ).and_return(double(web_url: web_url))

        without_dry_run do
          ClimateControl.modify('SECURITY' => 'critical') do
            expect(issue.release_pipeline.web_url).to eq(web_url)
          end
        end
        expect(issue.release_pipeline.web_url).to eq(web_url)
      end
    end

    context 'in dry run mode' do
      it 'imitates the create_pipeline API response' do
        expect(ReleaseTools::GitlabOpsClient).not_to receive(:create_pipeline)

        expect(issue.release_pipeline.web_url).to eq(dry_run_url)
      end
    end
  end

  describe '#unsupported_projects_list' do
    it 'returns a comma separated string list of the managed versioning projects' do
      expect(issue.unsupported_projects_list).to eq('cng-ee, gitaly, gitlab-pages')
    end
  end

  describe '#security_release_tracking_issue' do
    it 'returns the patch release tracking issue' do
      tracking_issue = build(:issue)
      expect(ReleaseTools::GitlabClient).to receive(:next_security_tracking_issue)
        .and_return(tracking_issue)

      expect(issue.security_release_tracking_issue).to eq(tracking_issue)
    end
  end

  describe '#version_type' do
    it 'returns the patch release type' do
      expect(issue.version_type).to eq('Non Critical')
    end

    context 'on a critical patch release' do
      it 'returns the associated patch release type' do
        allow(ReleaseTools::SharedStatus)
          .to receive(:critical_patch_release?)
          .and_return(true)

        expect(issue.version_type).to eq('Critical')
      end
    end
  end

  describe '#due_date' do
    it 'returns the same date as the tracking issue' do
      tracking_issue = build(:issue, due_date: '2024-04-01')

      allow(ReleaseTools::GitlabClient)
        .to receive(:next_security_tracking_issue)
        .and_return(tracking_issue)

      expect(issue.due_date).to eq('2024-04-01')
    end
  end
end
