# frozen_string_literal: true

require 'spec_helper'

describe 'GET /-/security/vulnerability_findings' do
  it_behaves_like 'security dashboard JSON endpoint' do
    let(:security_dashboard_request) do
      get security_vulnerability_findings_path, headers: { 'ACCEPT' => 'application/json' }
    end
  end

  context 'when the current user is authenticated' do
    let(:findings_request_params) { {} }
    let(:headers) { { 'ACCEPT' => 'application/json' } }
    let(:pipeline) { create(:ci_pipeline, :success, project: project) }
    let(:project) { create(:project) }
    let(:user) { create(:user) }

    before do
      project.add_developer(user)
      user.security_dashboard_projects << project

      login_as(user)
      stub_licensed_features(security_dashboard: true)
    end

    subject { get security_vulnerability_findings_path, headers: headers, params: findings_request_params }

    it "returns an ordered list of vulnerability findings for the user's security dashboard projects" do
      critical_vulnerability = create(
        :vulnerabilities_occurrence,
        pipelines: [pipeline],
        project: project,
        severity: :critical
      )
      create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, severity: :high)

      subject

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response.length).to eq 2
      expect(json_response.first['id']).to be(critical_vulnerability.id)
      expect(response).to match_response_schema('vulnerabilities/occurrence_list', dir: 'ee')
    end

    context 'when a specific page is requested' do
      let(:findings_request_params) { { page: 2 } }

      before do
        Vulnerabilities::Occurrence.paginates_per 2

        create_list(:vulnerabilities_occurrence, 3, pipelines: [pipeline], project: project)

        subject
      end

      after do
        Vulnerabilities::Occurrence.paginates_per Vulnerabilities::Occurrence::OCCURRENCES_PER_PAGE
      end

      it 'returns the list of vulnerability findings that are on the requested page' do
        expect(json_response.length).to eq 1
      end
    end

    context 'when the vulnerability findings have feedback' do
      before do
        vulnerability = create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, report_type: :sast)
        create(:vulnerability_feedback,
               :sast,
               :issue,
               pipeline: pipeline,
               issue: create(:issue, project: project),
               project: project,
               project_fingerprint: vulnerability.project_fingerprint)
      end

      it 'avoids N+1 queries', :request_store do
        control_count = ActiveRecord::QueryRecorder.new { subject }

        vulnerability = create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, report_type: :sast)
        create(:vulnerability_feedback,
               :sast,
               :issue,
               pipeline: pipeline,
               issue: create(:issue, project: project),
               project: project,
               project_fingerprint: vulnerability.project_fingerprint)

        expect { subject }.not_to exceed_all_query_limit(control_count)
      end
    end

    context 'when a project_id param is given' do
      let(:another_pipeline) { create(:ci_pipeline, :success, project: another_project) }
      let(:another_project) { create(:project) }
      let(:findings_request_params) { { project_id: [another_project.id] } }

      before do
        another_project.add_developer(user)
        user.security_dashboard_projects << another_project
      end

      it 'only returns findings for the given projects' do
        another_vulnerability = create(
          :vulnerabilities_occurrence,
          pipelines: [another_pipeline],
          project: another_project,
          severity: :high
        )

        subject

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response.length).to be(1)
        expect(json_response.first['id']).to be(another_vulnerability.id)
      end
    end

    context 'with multiple report types' do
      before do
        create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, report_type: :sast)
        create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, report_type: :dast)
        create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project, report_type: :dependency_scanning)

        subject
      end

      context 'with a single report filter' do
        let(:findings_request_params) { { report_type: ['sast'] } }

        it 'returns a list of vulnerability findings for that report type only' do
          expect(json_response.length).to eq 1
          expect(json_response.map { |v| v['report_type'] }.uniq).to contain_exactly('sast')
        end
      end

      context 'with multiple report filters' do
        let(:findings_request_params) { { report_type: %w[sast dependency_scanning] } }

        it 'returns a list of vulnerability findings for all filtered upon types' do
          expect(json_response.length).to eq 2
          expect(json_response.map { |v| v['report_type'] }.uniq).to contain_exactly('sast', 'dependency_scanning')
        end
      end
    end
  end
end

