# frozen_string_literal: true require 'spec_helper' describe API::Vulnerabilities do before do stub_licensed_features(security_dashboard: true) end let_it_be(:project) { create(:project, :with_vulnerabilities) } let_it_be(:user) { create(:user) } let_it_be(:vulnerability) { project.vulnerabilities.first } let(:vulnerability_id) { vulnerability.id } shared_examples 'forbids actions on vulnerability in case of disabled features' do context 'when "first-class vulnerabilities" feature is disabled' do before do stub_feature_flags(first_class_vulnerabilities: false) end it 'responds with "not found"' do subject expect(response).to have_gitlab_http_status(404) end end context 'when security dashboard feature is not available' do before do stub_licensed_features(security_dashboard: false) end it 'responds with 403 Forbidden' do subject expect(response).to have_gitlab_http_status(403) end end end shared_examples 'responds with "not found" for an unknown vulnerability ID' do let(:vulnerability_id) { 0 } it do subject expect(response).to have_gitlab_http_status(404) end end describe 'GET /projects/:id/vulnerabilities' do let(:project_vulnerabilities_path) { "/projects/#{project.id}/vulnerabilities" } subject(:get_vulnerabilities) { get api(project_vulnerabilities_path, user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end it 'returns all vulnerabilities of a project' do get_vulnerabilities expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(response).to match_response_schema('vulnerability_list', dir: 'ee') expect(response.headers['X-Total']).to eq project.vulnerabilities.count.to_s end context 'with pagination' do let(:project_vulnerabilities_path) { "#{super()}?page=2&per_page=1" } it 'paginates the vulnerabilities according to the pagination params' do get_vulnerabilities expect(response).to have_gitlab_http_status(200) expect(json_response.map { |v| v['id'] }).to contain_exactly(project.vulnerabilities.second.id) end end it_behaves_like 'forbids actions on vulnerability in case of disabled features' end it_behaves_like 'responds with "not found" when there is no access to the project' it_behaves_like 'prevents working with vulnerabilities in case of insufficient access level' end shared_examples 'prevents working with vulnerabilities for anonymous users' do it do subject expect(response).to have_gitlab_http_status(403) end end describe 'GET /vulnerabilities/:id' do subject(:get_vulnerability) { get api("/vulnerabilities/#{vulnerability_id}", user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end it 'returns the desired vulnerability' do get_vulnerability expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('vulnerability', dir: 'ee') expect(json_response['id']).to eq vulnerability_id end it_behaves_like 'responds with "not found" for an unknown vulnerability ID' it_behaves_like 'forbids actions on vulnerability in case of disabled features' end it_behaves_like 'prevents working with vulnerabilities in case of insufficient access level' it_behaves_like 'prevents working with vulnerabilities for anonymous users' end describe 'POST /vulnerabilities/:id/dismiss' do before do create_list(:vulnerabilities_occurrence, 2, vulnerability: vulnerability, project: vulnerability.project) end subject(:dismiss_vulnerability) { post api("/vulnerabilities/#{vulnerability_id}/dismiss", user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end it 'dismisses a vulnerability and its associated findings' do Timecop.freeze do dismiss_vulnerability expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('vulnerability', dir: 'ee') expect(vulnerability.reload).to( have_attributes(state: 'closed', closed_by: user, closed_at: be_like_time(Time.zone.now))) expect(vulnerability.findings).to all have_vulnerability_dismissal_feedback end end it_behaves_like 'responds with "not found" for an unknown vulnerability ID' context 'when there is a dismissal error' do before do Grape::Endpoint.before_each do |endpoint| allow(endpoint).to receive(:find_vulnerability!).and_wrap_original do |method, *args| vulnerability = method.call(*args) errors = ActiveModel::Errors.new(vulnerability) errors.add(:base, 'something went wrong') allow(vulnerability).to receive(:valid?).and_return(false) allow(vulnerability).to receive(:errors).and_return(errors) vulnerability end end end after do # resetting according to the https://github.com/ruby-grape/grape#stubbing-helpers Grape::Endpoint.before_each nil end it 'responds with error' do dismiss_vulnerability expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('base' => ['something went wrong']) end end context 'if a vulnerability is already dismissed' do let(:vulnerability) { create(:vulnerability, :closed, project: project) } it 'responds with 304 Not Modified' do dismiss_vulnerability expect(response).to have_gitlab_http_status(304) end end it_behaves_like 'forbids actions on vulnerability in case of disabled features' end it_behaves_like 'prevents working with vulnerabilities in case of insufficient access level' it_behaves_like 'prevents working with vulnerabilities for anonymous users' end describe 'POST /vulnerabilities/:id/resolve' do before do create_list(:vulnerabilities_finding, 2, vulnerability: vulnerability) end subject(:resolve_vulnerability) { post api("/vulnerabilities/#{vulnerability_id}/resolve", user) } context 'with an authorized user with proper permissions' do before do project.add_developer(user) end it 'resolves a vulnerability and its associated findings' do Timecop.freeze do resolve_vulnerability expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('vulnerability', dir: 'ee') expect(vulnerability.reload).to( have_attributes(state: 'closed', closed_by: user, closed_at: be_like_time(Time.zone.now))) expect(vulnerability.findings).to all have_attributes(state: 'resolved') end end it_behaves_like 'responds with "not found" for an unknown vulnerability ID' context 'when the vulnerability is already resolved' do let(:vulnerability) { create(:vulnerability, :closed, project: project) } it 'responds with 304 Not Modified response' do resolve_vulnerability expect(response).to have_gitlab_http_status(304) end end it_behaves_like 'forbids actions on vulnerability in case of disabled features' end it_behaves_like 'prevents working with vulnerabilities in case of insufficient access level' it_behaves_like 'prevents working with vulnerabilities for anonymous users' end end