# frozen_string_literal: true require 'spec_helper' describe API::VulnerabilityExports do include AccessMatchersForRequest before do stub_licensed_features(security_dashboard: true) end let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :with_vulnerability) } describe 'POST /security/projects/:id/vulnerability_exports' do let(:format) { 'csv' } let(:request_path) { "/security/projects/#{project.id}/vulnerability_exports" } subject(:create_vulnerability_export) { post api(request_path, user), params: { export_format: format } } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end context 'when format is csv' do it 'returns information about new vulnerability export' do create_vulnerability_export expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee') end it 'schedules job for export' do expect(::VulnerabilityExports::ExportWorker).to receive(:perform_async).with(anything) create_vulnerability_export end end context 'when format is invalid' do let(:format) { 'invalid' } it 'returns error message' do create_vulnerability_export expect(response).to have_gitlab_http_status(:bad_request) expect(json_response).to eq('error' => 'export_format does not have a valid value') end it 'does not schedule a job for export' do expect(::VulnerabilityExports::ExportWorker).not_to receive(:perform_async) create_vulnerability_export end end it_behaves_like 'forbids access to vulnerability API endpoint in case of disabled features' end describe 'permissions' do it { expect { create_vulnerability_export }.to be_allowed_for(:admin) } it { expect { create_vulnerability_export }.to be_allowed_for(:owner).of(project) } it { expect { create_vulnerability_export }.to be_allowed_for(:maintainer).of(project) } it { expect { create_vulnerability_export }.to be_allowed_for(:developer).of(project) } it { expect { create_vulnerability_export }.to be_allowed_for(:auditor) } it { expect { create_vulnerability_export }.to be_denied_for(:reporter).of(project) } it { expect { create_vulnerability_export }.to be_denied_for(:guest).of(project) } it { expect { create_vulnerability_export }.to be_denied_for(:anonymous) } end end describe 'POST /security/vulnerability_exports' do let(:format) { 'csv' } let(:request_path) { "/security/vulnerability_exports" } subject(:create_vulnerability_export) { post api(request_path, user), params: { export_format: format } } context 'when the request does not fulfill the requirements' do let(:format) { 'exif' } it 'responds with bad_request' do create_vulnerability_export expect(response).to have_gitlab_http_status(:bad_request) expect(json_response).to eq('error' => 'export_format does not have a valid value') end end context 'when the request fulfills the requirements' do let(:mock_service_object) { instance_double(VulnerabilityExports::CreateService, execute: vulnerability_export) } before do allow(VulnerabilityExports::CreateService).to receive(:new).and_return(mock_service_object) end context 'when the export creation succeeds' do let(:vulnerability_export) { create(:vulnerability_export) } it 'returns information about new vulnerability export' do create_vulnerability_export expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee') end end context 'when the export creation fails' do let(:errors) { instance_double(ActiveModel::Errors, any?: true, messages: ['foo']) } let(:vulnerability_export) { instance_double(Vulnerabilities::Export, persisted?: false, errors: errors) } it 'returns the error message' do create_vulnerability_export expect(response).to have_gitlab_http_status(:bad_request) expect(json_response).to eq('message' => ['foo']) end end end end describe 'GET /security/vulnerability_exports/:id' do let_it_be(:vulnerability_export) { create(:vulnerability_export, :finished, :csv, :with_csv_file, project: project, author: user) } let(:request_path) { "/security/vulnerability_exports/#{vulnerability_export.id}" } subject(:get_vulnerability_export) { get api(request_path, user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end context 'when export is finished' do it 'returns information about vulnerability export' do get_vulnerability_export expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee') expect(json_response['id']).to eq vulnerability_export.id end it 'does not return Poll-Interval header' do get_vulnerability_export expect(response.headers['Poll-Interval']).to be_blank end end context 'when export is running' do let_it_be(:vulnerability_export) { create(:vulnerability_export, :running, :csv, project: project, author: user) } it 'returns information about vulnerability export' do get_vulnerability_export expect(response).to have_gitlab_http_status(:accepted) expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee') expect(json_response['id']).to eq vulnerability_export.id end it 'returns Poll-Interval header with value set to 5 seconds' do get_vulnerability_export expect(response.headers['Poll-Interval']).to eq '5000' end end end describe 'permissions' do context 'for export author' do before do project.add_developer(user) end it { expect { get_vulnerability_export }.to be_allowed_for(user) } end it { expect { get_vulnerability_export }.to be_denied_for(:admin) } it { expect { get_vulnerability_export }.to be_denied_for(:owner).of(project) } it { expect { get_vulnerability_export }.to be_denied_for(:maintainer).of(project) } it { expect { get_vulnerability_export }.to be_denied_for(:developer).of(project) } it { expect { get_vulnerability_export }.to be_denied_for(:auditor) } it { expect { get_vulnerability_export }.to be_denied_for(:reporter).of(project) } it { expect { get_vulnerability_export }.to be_denied_for(:guest).of(project) } it { expect { get_vulnerability_export }.to be_denied_for(:anonymous) } end end describe 'GET /security/vulnerability_exports/:id/download' do let!(:vulnerability_export) { create(:vulnerability_export, :finished, :csv, :with_csv_file, project: project, author: user) } let(:request_path) { "/security/vulnerability_exports/#{vulnerability_export.id}/download" } subject(:download_vulnerability_export) { get api(request_path, user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end context 'when export is running' do let!(:vulnerability_export) { create(:vulnerability_export, :running, :csv, project: project, author: user) } it 'renders 404' do download_vulnerability_export expect(response).to have_gitlab_http_status(:not_found) expect(json_response).to eq('message' => '404 Vulnerability Export Not Found') end end context 'when export is failed' do let!(:vulnerability_export) { create(:vulnerability_export, :failed, :csv, project: project, author: user) } it 'renders 404' do download_vulnerability_export expect(response).to have_gitlab_http_status(:not_found) expect(json_response).to eq('message' => '404 Vulnerability Export Not Found') end end context 'when export is finished' do it 'renders 200 with CSV file' do download_vulnerability_export expect(response).to have_gitlab_http_status(:ok) expect(response.body).to include 'Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE' expect(response.headers['Poll-Interval']).to be_blank end end end describe 'permissions' do context 'for export author' do before do project.add_developer(user) end it { expect { download_vulnerability_export }.to be_allowed_for(user) } end it { expect { download_vulnerability_export }.to be_denied_for(:admin) } it { expect { download_vulnerability_export }.to be_denied_for(:owner).of(project) } it { expect { download_vulnerability_export }.to be_denied_for(:maintainer).of(project) } it { expect { download_vulnerability_export }.to be_denied_for(:developer).of(project) } it { expect { download_vulnerability_export }.to be_denied_for(:auditor) } it { expect { download_vulnerability_export }.to be_denied_for(:reporter).of(project) } it { expect { download_vulnerability_export }.to be_denied_for(:guest).of(project) } it { expect { download_vulnerability_export }.to be_denied_for(:anonymous) } end end end