Commit 7f86aa5a authored by Avielle Wolfe's avatar Avielle Wolfe Committed by Bob Van Landuyt

Fix heavy findings query on instance sec dash

The query we were using to fetch the list of vulnerability findings for
the instance security dashboard was fetching vulnerabilities for *all*
pipelines and _then_ filtering them by project.

Now we filter the pipelines we're fetching by project,
greatly reducing the time to execute the query.

https://gitlab.com/gitlab-org/gitlab/issues/197238
parent 580c2829
......@@ -16,7 +16,10 @@ module Security
end
def vulnerable
@vulnerable ||= ApplicationInstance.new
@vulnerable ||= InstanceSecurityDashboard.new(
current_user,
project_ids: params.fetch(:project_id, [])
)
end
end
end
......@@ -3,37 +3,5 @@
module Security
class VulnerabilityFindingsController < ::Security::ApplicationController
include ProjectCollectionVulnerabilityFindingsActions
before_action :remove_invalid_project_ids
private
def remove_invalid_project_ids
render_empty_response if valid_project_ids.empty?
params[:project_id] = valid_project_ids
end
def render_empty_response
respond_to do |format|
format.json do
render json: {}
end
end
end
def valid_project_ids
return security_dashboard_project_ids if request_project_ids.empty?
security_dashboard_project_ids & request_project_ids
end
def request_project_ids
params.fetch(:project_id, []).map(&:to_i)
end
def security_dashboard_project_ids
current_user.security_dashboard_project_ids
end
end
end
......@@ -62,11 +62,11 @@ module EE
end
def security_dashboard_available?
app_instance = ApplicationInstance.new
security_dashboard = InstanceSecurityDashboard.new(current_user)
::Feature.enabled?(:security_dashboard, default_enabled: true) &&
app_instance.feature_available?(:security_dashboard) &&
can?(current_user, :read_application_instance_security_dashboard, app_instance)
security_dashboard.feature_available?(:security_dashboard) &&
can?(current_user, :read_instance_security_dashboard, security_dashboard)
end
end
end
# frozen_string_literal: true
class ApplicationInstance
extend ActiveModel::Naming
include ::Vulnerable
def all_pipelines
::Ci::Pipeline.all
end
def feature_available?(feature)
License.feature_available?(feature)
end
end
......@@ -346,14 +346,6 @@ module EE
read_attribute(:support_bot)
end
def security_dashboard_project_ids
if self.can?(:read_all_resources)
security_dashboard_projects.ids
else
security_dashboard_projects.visible_to_user(self).ids
end
end
protected
override :password_required?
......
# frozen_string_literal: true
class InstanceSecurityDashboard
extend ActiveModel::Naming
include ::Vulnerable
def self.name
'Instance'
end
def initialize(user, project_ids: [])
@project_ids = project_ids
@user = user
end
def all_pipelines
::Ci::Pipeline.where(project_id: users_projects_with_security_reports)
end
def project_ids_with_security_reports
users_projects_with_security_reports.pluck(:project_id)
end
def feature_available?(feature)
License.feature_available?(feature)
end
private
attr_reader :project_ids, :user
def users_projects_with_security_reports
return visible_users_security_dashboard_projects if project_ids.empty?
visible_users_security_dashboard_projects.where(project_id: project_ids)
end
def visible_users_security_dashboard_projects
return users_security_dashboard_projects if user.can?(:read_all_resources)
users_security_dashboard_projects.where('EXISTS(?)', project_authorizations)
end
def users_security_dashboard_projects
UsersSecurityDashboardProject.select(:project_id).where(user: user)
end
def project_authorizations
ProjectAuthorization
.select(1)
.where(users_security_dashboard_projects: { user_id: user.id })
.where(project_authorizations: { user_id: user.id })
.where('users_security_dashboard_projects.project_id = project_authorizations.project_id')
end
end
# frozen_string_literal: true
class ApplicationInstancePolicy < BasePolicy
rule { ~anonymous }.enable :read_application_instance_security_dashboard
end
# frozen_string_literal: true
class InstancePolicy < BasePolicy
rule { ~anonymous }.enable :read_instance_security_dashboard
end
---
title: Fix vulnerability finding list endpoint query timeout on instance security dashboard
merge_request: 23232
author:
type: fixed
......@@ -58,10 +58,6 @@ module Gitlab
return filters[:project_id] if filters.key?('project_id')
vulnerable.project_ids_with_security_reports
rescue NoMethodError
vulnerable_name = vulnerable.model_name.human.downcase
raise NoProjectIDsError, "A project_id filter must be given with this #{vulnerable_name}"
end
end
end
......
......@@ -60,11 +60,13 @@ module Gitlab
end
def project_ids_to_fetch
project_ids = vulnerable.is_a?(Project) ? [vulnerable.id] : []
return [vulnerable.id] if vulnerable.is_a?(Project)
return filters[:project_id] + project_ids if filters.key?('project_id')
vulnerable.is_a?(Group) ? vulnerable.project_ids_with_security_reports : project_ids
if filters.key?('project_id')
vulnerable.project_ids_with_security_reports & filters[:project_id].map(&:to_i)
else
vulnerable.project_ids_with_security_reports
end
end
end
end
......
......@@ -58,9 +58,9 @@ describe DashboardHelper, type: :helper do
end
where(:ability, :feature_flag, :nav_link) do
:read_operations_dashboard | nil | :operations
:read_operations_dashboard | :environments_dashboard | :environments
:read_application_instance_security_dashboard | :security_dashboard | :security
:read_operations_dashboard | nil | :operations
:read_operations_dashboard | :environments_dashboard | :environments
:read_instance_security_dashboard | :security_dashboard | :security
end
with_them do
......@@ -120,10 +120,9 @@ describe DashboardHelper, type: :helper do
def stub_resource_visibility(feature_flag, read_other_resources:, read_security_dashboard:, security_dashboard_available:)
if feature_flag == :security_dashboard
app_instance = double(ApplicationInstance, feature_available?: security_dashboard_available)
allow(ApplicationInstance).to receive(:new).and_return(app_instance)
stub_licensed_features(feature_flag => security_dashboard_available)
allow(helper).to receive(:can?).with(user, ability, app_instance).and_return(read_security_dashboard)
allow(helper).to receive(:can?).and_return(read_security_dashboard)
else
allow(helper).to receive(:can?).with(user, ability).and_return(read_other_resources)
end
......
......@@ -55,10 +55,16 @@ describe Gitlab::Vulnerabilities::HistoryCache do
end
end
context 'when given an ApplicationInstance' do
context 'when given an InstanceSecurityDashboard' do
it_behaves_like 'the history cache when given an expected Vulnerable' do
let(:group) { create(:group) }
let(:vulnerable) { ApplicationInstance.new }
let(:user) { create(:user) }
let(:vulnerable) { InstanceSecurityDashboard.new(user) }
before do
project.add_developer(user)
user.security_dashboard_projects << project
end
end
end
end
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::Vulnerabilities::History do
describe '#findings_counter', :use_clean_rails_memory_store_caching do
shared_examples 'the history cache when given an expected Vulnerable' do
let(:filters) { project_ids }
let(:filters) { ActionController::Parameters.new }
let(:today) { Date.parse('20191031') }
before do
......@@ -18,7 +18,7 @@ describe Gitlab::Vulnerabilities::History do
subject(:counter) { described_class.new(vulnerable, params: filters).findings_counter }
context 'when filters are passed' do
let(:filters) { project_ids.merge(report_type: :sast) }
let(:filters) { ActionController::Parameters.new({ 'report_type' => ['sast'] }) }
it 'does not call Gitlab::Vulnerabilities::HistoryCache' do
expect(Gitlab::Vulnerabilities::HistoryCache).not_to receive(:new)
......@@ -57,6 +57,20 @@ describe Gitlab::Vulnerabilities::History do
end
end
context 'when a project_id filter is passed' do
let(:filters) { ActionController::Parameters.new({ 'project_id' => [project1] }) }
it 'only fetches history for the filtered by projects' do
expect(Gitlab::Vulnerabilities::HistoryCache).to receive(:new).once.and_call_original
Timecop.freeze(today) do
expect(counter[:total]).to eq({ today => 1 })
expect(counter[:high]).to eq({})
expect(counter[:medium]).to eq({ today => 1 })
end
end
end
def create_vulnerabilities(count, project, options = {})
report_type = options[:report_type] || :sast
severity = options[:severity] || :high
......@@ -71,29 +85,23 @@ describe Gitlab::Vulnerabilities::History do
let(:group) { create(:group) }
let(:project1) { create(:project, :public, namespace: group) }
let(:project2) { create(:project, :public, namespace: group) }
let(:project_ids) { {} }
let(:vulnerable) { group }
end
end
context 'when given an ApplicationInstance' do
let(:vulnerable) { ApplicationInstance.new }
context 'when given an InstanceSecurityDashboard' do
it_behaves_like 'the history cache when given an expected Vulnerable' do
let(:group) { create(:group) }
let(:project1) { create(:project, :public, namespace: group) }
let(:project2) { create(:project, :public, namespace: group) }
let(:user) { create(:user) }
let(:vulnerable) { InstanceSecurityDashboard.new(user) }
context 'and a project_id filter' do
it_behaves_like 'the history cache when given an expected Vulnerable' do
let(:group) { create(:group) }
let(:project1) { create(:project, :public, namespace: group) }
let(:project2) { create(:project, :public, namespace: group) }
let(:project_ids) { ActionController::Parameters.new({ 'project_id' => [project1, project2] }) }
end
end
before do
project1.add_developer(user)
project2.add_developer(user)
context 'and no project_id filter' do
it 'throws an error saying that the filter must be given' do
expect { described_class.new(vulnerable, params: {}).findings_counter }.to raise_error(
Gitlab::Vulnerabilities::History::NoProjectIDsError,
"A project_id filter must be given with this #{vulnerable.model_name.human.downcase}"
)
user.security_dashboard_projects << [project1, project2]
end
end
end
......
......@@ -60,6 +60,15 @@ describe Gitlab::Vulnerabilities::Summary do
expect(counter[:high]).to eq(2)
end
end
context 'when a project_id param is passed' do
let(:filters) { ActionController::Parameters.new({ project_id: [project1.id.to_s] }) }
it 'only fetches findings for the given projects' do
expect(counter[:high]).to eq(0)
expect(counter[:medium]).to eq(1)
end
end
end
def create_vulnerabilities(count, project, options = {})
......
# frozen_string_literal: true
require 'spec_helper'
describe ApplicationInstance do
it_behaves_like Vulnerable do
let(:vulnerable) { described_class.new }
end
describe '#all_pipelines' do
it 'returns all CI pipelines for the instance' do
allow(::Ci::Pipeline).to receive(:all)
described_class.new.all_pipelines
expect(::Ci::Pipeline).to have_received(:all)
end
end
describe '#feature_available?' do
subject { described_class.new.feature_available?(:security_dashboard) }
context "when the feature is available for the instance's license" do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns true' do
is_expected.to be_truthy
end
end
context "when the feature is not available for the instance's license" do
before do
stub_licensed_features(security_dashboard: false)
end
it 'returns false' do
is_expected.to be_falsy
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe InstanceSecurityDashboard do
let(:pipeline1) { create(:ci_pipeline, project: project1) }
let(:pipeline2) { create(:ci_pipeline, project: project2) }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let(:project_ids) { [project1.id] }
let(:user) { create(:user) }
before do
project1.add_developer(user)
user.security_dashboard_projects << [project1, project2]
end
subject { described_class.new(user, project_ids: project_ids) }
it_behaves_like Vulnerable do
let(:vulnerable) { described_class.new(user, project_ids: project_ids) }
end
describe '.name' do
it 'is programmatically named Instance' do
expect(described_class.name).to eq('Instance')
end
end
describe '#all_pipelines' do
it 'returns pipelines for the projects with security reports' do
expect(subject.all_pipelines).to contain_exactly(pipeline1)
end
end
describe '#project_ids_with_security_reports' do
context 'when given project IDs' do
it "returns the project IDs that are also on the user's security dashboard" do
expect(subject.project_ids_with_security_reports).to contain_exactly(project1.id)
end
end
context 'when not given project IDs' do
let(:project_ids) { [] }
it "returns the security dashboard projects' IDs" do
expect(subject.project_ids_with_security_reports).to contain_exactly(project1.id)
end
end
context 'when the user cannot read all resources' do
let(:project_ids) { [project1.id, project2.id] }
it 'only includes projects they can read' do
expect(subject.project_ids_with_security_reports).to contain_exactly(project1.id)
end
end
context 'when the user can read all resources' do
let(:project_ids) { [project1.id, project2.id] }
let(:user) { create(:auditor) }
it 'includes all dashboard projects' do
expect(subject.project_ids_with_security_reports).to contain_exactly(project1.id, project2.id)
end
end
end
describe '#feature_available?' do
subject { described_class.new(user).feature_available?(:security_dashboard) }
context "when the feature is available for the instance's license" do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns true' do
is_expected.to be_truthy
end
end
context "when the feature is not available for the instance's license" do
before do
stub_licensed_features(security_dashboard: false)
end
it 'returns false' do
is_expected.to be_falsy
end
end
end
end
......@@ -824,32 +824,4 @@ describe User do
end
end
end
describe '#security_dashboard_project_ids' do
let(:project) { create(:project) }
context 'when the user can read all resources' do
it "returns the ids for all of the user's security dashboard projects" do
admin = create(:admin)
auditor = create(:auditor)
admin.security_dashboard_projects << project
auditor.security_dashboard_projects << project
expect(admin.security_dashboard_project_ids).to eq([project.id])
expect(auditor.security_dashboard_project_ids).to eq([project.id])
end
end
context 'when the user cannot read all resources' do
it 'returns the ids for security dashboard projects visible to the user' do
user = create(:user)
member_project = create(:project)
member_project.add_developer(user)
user.security_dashboard_projects << [project, member_project]
expect(user.security_dashboard_project_ids).to eq([member_project.id])
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe ApplicationInstancePolicy do
describe InstancePolicy do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
......@@ -12,15 +12,15 @@ describe ApplicationInstancePolicy do
subject { described_class.new(current_user, [user]) }
describe 'read_application_instance_security_dashboard' do
describe 'read_instance_security_dashboard' do
context 'when the user is not logged in' do
let(:current_user) { nil }
it { is_expected.not_to be_allowed(:read_application_instance_security_dashboard) }
it { is_expected.not_to be_allowed(:read_instance_security_dashboard) }
end
context 'when the user is logged in' do
it { is_expected.to be_allowed(:read_application_instance_security_dashboard) }
it { is_expected.to be_allowed(:read_instance_security_dashboard) }
end
end
end
......@@ -2,112 +2,6 @@
require 'spec_helper'
shared_examples 'instance security dashboard vulnerability findings endpoint' do
let(:findings_headers) { { 'ACCEPT' => 'application/json' } }
let(:findings_project) { create(:project) }
let(:findings_user) { create(:user) }
let(:fetcher_double) do
double(
findings_fetcher,
execute: Vulnerabilities::Occurrence.none,
findings_counter: {}
)
end
before do
findings_project.add_developer(findings_user)
findings_user.security_dashboard_projects << findings_project
login_as(findings_user)
stub_licensed_features(security_dashboard: true)
end
context 'when no project ID param is given' do
it "defaults to the current user's security dashboard projects" do
_other_project = create(:project)
expect(findings_fetcher).to receive(:new).with(
anything,
params: hash_including(project_id: [findings_project.id])
).and_return(fetcher_double)
get findings_endpoint, headers: findings_headers
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when the request includes IDs from projects the current user does not have on their dashboard' do
it "only fetches vulnerabilities from the current user's security dashboard projects" do
inaccessible_project = create(:project)
project_ids_param = { project_id: [findings_project.id, inaccessible_project.id] }
expect(findings_fetcher).to receive(:new).with(
anything,
params: hash_including(project_id: [findings_project.id])
).and_return(fetcher_double)
get findings_endpoint, headers: findings_headers, params: project_ids_param
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when the request only includes invalid project IDs' do
it 'replies with an empty response' do
inaccessible_project = create(:project)
project_ids_param = { project_id: [inaccessible_project.id] }
expect(findings_fetcher).not_to receive(:new)
get findings_endpoint, headers: findings_headers, params: project_ids_param
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
end
context 'when the request includes project IDs the current user is not a member of' do
context 'and the user is not an auditor' do
it 'only fetches vulnerabilities from projects to which the user has access' do
inaccessible_project = create(:project)
findings_user.security_dashboard_projects << inaccessible_project
project_ids_param = { project_id: [findings_project.id, inaccessible_project.id] }
expect(findings_fetcher).to receive(:new).with(
anything,
params: hash_including(project_id: [findings_project.id])
).and_return(fetcher_double)
get findings_endpoint, headers: findings_headers, params: project_ids_param
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'and the user is an auditor' do
let(:findings_user) { create(:auditor) }
it 'fetches vulnerabilities for all given projects on their dashboard' do
stub_feature_flags(cache_vulnerability_summary: false)
unmembered_project = create(:project)
findings_user.security_dashboard_projects << unmembered_project
project_ids_param = { project_id: [findings_project.id, unmembered_project.id] }
expect(findings_fetcher).to receive(:new).with(
anything,
params: hash_including(project_id: array_including(findings_project.id, unmembered_project.id))
).and_return(fetcher_double)
get findings_endpoint, headers: findings_headers, params: project_ids_param
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
describe 'GET /-/security/vulnerability_findings' do
it_behaves_like 'security dashboard JSON endpoint' do
let(:security_dashboard_request) do
......@@ -116,18 +10,12 @@ describe 'GET /-/security/vulnerability_findings' do
end
context 'when the current user is authenticated' do
let(:findings_request_params) { project_ids_param }
let(:findings_request_params) { {} }
let(:headers) { { 'ACCEPT' => 'application/json' } }
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
let(:project) { create(:project) }
let(:project_ids_param) { { project_id: [project.id] } }
let(:user) { create(:user) }
it_behaves_like 'instance security dashboard vulnerability findings endpoint' do
let(:findings_endpoint) { security_vulnerability_findings_path }
let(:findings_fetcher) { ::Security::VulnerabilityFindingsFinder }
end
before do
project.add_developer(user)
user.security_dashboard_projects << project
......@@ -138,7 +26,7 @@ describe 'GET /-/security/vulnerability_findings' do
subject { get security_vulnerability_findings_path, headers: headers, params: findings_request_params }
it 'returns an ordered list of vulnerability findings for the given projects' do
it "returns an ordered list of vulnerability findings for the user's security dashboard projects" do
critical_vulnerability = create(
:vulnerabilities_occurrence,
pipelines: [pipeline],
......@@ -149,14 +37,14 @@ describe 'GET /-/security/vulnerability_findings' do
subject
expect(response).to have_gitlab_http_status(200)
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) { project_ids_param.merge(page: 2) }
let(:findings_request_params) { { page: 2 } }
before do
Vulnerabilities::Occurrence.paginates_per 2
......@@ -203,6 +91,32 @@ describe 'GET /-/security/vulnerability_findings' do
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)
......@@ -213,7 +127,7 @@ describe 'GET /-/security/vulnerability_findings' do
end
context 'with a single report filter' do
let(:findings_request_params) { project_ids_param.merge(report_type: ['sast']) }
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
......@@ -222,7 +136,7 @@ describe 'GET /-/security/vulnerability_findings' do
end
context 'with multiple report filters' do
let(:findings_request_params) { project_ids_param.merge(report_type: %w[sast dependency_scanning]) }
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
......@@ -241,18 +155,12 @@ describe 'GET /-/security/vulnerability_findings/summary' do
end
context 'when the current user is authenticated' do
let(:findings_request_params) { project_ids_param }
let(:findings_request_params) { {} }
let(:headers) { { 'ACCEPT' => 'application/json' } }
let(:project_ids_param) { { project_id: [project.id] } }
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
let(:user) { create(:user) }
let(:project) { create(:project) }
it_behaves_like 'instance security dashboard vulnerability findings endpoint' do
let(:findings_endpoint) { summary_security_vulnerability_findings_path }
let(:findings_fetcher) { ::Security::VulnerabilityFindingsFinder }
end
before do
project.add_developer(user)
user.security_dashboard_projects << project
......@@ -275,15 +183,40 @@ describe 'GET /-/security/vulnerability_findings/summary' do
it 'returns vulnerability findings counts for all report types' do
subject
expect(response).to have_gitlab_http_status(200)
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) { project_ids_param.merge(report_type: %w[sast dast], severity: %w[high low]) }
let(:findings_request_params) { { report_type: %w[sast dast], severity: %w[high low] } }
it 'returns counts for filtered vulnerability findings' do
subject
......@@ -301,25 +234,18 @@ describe 'GET /-/security/vulnerability_findings/history' do
let(:security_dashboard_request) do
get(
history_security_vulnerability_findings_path,
headers: { 'ACCEPT' => 'application/json' },
params: { project_id: [] }
headers: { 'ACCEPT' => 'application/json' }
)
end
end
context 'when the current user is authenticated' do
let(:findings_request_params) { project_ids_param }
let(:findings_request_params) { {} }
let(:headers) { { 'ACCEPT' => 'application/json' } }
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
let(:project) { create(:project) }
let(:project_ids_param) { { project_id: [project.id] } }
let(:user) { create(:user) }
it_behaves_like 'instance security dashboard vulnerability findings endpoint' do
let(:findings_endpoint) { history_security_vulnerability_findings_path }
let(:findings_fetcher) { ::Gitlab::Vulnerabilities::History }
end
before do
project.add_developer(user)
user.security_dashboard_projects << project
......@@ -363,7 +289,7 @@ describe 'GET /-/security/vulnerability_findings/history' do
subject
end
expect(response).to have_gitlab_http_status(200)
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 })
......@@ -387,8 +313,38 @@ describe 'GET /-/security/vulnerability_findings/history' do
})
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) { project_ids_param.merge(report_type: %w[sast]) }
let(:findings_request_params) { { report_type: %w[sast] } }
before do
travel_to(Time.zone.parse('2019-02-11')) do
......
......@@ -3,7 +3,7 @@
module VulnerableHelpers
class BadVulnerableError < StandardError
def message
'The given vulnerable must be either `Project`, `Namespace`, or `ApplicationInstance`'
'The given vulnerable must be either `Project`, `Namespace`, or `InstanceSecurityDashboard`'
end
end
......@@ -13,21 +13,8 @@ module VulnerableHelpers
vulnerable
when Namespace
create(:project, namespace: vulnerable)
when ApplicationInstance
create(:project)
else
raise BadVulnerableError
end
end
def as_external_vulnerable_project(vulnerable)
case vulnerable
when Project
create(:project)
when Namespace
create(:project)
when ApplicationInstance
nil
when InstanceSecurityDashboard
Project.find(vulnerable.project_ids_with_security_reports.first)
else
raise BadVulnerableError
end
......
......@@ -3,7 +3,7 @@
RSpec.shared_examples Vulnerable do
include VulnerableHelpers
let(:external_project) { as_external_vulnerable_project(vulnerable) }
let(:external_project) { create(:project) }
let(:failed_pipeline) { create(:ci_pipeline, :failed, project: vulnerable_project) }
let!(:old_vuln) { create_vulnerability(vulnerable_project) }
......
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