Commit e71bbebc authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '350333-api-endpoint-ls-collapsed' into 'master'

Add collapsed comparer for license_scanning

See merge request gitlab-org/gitlab!82526
parents fe8946ee 44f73201
......@@ -26,13 +26,13 @@ module EE
:sast_reports, :secret_detection_reports,
:dast_reports, :coverage_fuzzing_reports, :api_fuzzing_reports,
:metrics_reports]
before_action :authorize_read_licenses!, only: [:license_scanning_reports]
before_action :authorize_read_licenses!, only: [:license_scanning_reports, :license_scanning_reports_collapsed]
feature_category :vulnerability_management, [:container_scanning_reports, :dependency_scanning_reports,
:sast_reports, :secret_detection_reports,
:dast_reports, :coverage_fuzzing_reports, :api_fuzzing_reports]
feature_category :metrics, [:metrics_reports]
feature_category :license_compliance, [:license_scanning_reports]
feature_category :license_compliance, [:license_scanning_reports, :license_scanning_reports_collapsed]
feature_category :code_review, [:delete_description_version, :description_diff]
urgency :high, [:delete_description_version]
......@@ -52,6 +52,10 @@ module EE
reports_response(merge_request.compare_license_scanning_reports(current_user))
end
def license_scanning_reports_collapsed
reports_response(merge_request.compare_license_scanning_reports_collapsed(current_user))
end
def container_scanning_reports
reports_response(merge_request.compare_container_scanning_reports(current_user), head_pipeline)
end
......
......@@ -247,6 +247,12 @@ module EE
compare_reports(::Ci::CompareLicenseScanningReportsService, current_user)
end
def compare_license_scanning_reports_collapsed(current_user)
return missing_report_error("license scanning") unless actual_head_pipeline&.license_scan_completed?
compare_reports(::Ci::CompareLicenseScanningReportsCollapsedService, current_user)
end
def has_metrics_reports?
!!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.metrics_reports)
end
......
# frozen_string_literal: true
module LicenseCompliance
class CollapsedComparerEntity < Grape::Entity
expose :new_licenses do |comparer|
comparer.new_licenses.count
end
expose :existing_licenses do |comparer|
comparer.existing_licenses.count
end
expose :removed_licenses do |comparer|
comparer.removed_licenses.count
end
end
end
# frozen_string_literal: true
module LicenseCompliance
class CollapsedComparerSerializer < BaseSerializer
entity CollapsedComparerEntity
end
end
# frozen_string_literal: true
module LicenseCompliance
class ComparerEntity < Grape::Entity
expose :new_licenses, using: ::Security::LicensePolicyEntity
expose :existing_licenses, using: ::Security::LicensePolicyEntity
expose :removed_licenses, using: ::Security::LicensePolicyEntity
end
end
# frozen_string_literal: true
module LicenseCompliance
class ComparerSerializer < BaseSerializer
entity ComparerEntity
end
end
# frozen_string_literal: true
class LicenseScanningReportsComparerEntity < Grape::Entity
expose :new_licenses, using: ::Security::LicensePolicyEntity
expose :existing_licenses, using: ::Security::LicensePolicyEntity
expose :removed_licenses, using: ::Security::LicensePolicyEntity
end
# frozen_string_literal: true
class LicenseScanningReportsComparerSerializer < BaseSerializer
entity LicenseScanningReportsComparerEntity
end
# frozen_string_literal: true
module Ci
class CompareLicenseScanningReportsCollapsedService < ::Ci::CompareLicenseScanningReportsService
def serializer_class
::LicenseCompliance::CollapsedComparerSerializer
end
end
end
......@@ -7,7 +7,7 @@ module Ci
end
def serializer_class
LicenseScanningReportsComparerSerializer
::LicenseCompliance::ComparerSerializer
end
def get_report(pipeline)
......
......@@ -6,6 +6,7 @@ resources :merge_requests, only: [], constraints: { id: /\d+/ } do
delete '/descriptions/:version_id', action: :delete_description_version, as: :delete_description_version
get :metrics_reports
get :license_scanning_reports
get :license_scanning_reports_collapsed
get :container_scanning_reports
get :dependency_scanning_reports
get :sast_reports
......
......@@ -9,7 +9,7 @@ RSpec.shared_examples 'authorize read pipeline' do
let(:comparison_status) { {} }
it 'restricts access to signed out users' do
sign_out user
sign_out viewer
subject
......@@ -908,6 +908,7 @@ RSpec.describe Projects::MergeRequestsController do
let_it_be_with_reload(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: project, author: author) }
let(:comparison_status) { { status: :parsed, data: { new_licenses: [], existing_licenses: [], removed_licenses: [] } } }
let(:expected_response) { { "new_licenses" => [], "existing_licenses" => [], "removed_licenses" => [] } }
let(:params) do
{
......@@ -921,131 +922,45 @@ RSpec.describe Projects::MergeRequestsController do
before do
stub_licensed_features(license_scanning: true)
allow_any_instance_of(::MergeRequest).to receive(:compare_reports)
.with(::Ci::CompareLicenseScanningReportsService, viewer).and_return(comparison_status)
end
context 'when the pipeline is running' do
before do
allow(::Gitlab::PollingInterval).to receive(:set_header)
merge_request.head_pipeline.update!(status: :running)
subject
end
context 'when the report is being parsed' do
let(:comparison_status) { { status: :parsing } }
specify { expect(::Gitlab::PollingInterval).to have_received(:set_header) }
specify { expect(response).to have_gitlab_http_status(:no_content) }
end
context 'when the report is ready' do
let(:comparison_status) { { status: :parsed, data: { new_licenses: [], existing_licenses: [], removed_licenses: [] } } }
specify { expect(::Gitlab::PollingInterval).not_to have_received(:set_header) }
specify { expect(response).to have_gitlab_http_status(:ok) }
specify { expect(json_response).to eq({ "new_licenses" => [], "existing_licenses" => [], "removed_licenses" => [] }) }
end
end
context 'when comparison is being processed' do
let(:comparison_status) { { status: :parsing } }
it 'sends polling interval' do
expect(::Gitlab::PollingInterval).to receive(:set_header)
subject
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when comparison is done' do
let(:comparison_status) { { status: :parsed, data: { new_licenses: [], existing_licenses: [], removed_licenses: [] } } }
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 200 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ "new_licenses" => [], "existing_licenses" => [], "removed_licenses" => [] })
allow_next_found_instance_of(::MergeRequest) do |merge_request|
allow(merge_request).to receive(:compare_reports)
.with(::Ci::CompareLicenseScanningReportsService, viewer)
.and_return(comparison_status)
end
end
context 'when user created corrupted test reports' do
let(:comparison_status) { { status: :error, status_reason: 'Failed to parse license scanning reports' } }
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 400 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq({ 'status_reason' => 'Failed to parse license scanning reports' })
end
end
it_behaves_like 'license scanning report comparison'
it_behaves_like 'authorize read pipeline'
end
context "when a user is NOT authorized to read licenses on a project" do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: project, author: author) }
describe 'GET #license_scanning_reports_collapsed' do
let_it_be_with_reload(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: project, author: author) }
let(:viewer) { create(:user) }
let(:comparison_status) { { status: :parsed, data: { new_licenses: 0, existing_licenses: 0, removed_licenses: 0 } } }
let(:expected_response) { { "new_licenses" => 0, "existing_licenses" => 0, "removed_licenses" => 0 } }
it 'returns a report' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
let(:params) do
{
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid
}
end
context "when a user is authorized to read the licenses" do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: project, author: author) }
subject { get :license_scanning_reports_collapsed, params: params, format: :json }
let(:viewer) { create(:user) }
before do
project.add_reporter(viewer)
end
it 'returns a report' do
subject
before do
stub_licensed_features(license_scanning: true)
expect(response).to have_gitlab_http_status(:ok)
allow_next_found_instance_of(::MergeRequest) do |merge_request|
allow(merge_request).to receive(:compare_reports)
.with(::Ci::CompareLicenseScanningReportsCollapsedService, viewer)
.and_return(comparison_status)
end
end
context "when a maintainer is authorized to read licenses on a merge request from a forked project" do
let(:project) { create(:project, :repository, :public, :builds_private) }
let(:forked_project) { fork_project(project, nil, repository: true) }
let(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: forked_project, target_project: project) }
let(:viewer) { create(:user) }
before do
project.add_maintainer(viewer)
forked_project.add_maintainer(user)
end
it 'returns a report' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
it_behaves_like 'license scanning report comparison'
it_behaves_like 'authorize read pipeline'
end
describe 'GET #metrics_reports' do
......
......@@ -862,6 +862,47 @@ RSpec.describe MergeRequest do
end
end
describe '#compare_license_scanning_reports_collapsed' do
subject(:report) { merge_request.compare_license_scanning_reports_collapsed(current_user) }
let(:current_user) { project.users.first }
let!(:base_pipeline) do
create(:ee_ci_pipeline,
:with_license_scanning_report,
project: project,
ref: merge_request.target_branch,
sha: merge_request.diff_base_sha)
end
let!(:head_pipeline) do
create(:ci_pipeline,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
context 'when service can be executed' do
before do
merge_request.update!(head_pipeline_id: head_pipeline.id)
allow_next_found_instance_of(Ci::Pipeline) do |pipeline|
allow(pipeline).to receive(:license_scan_completed?).and_return(true)
end
end
it 'returns compared report' do
expect(report[:status]).to eq(:parsing)
end
end
context 'when head pipeline does not have license scanning reports' do
it 'returns status and error message' do
expect(subject[:status]).to eq(:error)
end
end
end
describe '#compare_metrics_reports' do
subject { merge_request.compare_metrics_reports }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LicenseCompliance::CollapsedComparerEntity do
it_behaves_like 'comparer entity'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LicenseCompliance::ComparerEntity do
it_behaves_like 'comparer entity'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CompareLicenseScanningReportsCollapsedService do
include ProjectForksHelper
let_it_be(:project) { create(:project, :repository) }
let(:service) { described_class.new(project, nil) }
before do
stub_licensed_features(license_scanning: true)
end
describe '#execute' do
subject { service.execute(base_pipeline, head_pipeline) }
context 'when base and head pipelines have test reports' do
let_it_be(:base_pipeline) { create(:ee_ci_pipeline, :with_license_scanning_report, project: project) }
let_it_be(:head_pipeline) { create(:ee_ci_pipeline, :with_license_scanning_feature_branch, project: project) }
it 'exposes report with numbers of licenses by type' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['new_licenses']).to eq(1)
expect(subject[:data]['existing_licenses']).to eq(1)
expect(subject[:data]['removed_licenses']).to eq(3)
end
end
context 'when head pipeline has corrupted license scanning reports' do
let_it_be(:base_pipeline) { build(:ee_ci_pipeline, :with_corrupted_license_scanning_report, project: project) }
let_it_be(:head_pipeline) { build(:ee_ci_pipeline, :with_corrupted_license_scanning_report, project: project) }
it 'exposes empty report' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['new_licenses']).to eq(0)
expect(subject[:data]['existing_licenses']).to eq(0)
expect(subject[:data]['removed_licenses']).to eq(0)
end
context "when the base pipeline is nil" do
subject { service.execute(nil, head_pipeline) }
it 'exposes empty report' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['new_licenses']).to eq(0)
expect(subject[:data]['existing_licenses']).to eq(0)
expect(subject[:data]['removed_licenses']).to eq(0)
end
end
end
end
describe '#serializer_class' do
subject { service.serializer_class }
it { is_expected.to be(::LicenseCompliance::CollapsedComparerSerializer) }
end
end
# frozen_string_literal: true
RSpec.shared_examples 'license scanning report comparison' do
context 'when the report is being parsed' do
let(:comparison_status) { { status: :parsing } }
before do
allow(::Gitlab::PollingInterval).to receive(:set_header)
end
it 'returns 204 HTTP status' do
subject
expect(::Gitlab::PollingInterval).to have_received(:set_header)
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when comparison is being processed' do
let(:comparison_status) { { status: :parsing } }
it 'sends polling interval' do
expect(::Gitlab::PollingInterval).to receive(:set_header)
subject
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when comparison is done' do
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 200 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(expected_response)
end
end
context 'when user created corrupted test reports' do
let(:comparison_status) { { status: :error, status_reason: 'Failed to parse license scanning reports' } }
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 400 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq({ 'status_reason' => 'Failed to parse license scanning reports' })
end
end
context "when a user is authorized to read the licenses" do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:merge_request) { create(:ee_merge_request, :with_license_scanning_reports, source_project: project, author: author) }
let(:viewer) { create(:user) }
before do
project.add_reporter(viewer)
end
it 'returns 200 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when license_scanning feature is disabled" do
before do
stub_licensed_features(license_scanning: false)
end
it 'returns 404 status' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LicenseScanningReportsComparerEntity do
RSpec.shared_examples 'comparer entity' do
let(:entity) { described_class.new(::Gitlab::Ci::Reports::LicenseScanning::ReportsComparer.new(project.license_compliance(base_pipeline), project.license_compliance(head_pipeline))) }
let(:project) { create_default(:project, :repository) }
let(:base_pipeline) { create(:ci_pipeline, :success, project: project, builds: [create(:ee_ci_build, :license_scan_v2_1, :success)]) }
let(:head_pipeline) { create(:ci_pipeline, :success, project: project, builds: [create(:ee_ci_build, :success)]) }
let_it_be(:project) { create_default(:project, :repository) }
let_it_be(:base_pipeline) { create(:ci_pipeline, :success, project: project, builds: [create(:ee_ci_build, :license_scan_v2_1, :success)]) }
let_it_be(:head_pipeline) { create(:ci_pipeline, :success, project: project, builds: [create(:ee_ci_build, :success)]) }
describe '#as_json' do
subject { entity.as_json }
......
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