diff options
Diffstat (limited to 'spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb')
-rw-r--r-- | spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb new file mode 100644 index 00000000000..44e66fd9028 --- /dev/null +++ b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do + let(:identifier) { build(:ci_reports_security_identifier) } + + let_it_be(:project) { create(:project, :repository) } + + let(:location_param) { build(:ci_reports_security_locations_sast, :dynamic) } + let(:vulnerability_params) { vuln_params(project.id, [identifier], confidence: :low, severity: :critical) } + let(:base_vulnerability) { build(:ci_reports_security_finding, location: location_param, **vulnerability_params) } + let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability]) } + + let(:head_vulnerability) { build(:ci_reports_security_finding, location: location_param, uuid: base_vulnerability.uuid, **vulnerability_params) } + let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability]) } + + shared_context 'comparing reports' do + let(:vul_params) { vuln_params(project.id, [identifier]) } + let(:base_vulnerability) { build(:ci_reports_security_finding, :dynamic, **vul_params) } + let(:head_vulnerability) { build(:ci_reports_security_finding, :dynamic, **vul_params) } + let(:head_vul_findings) { [head_vulnerability, vuln] } + end + + subject { described_class.new(project, base_report, head_report) } + + where(vulnerability_finding_signatures: [true, false]) + + with_them do + before do + stub_licensed_features(vulnerability_finding_signatures: vulnerability_finding_signatures) + end + + describe '#base_report_out_of_date' do + context 'no base report' do + let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [], findings: []) } + + it 'is not out of date' do + expect(subject.base_report_out_of_date).to be false + end + end + + context 'base report older than one week' do + let(:report) { build(:ci_reports_security_report, created_at: 1.week.ago - 60.seconds) } + let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [report]) } + + it 'is not out of date' do + expect(subject.base_report_out_of_date).to be true + end + end + + context 'base report less than one week old' do + let(:report) { build(:ci_reports_security_report, created_at: 1.week.ago + 60.seconds) } + let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [report]) } + + it 'is not out of date' do + expect(subject.base_report_out_of_date).to be false + end + end + end + + describe '#added' do + let(:new_location) {build(:ci_reports_security_locations_sast, :dynamic) } + let(:vul_params) { vuln_params(project.id, [identifier], confidence: :high) } + let(:vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:critical], location: new_location, **vul_params) } + let(:low_vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:low], location: new_location, **vul_params) } + + context 'with new vulnerability' do + let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability, vuln]) } + + it 'points to source tree' do + expect(subject.added).to eq([vuln]) + end + end + + context 'when comparing reports with different fingerprints' do + include_context 'comparing reports' + + let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: head_vul_findings) } + + it 'does not find any overlap' do + expect(subject.added).to eq(head_vul_findings) + end + end + + context 'order' do + let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability, vuln, low_vuln]) } + + it 'does not change' do + expect(subject.added).to eq([vuln, low_vuln]) + end + end + end + + describe '#fixed' do + let(:vul_params) { vuln_params(project.id, [identifier]) } + let(:vuln) { build(:ci_reports_security_finding, :dynamic, **vul_params ) } + let(:medium_vuln) { build(:ci_reports_security_finding, confidence: ::Enums::Vulnerability.confidence_levels[:high], severity: Enums::Vulnerability.severity_levels[:medium], uuid: vuln.uuid, **vul_params) } + + context 'with fixed vulnerability' do + let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability, vuln]) } + + it 'points to base tree' do + expect(subject.fixed).to eq([vuln]) + end + end + + context 'when comparing reports with different fingerprints' do + include_context 'comparing reports' + + let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability, vuln]) } + + it 'does not find any overlap' do + expect(subject.fixed).to eq([base_vulnerability, vuln]) + end + end + + context 'order' do + let(:vul_findings) { [vuln, medium_vuln] } + let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [*vul_findings, base_vulnerability]) } + + it 'does not change' do + expect(subject.fixed).to eq(vul_findings) + end + end + end + + describe 'with empty vulnerabilities' do + let(:empty_report) { build(:ci_reports_security_aggregated_reports, reports: [], findings: []) } + + it 'returns empty array when reports are not present' do + comparer = described_class.new(project, empty_report, empty_report) + + expect(comparer.fixed).to eq([]) + expect(comparer.added).to eq([]) + end + + it 'returns added vulnerability when base is empty and head is not empty' do + comparer = described_class.new(project, empty_report, head_report) + + expect(comparer.fixed).to eq([]) + expect(comparer.added).to eq([head_vulnerability]) + end + + it 'returns fixed vulnerability when head is empty and base is not empty' do + comparer = described_class.new(project, base_report, empty_report) + + expect(comparer.fixed).to eq([base_vulnerability]) + expect(comparer.added).to eq([]) + end + end + end + + def vuln_params(project_id, identifiers, confidence: :high, severity: :critical) + { + project_id: project_id, + report_type: :sast, + identifiers: identifiers, + confidence: ::Enums::Vulnerability.confidence_levels[confidence], + severity: ::Enums::Vulnerability.severity_levels[severity] + } + end +end |