describe 'GET /-/security/vulnerability_findings/summary' do
  it_behaves_like 'security dashboard JSON endpoint' do
    let(:security_dashboard_request) do
      get summary_security_vulnerability_findings_path, headers: { 'ACCEPT' => 'application/json' }
    end
  end

  context 'when the current user is authenticated' do
    let(:findings_request_params) { {} }
    let(:headers) { { 'ACCEPT' => 'application/json' } }
    let(:pipeline) { create(:ci_pipeline, :success, project: project) }
    let(:user) { create(:user) }
    let(:project) { create(:project) }

    before do
      project.add_developer(user)
      user.security_dashboard_projects << project

      login_as(user)
      stub_licensed_features(security_dashboard: true)

      create_list(:vulnerabilities_occurrence, 3,
                  pipelines: [pipeline], project: project, report_type: :sast, severity: :high)
      create_list(:vulnerabilities_occurrence, 2,
                  pipelines: [pipeline], project: project, report_type: :dependency_scanning, severity: :low)
      create_list(:vulnerabilities_occurrence, 1,
                  pipelines: [pipeline], project: project, report_type: :dast, severity: :medium)
      create_list(:vulnerabilities_occurrence, 1,
                  pipelines: [pipeline], project: project, report_type: :sast, severity: :medium)
    end

    subject { get summary_security_vulnerability_findings_path, headers: headers, params: findings_request_params }

    it 'returns vulnerability findings counts for all report types' do
      subject

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['high']).to eq(3)
      expect(json_response['low']).to eq(2)
      expect(json_response['medium']).to eq(2)
      expect(response).to match_response_schema('vulnerabilities/summary', dir: 'ee')
    end

    context 'when a project_id param is given' do
      let(:another_pipeline) { create(:ci_pipeline, :success, project: another_project) }
      let(:another_project) { create(:project) }
      let(:findings_request_params) { { project_id: [another_project.id] } }

      before do
        another_project.add_developer(user)
        user.security_dashboard_projects << another_project
      end

      it 'only returns findings counts for the given projects' do
        create(
          :vulnerabilities_occurrence,
          pipelines: [another_pipeline],
          project: another_project,
          severity: :high
        )

        subject

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['high']).to be(1)
      end
    end

    context 'with enabled filters' do
      let(:findings_request_params) { { report_type: %w[sast dast], severity: %w[high low] } }

      it 'returns counts for filtered vulnerability findings' do
        subject

        expect(json_response['high']).to eq(3)
        expect(json_response['medium']).to eq(0)
        expect(json_response['low']).to eq(0)
      end
    end
  end
end

describe 'GET /-/security/vulnerability_findings/history' do
  it_behaves_like 'security dashboard JSON endpoint' do
    let(:security_dashboard_request) do
      get(
        history_security_vulnerability_findings_path,
        headers: { 'ACCEPT' => 'application/json' }
      )
    end
  end

  context 'when the current user is authenticated' do
    let(:findings_request_params) { {} }
    let(:headers) { { 'ACCEPT' => 'application/json' } }
    let(:pipeline) { create(:ci_pipeline, :success, project: project) }
    let(:project) { create(:project) }
    let(:user) { create(:user) }

    before do
      project.add_developer(user)
      user.security_dashboard_projects << project

      login_as(user)
      stub_licensed_features(security_dashboard: true)

      travel_to(Time.zone.parse('2018-11-10')) do
        create(:vulnerabilities_occurrence,
               pipelines: [pipeline],
               project: project,
               report_type: :sast,
               severity: :critical)

        create(:vulnerabilities_occurrence,
               pipelines: [pipeline],
               project: project,
               report_type: :dependency_scanning,
               severity: :low)
      end

      travel_to(Time.zone.parse('2018-11-12')) do
        create(:vulnerabilities_occurrence,
               pipelines: [pipeline],
               project: project,
               report_type: :sast,
               severity: :critical)

        create(:vulnerabilities_occurrence,
               pipelines: [pipeline],
               project: project,
               report_type: :dependency_scanning,
               severity: :low)
      end
    end

    subject { get history_security_vulnerability_findings_path, headers: headers, params: findings_request_params }

    it 'returns vulnerability history within last 90 days' do
      travel_to(Time.zone.parse('2019-02-11')) do
        subject
      end

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['total']).to eq({ '2018-11-12' => 2 })
      expect(json_response['critical']).to eq({ '2018-11-12' => 1 })
      expect(json_response['low']).to eq({ '2018-11-12' => 1 })
      expect(response).to match_response_schema('vulnerabilities/history', dir: 'ee')
    end

    it 'returns empty history if there are no vulnerabilities within last 90 days' do
      travel_to(Time.zone.parse('2019-02-13')) do
        subject
      end

      expect(json_response).to eq({
        "undefined" => {},
        "info" => {},
        "unknown" => {},
        "low" => {},
        "medium" => {},
        "high" => {},
        "critical" => {},
        "total" => {}
      })
    end

    context 'when a project_id param is given' do
      let(:another_pipeline) { create(:ci_pipeline, :success, project: another_project) }
      let(:another_project) { create(:project) }
      let(:findings_request_params) { { project_id: [another_project.id] } }

      before do
        another_project.add_developer(user)
        user.security_dashboard_projects << another_project
      end

      it 'only returns findings counts for the given projects' do
        travel_to(Time.zone.parse('2018-11-12')) do
          create(
            :vulnerabilities_occurrence,
            pipelines: [another_pipeline],
            project: another_project,
            severity: :high
          )
        end

        travel_to(Time.zone.parse('2019-02-11')) do
          subject
        end

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['total']).to eq({ '2018-11-12' => 1 })
        expect(json_response['high']).to eq({ '2018-11-12' => 1 })
      end
    end

    context 'with a report type filter' do
      let(:findings_request_params) { { report_type: %w[sast] } }

      before do
        travel_to(Time.zone.parse('2019-02-11')) do
          subject
        end
      end

      it 'returns filtered history if filters are enabled' do
        expect(json_response['total']).to eq({ '2018-11-12' => 1 })
        expect(json_response['critical']).to eq({ '2018-11-12' => 1 })
        expect(json_response['low']).to eq({})
      end
    end
  end
end
