# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::Security::IssueCrawler do
  let(:tracking_issue1) do
    create(
      :issue,
      project_id: 1,
      iid: 1,
      due_date: '2020-01-04',
      state: described_class::OPENED,
      web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/issues/1'
    )
  end

  let(:issue1) do
    create(
      :issue,
      project_id: 1,
      iid: 2,
      due_date: '2020-01-01',
      labels: [],
      state: described_class::OPENED,
      web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/issues/2'
    )
  end

  let(:issue2) do
    create(
      :issue,
      project_id: 1,
      iid: 3,
      due_date: nil,
      labels: [],
      state: described_class::OPENED,
      web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/issues/2'
    )
  end

  let(:issue3) do
    create(
      :issue,
      project_id: 2,
      iid: 1,
      state: described_class::OPENED,
      web_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/1'
    )
  end

  let(:page) { Gitlab::PaginatedResponse.new([tracking_issue1, issue1, issue2]) }

  subject(:crawler) { described_class.new }

  describe '#upcoming_security_issues_and_merge_requests' do
    context 'when there are no security issues' do
      it 'returns an empty Array' do
        allow(ReleaseTools::GitlabClient).to receive(:next_security_tracking_issue).and_return(nil)

        expect(crawler.upcoming_security_issues_and_merge_requests).to eq([])
      end
    end

    context 'when there are security issues' do
      it 'returns all security issues and merge requests for the most recent issue' do
        allow(ReleaseTools::GitlabClient)
          .to receive(:next_security_tracking_issue)
          .and_return(issue3)

        expect(crawler)
          .to receive(:related_security_issues)
          .and_return([tracking_issue1, issue1])

        crawler.upcoming_security_issues_and_merge_requests
      end
    end
  end

  describe '#related_security_issues' do
    it 'returns issues related to the given release issue iid' do
      implementation_issue1 = create(:security_implementation_issue, issue: tracking_issue1)

      allow(crawler).to receive(:security_tracking_issue).and_return(tracking_issue1)

      expect(crawler).to receive(:security_issues_for).with(tracking_issue1.iid).and_return([tracking_issue1])
      expect(crawler).to receive(:security_issues_and_merge_requests_for).with([tracking_issue1])
        .and_return([implementation_issue1])

      expect(crawler.related_security_issues).to eq([implementation_issue1])
    end
  end

  describe '#security_issues_for' do
    it 'returns only the security issues related to the tracking release issue' do
      page = Gitlab::PaginatedResponse.new([issue1, issue2, issue3])

      allow(ReleaseTools::GitlabClient.client)
        .to receive(:issue_links)
        .with(described_class::PUBLIC_PROJECT, tracking_issue1.iid)
        .and_return(page)

      issues = crawler.security_issues_for(tracking_issue1.iid)

      expect(issues.length).to eq(2)

      issues.each do |issue|
        expect(issue.web_url).to include('gitlab-org/security')
      end
    end

    it 'filters out closed issues' do
      issue4 = create(
        :issue,
        project_id: 1,
        iid: 2,
        state: 'closed',
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/issues/2'
      )

      page = Gitlab::PaginatedResponse.new([issue1, issue2, issue3, issue4])

      allow(ReleaseTools::GitlabClient.client)
        .to receive(:issue_links)
        .with(described_class::PUBLIC_PROJECT, tracking_issue1.iid)
        .and_return(page)

      issues = crawler.security_issues_for(tracking_issue1.iid)

      expect(issues).not_to include(issue4)
    end
  end

  describe '#evaluable_security_issues' do
    let(:issue4) { create(:issue, project_id: 2, iid: 1) }

    let(:project1_issue_page) { Gitlab::PaginatedResponse.new([tracking_issue1, issue1, issue3]) }
    let(:project2_issue_page) { Gitlab::PaginatedResponse.new([issue4]) }

    it 'returns the security issues with the specified label' do
      stub_const('ReleaseTools::ManagedVersioning::PROJECTS', [ReleaseTools::Project::GitlabEe, ReleaseTools::Project::Gitaly, ReleaseTools::Project::GitlabPages])
      implementation_issue1 = create(:security_implementation_issue, issue: tracking_issue1)
      implementation_issue2 = create(:security_implementation_issue, issue: issue1)
      implementation_issue3 = create(:security_implementation_issue, issue: issue3)
      implementation_issue4 = create(:security_implementation_issue, issue: issue4)
      results = [implementation_issue1, implementation_issue2, implementation_issue3, implementation_issue4]

      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(ReleaseTools::Project::GitlabEe.security_path, labels: [described_class::SECURITY_TARGET_LABEL], state: described_class::OPENED)
        .and_return(project1_issue_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(ReleaseTools::Project::Gitaly.security_path, labels: [described_class::SECURITY_TARGET_LABEL], state: described_class::OPENED)
        .and_return(project2_issue_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(ReleaseTools::Project::GitlabPages.security_path, labels: [described_class::SECURITY_TARGET_LABEL], state: described_class::OPENED)
        .and_return(nil)

      expect(crawler)
        .to receive(:security_issues_and_merge_requests_for)
        .with([tracking_issue1, issue1, issue3, issue4]).and_return(results)

      issues = crawler.evaluable_security_issues

      expect(issues).to eq(results)
    end
  end

  describe '#notifiable_security_issues_for' do
    let(:issue1) { build(:issue, project_id: 1) }
    let(:issue2) { build(:issue, project_id: 1) }
    let(:issue3) { build(:issue, project_id: 1) }
    let(:issue4) { build(:issue, project_id: 2) }

    let(:project1_issue_page) { Gitlab::PaginatedResponse.new([issue1, issue2, issue3]) }
    let(:project2_issue_page) { Gitlab::PaginatedResponse.new([issue4]) }

    before do
      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(
          ReleaseTools::Project::GitlabEe.security_path,
          labels: [described_class::SECURITY_NOTIFICATIONS_LABEL],
          state: described_class::OPENED
        )
        .and_return(project1_issue_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(
          ReleaseTools::Project::Gitaly.security_path,
          labels: [described_class::SECURITY_NOTIFICATIONS_LABEL],
          state: described_class::OPENED
        )
        .and_return(project2_issue_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:issues)
        .with(
          ReleaseTools::Project::GitlabPages.security_path,
          labels: [described_class::SECURITY_NOTIFICATIONS_LABEL],
          state: described_class::OPENED
        )
        .and_return([])

      allow(ReleaseTools::GitlabClient)
        .to receive(:related_merge_requests)
        .and_return(Gitlab::PaginatedResponse.new([]))
    end

    it 'returns the security issues with the specified label' do
      gitlab_issues = crawler.notifiable_security_issues_for(ReleaseTools::Project::GitlabEe)
      gitaly_issues = crawler.notifiable_security_issues_for(ReleaseTools::Project::Gitaly)
      gitlab_pages_issues = crawler.notifiable_security_issues_for(ReleaseTools::Project::GitlabPages)

      expect(gitlab_issues.map(&:issue)).to eq([issue1, issue2, issue3])
      expect(gitaly_issues.map(&:issue)).to eq([issue4])
      expect(gitlab_pages_issues).to eq([])
    end
  end

  describe '#security_issues_and_merge_requests_for' do
    it 'returns the security issues and merge requests related to the given issues' do
      mr1 = create(
        :merge_request,
        state: described_class::OPENED,
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1'
      )

      mr2 = create(
        :merge_request,
        state: described_class::OPENED,
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1'
      )

      mr3 = create(
        :merge_request,
        state: described_class::OPENED,
        web_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1'
      )

      issue_page = Gitlab::PaginatedResponse.new([issue1, issue3])
      mr_page = Gitlab::PaginatedResponse.new([mr1, mr2, mr3])

      allow(ReleaseTools::GitlabClient.client)
        .to receive(:issue_links)
        .with(described_class::PUBLIC_PROJECT, tracking_issue1.iid)
        .and_return(issue_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:related_merge_requests)
        .with(1, 1)
        .and_return(mr_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:related_merge_requests)
        .with(1, 2)
        .and_return(mr_page)

      allow(ReleaseTools::GitlabClient)
        .to receive(:related_merge_requests)
        .with(2, 1)
        .and_return(mr_page)

      issues = crawler.security_issues_and_merge_requests_for(issue_page)

      expect(issues.length).to eq(2)
      expect(issues[0].project_id).to eq(1)
      expect(issues[0].iid).to eq(issue1.iid)
      expect(issues[0].merge_requests).to eq([mr1, mr2])
    end

    it 'filters out closed merge requests' do
      mr1 = create(
        :merge_request,
        state: described_class::OPENED,
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1'
      )

      mr2 = create(
        :merge_request,
        state: described_class::CLOSED,
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/2'
      )

      mr3 = create(
        :merge_request,
        state: 'merged',
        web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3'
      )

      issue_page = Gitlab::PaginatedResponse.new([tracking_issue1])
      mr_page = Gitlab::PaginatedResponse.new([mr1, mr2, mr3])

      allow(ReleaseTools::GitlabClient)
        .to receive(:related_merge_requests)
        .with(tracking_issue1.project_id, tracking_issue1.iid)
        .and_return(mr_page)

      issues = crawler.security_issues_and_merge_requests_for(issue_page)

      expect(issues[0].merge_requests.length).to eq(2)
      expect(issues[0].merge_requests).to contain_exactly(mr1, mr3)
      expect(issues[0].merge_requests).not_to include(mr2)
    end
  end
end
