Commit 9ab299f4 authored by Robert Speicher's avatar Robert Speicher

Merge branch '255313_optimize_vulnerability_spec' into 'master'

Improve the performance of Vulnerability model specs

See merge request gitlab-org/gitlab!52121
parents fd43d761 0295551f
......@@ -20,12 +20,19 @@ RSpec.describe Vulnerability do
api_fuzzing: 6 }
end
it { is_expected.to define_enum_for(:state).with_values(state_values) }
it { is_expected.to define_enum_for(:severity).with_values(severity_values).with_prefix(:severity) }
it { is_expected.to define_enum_for(:confidence).with_values(confidence_values).with_prefix(:confidence) }
it { is_expected.to define_enum_for(:report_type).with_values(report_types) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:vulnerability) { create(:vulnerability, :sast, :confirmed, :low, project: project) }
let_it_be(:finding) { create(:vulnerabilities_finding, vulnerability: vulnerability) }
it_behaves_like 'having unique enum values'
describe 'enums' do
it { is_expected.to define_enum_for(:state).with_values(state_values) }
it { is_expected.to define_enum_for(:severity).with_values(severity_values).with_prefix(:severity) }
it { is_expected.to define_enum_for(:confidence).with_values(confidence_values).with_prefix(:confidence) }
it { is_expected.to define_enum_for(:report_type).with_values(report_types) }
it_behaves_like 'having unique enum values'
end
describe 'associations' do
subject { build(:vulnerability) }
......@@ -70,66 +77,71 @@ RSpec.describe Vulnerability do
end
describe 'text fields' do
subject { create(:vulnerability, title: '_My title_ ', description: '**Hello `world`**') }
let_it_be(:vulnerability) { create(:vulnerability, title: '_My title_ ', description: '**Hello `world`**') }
describe '#title_html' do
let(:expected_title) { '_My title_' } # no markdown rendering because it's a single line field
subject { vulnerability.title_html }
it 'has proper markdown for title field' do
expect(subject.title_html).to eq('_My title_') # no markdown rendering because it's a single line field
it { is_expected.to eq(expected_title) }
end
it 'has proper markdown for title field' do
expect(subject.description_html).to(
eq('<p data-sourcepos="1:1-1:17" dir="auto"><strong>Hello <code>world</code></strong></p>')
)
describe '#description_html' do
let(:expected_description) { '<p data-sourcepos="1:1-1:17" dir="auto"><strong>Hello <code>world</code></strong></p>' }
subject { vulnerability.description_html }
it { is_expected.to eq(expected_description) }
end
context 'redactable fields' do
describe 'redactable fields' do
before do
stub_commonmark_sourcepos_disabled
end
it_behaves_like 'model with redactable field' do
let(:model) { create(:vulnerability) }
let(:model) { build(:vulnerability) }
let(:field) { :description }
end
end
end
describe '.visible_to_user_and_access_level' do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let!(:vulnerability1) { create(:vulnerability, project: project1) }
let!(:vulnerability2) { create(:vulnerability, project: project2) }
let(:user) { create(:user) }
let(:project_2) { create(:project) }
before do
project1.add_developer(user)
project.add_developer(user)
create(:vulnerability, project: project_2)
end
subject { described_class.visible_to_user_and_access_level(user, ::Gitlab::Access::DEVELOPER) }
it 'returns vulnerabilities visible for given user with provided access level' do
is_expected.to contain_exactly(vulnerability1)
is_expected.to contain_exactly(vulnerability)
end
end
describe '.with_limit' do
let(:project) { create(:project) }
let!(:vulnerabilities) { create_list(:vulnerability, 10, project: project) }
subject(:limited_vulnerabilities) { described_class.with_limit(1) }
subject { described_class.with_limit(5) }
before do
# There is already a vulnerability created before
# so this will make the total of 2 vulnerabilities.
create(:vulnerability, project: project)
end
it 'returns vulnerabilities limited by provided value' do
expect(subject.count).to eq(5)
expect(limited_vulnerabilities.count).to eq(1)
end
end
describe '.autocomplete_search' do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
let!(:vulnerability_1) { create(:vulnerability, title: 'Predictable pseudorandom number generator') }
let!(:vulnerability_2) { create(:vulnerability, title: 'Use of pseudorandom MD2, MD4, or MD5 hash function.') }
let_it_be(:vulnerability_1) { create(:vulnerability, title: 'Predictable pseudorandom number generator') }
let_it_be(:vulnerability_2) { create(:vulnerability, title: 'Use of pseudorandom MD2, MD4, or MD5 hash function.') }
subject { described_class.autocomplete_search(search) }
......@@ -154,27 +166,28 @@ RSpec.describe Vulnerability do
context 'when query is empty' do
let(:search) { '' }
it { is_expected.to match_array([vulnerability_1, vulnerability_2]) }
it { is_expected.to match_array([vulnerability, vulnerability_1, vulnerability_2]) }
end
end
describe '.for_projects' do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let!(:vulnerability1) { create(:vulnerability, project: project1) }
let!(:vulnerability2) { create(:vulnerability, project: project2) }
let(:project_2) { create(:project) }
before do
create(:vulnerability, project: project_2)
end
subject { described_class.for_projects([project1.id]) }
subject { described_class.for_projects([project.id]) }
it 'returns vulnerabilities related to the given project IDs' do
is_expected.to contain_exactly(vulnerability1)
is_expected.to contain_exactly(vulnerability)
end
end
describe '.with_report_types' do
let!(:sast_vulnerability) { create(:vulnerability, :sast) }
let!(:dast_vulnerability) { create(:vulnerability, :dast) }
let!(:dependency_scanning_vulnerability) { create(:vulnerability, :dependency_scanning) }
let(:sast_vulnerability) { vulnerability }
let(:report_types) { %w[sast dast] }
subject { described_class.with_report_types(report_types) }
......@@ -187,7 +200,7 @@ RSpec.describe Vulnerability do
describe '.with_severities' do
let!(:high_vulnerability) { create(:vulnerability, :high) }
let!(:medium_vulnerability) { create(:vulnerability, :medium) }
let!(:low_vulnerability) { create(:vulnerability, :low) }
let(:low_vulnerability) { vulnerability }
let(:severities) { %w[medium low] }
subject { described_class.with_severities(severities) }
......@@ -200,7 +213,7 @@ RSpec.describe Vulnerability do
describe '.with_states' do
let!(:detected_vulnerability) { create(:vulnerability, :detected) }
let!(:dismissed_vulnerability) { create(:vulnerability, :dismissed) }
let!(:confirmed_vulnerability) { create(:vulnerability, :confirmed) }
let(:confirmed_vulnerability) { vulnerability }
let(:states) { %w[detected confirmed] }
subject { described_class.with_states(states) }
......@@ -211,48 +224,46 @@ RSpec.describe Vulnerability do
end
describe '.with_scanners' do
let!(:detected_vulnerability) { create(:vulnerability, :detected, :with_findings) }
let!(:dismissed_vulnerability) { create(:vulnerability, :dismissed, :with_findings) }
let!(:confirmed_vulnerability) { create(:vulnerability, :confirmed, :with_findings) }
let(:scanners) { [detected_vulnerability.finding_scanner_external_id, confirmed_vulnerability.finding_scanner_external_id] }
let!(:vulnerability_1) { create(:vulnerability, :with_findings) }
let!(:vulnerability_2) { create(:vulnerability, :with_findings) }
let(:vulnerability_3) { vulnerability }
let(:scanners) { [vulnerability_1.finding_scanner_external_id, vulnerability_3.finding_scanner_external_id] }
subject { described_class.with_scanners(scanners) }
it 'returns vulnerabilities matching the given scanners' do
is_expected.to contain_exactly(detected_vulnerability, confirmed_vulnerability)
is_expected.to contain_exactly(vulnerability_1, vulnerability_3)
end
end
describe '.order_severity_asc' do
subject { described_class.order_severity_asc }
describe '.order_severity_' do
let_it_be(:low_vulnerability) { vulnerability }
let_it_be(:critical_vulnerability) { create(:vulnerability, :critical) }
let_it_be(:medium_vulnerability) { create(:vulnerability, :medium) }
it 'returns vulnerabilities ordered by severity' do
low_vulnerability = create(:vulnerability, :low)
critical_vulnerability = create(:vulnerability, :critical)
medium_vulnerability = create(:vulnerability, :medium)
describe 'ascending' do
subject { described_class.order_severity_asc }
is_expected.to eq([low_vulnerability, medium_vulnerability, critical_vulnerability])
it { is_expected.to eq([low_vulnerability, medium_vulnerability, critical_vulnerability]) }
end
end
describe '.order_severity_desc' do
subject { described_class.order_severity_desc }
it 'returns vulnerabilities ordered by severity' do
low_vulnerability = create(:vulnerability, :low)
critical_vulnerability = create(:vulnerability, :critical)
medium_vulnerability = create(:vulnerability, :medium)
describe 'descending' do
subject { described_class.order_severity_desc }
is_expected.to eq([critical_vulnerability, medium_vulnerability, low_vulnerability])
it { is_expected.to eq([critical_vulnerability, medium_vulnerability, low_vulnerability]) }
end
end
describe '.order_title_' do
let_it_be(:vulnerability_b) { create(:vulnerability, title: 'B Vulnerability') }
let_it_be(:vulnerability_a) { create(:vulnerability, title: 'A Vulnerability') }
let_it_be(:vulnerability_c) { create(:vulnerability, title: 'C Vulnerability') }
let_it_be(:vulnerability_c) { vulnerability }
before(:all) do
vulnerability.update_column(:title, 'C Vulnerability')
end
describe 'asc' do
describe 'ascending' do
subject { described_class.order_title_asc }
it 'returns vulnerabilities ordered by title' do
......@@ -260,7 +271,7 @@ RSpec.describe Vulnerability do
end
end
describe 'desc' do
describe 'descending' do
subject { described_class.order_title_desc }
it 'returns vulnerabilities ordered by title' do
......@@ -271,10 +282,14 @@ RSpec.describe Vulnerability do
describe '.order_created_at_' do
let_it_be(:old_vulnerability) { create(:vulnerability, created_at: 2.weeks.ago) }
let_it_be(:very_old_vulnerability) { create(:vulnerability, created_at: 1.year.ago) }
let_it_be(:very_old_vulnerability) { vulnerability }
let_it_be(:fresh_vulnerability) { create(:vulnerability, created_at: 3.days.ago) }
describe 'desc' do
before(:all) do
vulnerability.update_column(:created_at, 1.year.ago)
end
describe 'ascending' do
subject { described_class.order_created_at_asc }
it 'returns vulnerabilities ordered by created_at' do
......@@ -282,7 +297,7 @@ RSpec.describe Vulnerability do
end
end
describe 'asc' do
describe 'descending' do
subject { described_class.order_created_at_desc }
it 'returns vulnerabilities ordered by created_at' do
......@@ -295,7 +310,7 @@ RSpec.describe Vulnerability do
subject { described_class.order_id_desc }
before do
create_list(:vulnerability, 10)
create_list(:vulnerability, 2)
end
it 'returns vulnerabilities ordered by id' do
......@@ -306,11 +321,11 @@ RSpec.describe Vulnerability do
describe '.order_report_type_' do
let_it_be(:vulnerability_dast) { create(:vulnerability, :dast) }
let_it_be(:vulnerability_secret_detection) { create(:vulnerability, :secret_detection) }
let_it_be(:vulnerability_sast) { create(:vulnerability, :sast) }
let_it_be(:vulnerability_sast) { vulnerability }
let_it_be(:vulnerability_coverage_fuzzing) { create(:vulnerability, :coverage_fuzzing) }
let_it_be(:vulnerability_api_fuzzing) { create(:vulnerability, :api_fuzzing) }
describe 'asc' do
describe 'ascending' do
subject { described_class.order_report_type_asc }
it 'returns vulnerabilities ordered by report_type' do
......@@ -318,7 +333,7 @@ RSpec.describe Vulnerability do
end
end
describe 'desc' do
describe 'descending' do
subject { described_class.order_report_type_desc }
it 'returns vulnerabilities ordered by report_type' do
......@@ -328,12 +343,12 @@ RSpec.describe Vulnerability do
end
describe '.order_state_' do
let_it_be(:vulnerability_confirmed) { create(:vulnerability, :confirmed) }
let_it_be(:vulnerability_confirmed) { vulnerability }
let_it_be(:vulnerability_detected) { create(:vulnerability, :detected) }
let_it_be(:vulnerability_dismissed) { create(:vulnerability, :dismissed) }
let_it_be(:vulnerability_resolved) { create(:vulnerability, :resolved) }
describe 'asc' do
describe 'ascending' do
subject { described_class.order_state_asc }
it 'returns vulnerabilities ordered by state' do
......@@ -341,7 +356,7 @@ RSpec.describe Vulnerability do
end
end
describe 'desc' do
describe 'descending' do
subject { described_class.order_state_desc }
it 'returns vulnerabilities ordered by state' do
......@@ -352,32 +367,38 @@ RSpec.describe Vulnerability do
describe '.with_resolution' do
let_it_be(:vulnerability_with_resolution) { create(:vulnerability, resolved_on_default_branch: true) }
let_it_be(:vulnerability_without_resolution) { create(:vulnerability, resolved_on_default_branch: false) }
let_it_be(:vulnerability_without_resolution) { vulnerability }
subject { described_class.with_resolution(with_resolution) }
before(:all) do
vulnerability.update!(resolved_on_default_branch: false)
end
context 'when no argument is provided' do
subject { described_class.with_resolution }
it { is_expected.to eq([vulnerability_with_resolution]) }
end
context 'when true argument is provided' do
let(:with_resolution) { true }
context 'when the argument is provided' do
context 'when the given argument is `true`' do
let(:with_resolution) { true }
it { is_expected.to eq([vulnerability_with_resolution]) }
end
it { is_expected.to eq([vulnerability_with_resolution]) }
end
context 'when false argument is provided' do
let(:with_resolution) { false }
context 'when the given argument is `false`' do
let(:with_resolution) { false }
it { is_expected.to eq([vulnerability_without_resolution]) }
it { is_expected.to eq([vulnerability_without_resolution]) }
end
end
end
describe '.with_issues' do
let_it_be(:vulnerability_with_issues) { create(:vulnerability, :with_issue_links) }
let_it_be(:vulnerability_without_issues) { create(:vulnerability) }
let_it_be(:vulnerability_without_issues) { vulnerability }
subject { described_class.with_issues(with_issues) }
......@@ -387,25 +408,27 @@ RSpec.describe Vulnerability do
it { is_expected.to eq([vulnerability_with_issues]) }
end
context 'when true argument is provided' do
let(:with_issues) { true }
context 'when the argument is provided' do
context 'when the given argument is `true`' do
let(:with_issues) { true }
it { is_expected.to eq([vulnerability_with_issues]) }
end
it { is_expected.to eq([vulnerability_with_issues]) }
end
context 'when false argument is provided' do
let(:with_issues) { false }
context 'when the given argument is `false`' do
let(:with_issues) { false }
it { is_expected.to eq([vulnerability_without_issues]) }
it { is_expected.to eq([vulnerability_without_issues]) }
end
end
end
describe '.order_by' do
subject { described_class.order_by(method) }
let_it_be(:low_vulnerability) { vulnerability }
let_it_be(:critical_vulnerability) { create(:vulnerability, :critical) }
let_it_be(:medium_vulnerability) { create(:vulnerability, :medium) }
let!(:low_vulnerability) { create(:vulnerability, :low) }
let!(:critical_vulnerability) { create(:vulnerability, :critical) }
let!(:medium_vulnerability) { create(:vulnerability, :medium) }
subject { described_class.order_by(method) }
context 'when ordered by severity_desc' do
let(:method) { :severity_desc }
......@@ -421,16 +444,26 @@ RSpec.describe Vulnerability do
end
describe '.counts_by_day_and_severity' do
let(:current_date) { Time.zone.parse('2019-10-31') }
let(:from_date) { Date.parse('2019-10-22') }
let(:to_date) { Date.parse('2019-10-28') }
let!(:vulnerability_1) { create(:vulnerability, created_at: 5.days.ago, dismissed_at: Date.current, severity: :critical) }
let!(:vulnerability_2) { create(:vulnerability, created_at: 5.days.ago, dismissed_at: 1.day.ago, severity: :high) }
let!(:vulnerability_3) { create(:vulnerability, created_at: 4.days.ago, resolved_at: 2.days.ago, severity: :critical) }
subject(:counts_by_day_and_severity) { ::Vulnerability.counts_by_day_and_severity(from_date, to_date) }
around do |example|
travel_to(current_date) { example.run }
end
context 'when the vulnerability_history feature flag is disabled' do
before do
stub_feature_flags(vulnerability_history: false)
end
it 'returns an empty array' do
create(:vulnerability, created_at: 1.day.ago)
counts_by_day_and_severity = ::Vulnerability.counts_by_day_and_severity(1.day.ago, Date.current)
expect(counts_by_day_and_severity).to be_empty
end
end
......@@ -440,14 +473,8 @@ RSpec.describe Vulnerability do
stub_feature_flags(vulnerability_history: true)
end
it 'returns the count of unresolved, undismissed vulnerabilities for each severity for each day from the start date to the end date' do
travel_to(Time.zone.parse('2019-10-31')) do
create(:vulnerability, created_at: 5.days.ago, dismissed_at: Date.current, severity: :critical)
create(:vulnerability, created_at: 5.days.ago, dismissed_at: 1.day.ago, severity: :high)
create(:vulnerability, created_at: 4.days.ago, resolved_at: 2.days.ago, severity: :critical)
counts_by_day_and_severity = ::Vulnerability.counts_by_day_and_severity(Date.parse('2019-10-22'), Date.parse('2019-10-28'))
context 'when there are less than 10 days between the from and to dates' do
it 'returns the count of unresolved, undismissed vulnerabilities for each severity for each day from the start date to the end date' do
expect(counts_by_day_and_severity.order(:day, :severity).to_json).to eq([
{ 'id' => nil, 'severity' => 'high', 'day' => '2019-10-26', 'count' => 1 },
{ 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-26', 'count' => 1 },
......@@ -459,9 +486,12 @@ RSpec.describe Vulnerability do
end
end
context 'there are more than 10 days between the start and end dates' do
context 'when theere are more than 10 days between the from and to dates' do
let(:from_date) { 10.days.ago.to_date }
let(:to_date) { Date.current }
it 'raises a TooManyDaysError' do
expect { ::Vulnerability.counts_by_day_and_severity(10.days.ago.to_date, Date.current) }.to raise_error(
expect { counts_by_day_and_severity }.to raise_error(
::Vulnerability::TooManyDaysError,
'Cannot fetch counts for more than 10 days'
)
......@@ -480,22 +510,24 @@ RSpec.describe Vulnerability do
describe '.grouped_by_severity' do
before do
create_list(:vulnerability, 6, :critical)
create_list(:vulnerability, 4, :high)
create_list(:vulnerability, 2, :medium)
create_list(:vulnerability, 5, :low)
# There is already a vulnerability created with `low`
# severity therefore we are not creating one with `low` severity here.
create_list(:vulnerability, 2, :critical)
create_list(:vulnerability, 1, :high)
create_list(:vulnerability, 1, :medium)
create_list(:vulnerability, 1, :info)
create_list(:vulnerability, 3, :unknown)
create_list(:vulnerability, 1, :unknown)
end
subject { described_class.grouped_by_severity.count }
it { is_expected.to eq('critical' => 6, 'high' => 4, 'info' => 1, 'low' => 5, 'medium' => 2, 'unknown' => 3) }
it { is_expected.to eq('critical' => 2, 'high' => 1, 'info' => 1, 'low' => 1, 'medium' => 1, 'unknown' => 1) }
end
describe '.by_project_fingerprints' do
let!(:vulnerability_1) { create(:vulnerability, :with_findings) }
let!(:vulnerability_2) { create(:vulnerability, :with_findings) }
let_it_be(:vulnerability_1) { vulnerability }
let_it_be(:vulnerability_2) { create(:vulnerability, :with_findings) }
let(:expected_vulnerabilities) { [vulnerability_1] }
subject { described_class.by_project_fingerprints(vulnerability_1.finding.project_fingerprint) }
......@@ -612,15 +644,13 @@ RSpec.describe Vulnerability do
end
describe '#finding' do
let_it_be(:project) { create(:project, :with_vulnerability) }
let_it_be(:vulnerability) { project.vulnerabilities.first }
let_it_be(:finding1) { create(:vulnerabilities_finding, vulnerability: vulnerability) }
let_it_be(:finding2) { create(:vulnerabilities_finding, vulnerability: vulnerability) }
let_it_be(:finding_1) { finding }
let_it_be(:finding_2) { create(:vulnerabilities_finding, vulnerability: vulnerability) }
subject { vulnerability.finding }
context 'with multiple findings' do
it { is_expected.to eq(finding1) }
it { is_expected.to eq(finding_1) }
end
end
......@@ -637,16 +667,12 @@ RSpec.describe Vulnerability do
end
describe '#resource_parent' do
let(:vulnerability) { build(:vulnerability) }
subject(:resource_parent) { vulnerability.resource_parent }
it { is_expected.to eq(vulnerability.project) }
it { is_expected.to eq(project) }
end
describe '#discussions_rendered_on_frontend?' do
let(:vulnerability) { build(:vulnerability) }
subject(:discussions_rendered_on_frontend) { vulnerability.discussions_rendered_on_frontend? }
it { is_expected.to be true }
......@@ -665,18 +691,14 @@ RSpec.describe Vulnerability do
end
describe '#note_etag_key' do
let(:vulnerability) { create(:vulnerability) }
it 'returns a correct etag key' do
expect(vulnerability.note_etag_key).to eq(
::Gitlab::Routing.url_helpers.project_security_vulnerability_notes_path(vulnerability.project, vulnerability)
::Gitlab::Routing.url_helpers.project_security_vulnerability_notes_path(project, vulnerability)
)
end
end
describe '#user_notes_count' do
let_it_be(:vulnerability) { create(:vulnerability) }
let(:expected_count) { 10 }
let(:mock_service_class) { instance_double(Vulnerabilities::UserNotesCountService, count: expected_count) }
......@@ -693,8 +715,7 @@ RSpec.describe Vulnerability do
end
describe '#after_note_changed' do
let_it_be(:vulnerability) { create(:vulnerability) }
let(:vulnerability) { build(:vulnerability) }
let(:note) { instance_double(Note, system?: is_system_note?) }
let(:mock_service_class) { instance_double(Vulnerabilities::UserNotesCountService, delete_cache: true) }
......@@ -738,8 +759,6 @@ RSpec.describe Vulnerability do
end
describe '#stat_diff' do
let(:vulnerability) { build(:vulnerability) }
subject { vulnerability.stat_diff }
it { is_expected.to be_an_instance_of(Vulnerabilities::StatDiff) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment