diff options
Diffstat (limited to 'spec/requests/api/alert_management_alerts_spec.rb')
-rw-r--r-- | spec/requests/api/alert_management_alerts_spec.rb | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/spec/requests/api/alert_management_alerts_spec.rb b/spec/requests/api/alert_management_alerts_spec.rb new file mode 100644 index 00000000000..99293e5ae95 --- /dev/null +++ b/spec/requests/api/alert_management_alerts_spec.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::AlertManagementAlerts do + let_it_be(:creator) { create(:user) } + let_it_be(:project) do + create(:project, :public, creator_id: creator.id, namespace: creator.namespace) + end + + let_it_be(:user) { create(:user) } + let_it_be(:alert) { create(:alert_management_alert, project: project) } + + describe 'PUT /projects/:id/alert_management_alerts/:alert_iid/metric_images/authorize' do + include_context 'workhorse headers' + + before do + project.add_developer(user) + end + + subject do + post api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/authorize", user), + headers: workhorse_headers + end + + it 'authorizes uploading with workhorse header' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + + it 'rejects requests that bypassed gitlab-workhorse' do + workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) + + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + + context 'when using remote storage' do + context 'when direct upload is enabled' do + before do + stub_uploads_object_storage(MetricImageUploader, enabled: true, direct_upload: true) + end + + it 'responds with status 200, location of file remote store and object details' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response).not_to have_key('TempPath') + expect(json_response['RemoteObject']).to have_key('ID') + expect(json_response['RemoteObject']).to have_key('GetURL') + expect(json_response['RemoteObject']).to have_key('StoreURL') + expect(json_response['RemoteObject']).to have_key('DeleteURL') + expect(json_response['RemoteObject']).to have_key('MultipartUpload') + end + end + + context 'when direct upload is disabled' do + before do + stub_uploads_object_storage(MetricImageUploader, enabled: true, direct_upload: false) + end + + it 'handles as a local file' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response['TempPath']).to eq(MetricImageUploader.workhorse_local_upload_path) + expect(json_response['RemoteObject']).to be_nil + end + end + end + end + + describe 'POST /projects/:id/alert_management_alerts/:alert_iid/metric_images' do + include WorkhorseHelpers + using RSpec::Parameterized::TableSyntax + + include_context 'workhorse headers' + + let(:file) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:file_name) { 'rails_sample.jpg' } + let(:url) { 'http://gitlab.com' } + let(:url_text) { 'GitLab' } + + let(:params) { { url: url, url_text: url_text } } + + subject do + workhorse_finalize( + api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images", user), + method: :post, + file_key: :file, + params: params.merge(file: file), + headers: workhorse_headers, + send_rewritten_field: true + ) + end + + shared_examples 'can_upload_metric_image' do + it 'creates a new metric image' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['filename']).to eq(file_name) + expect(json_response['url']).to eq(url) + expect(json_response['url_text']).to eq(url_text) + expect(json_response['created_at']).not_to be_nil + expect(json_response['id']).not_to be_nil + file_path_regex = %r{/uploads/-/system/alert_management_metric_image/file/\d+/#{file_name}} + expect(json_response['file_path']).to match(file_path_regex) + end + end + + shared_examples 'unauthorized_upload' do + it 'disallows the upload' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden') + end + end + + where(:user_role, :expected_status) do + :guest | :unauthorized_upload + :reporter | :unauthorized_upload + :developer | :can_upload_metric_image + end + + with_them do + before do + # Local storage + stub_uploads_object_storage(MetricImageUploader, enabled: false) + allow_next_instance_of(MetricImageUploader) do |uploader| + allow(uploader).to receive(:file_storage?).and_return(true) + end + + project.send("add_#{user_role}", user) + end + + it_behaves_like "#{params[:expected_status]}" + end + + context 'file size too large' do + before do + allow_next_instance_of(UploadedFile) do |upload_file| + allow(upload_file).to receive(:size).and_return(AlertManagement::MetricImage::MAX_FILE_SIZE + 1) + end + end + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.body).to match(/File is too large/) + end + end + + context 'error when saving' do + before do + project.add_developer(user) + + allow_next_instance_of(::AlertManagement::MetricImages::UploadService) do |service| + error = instance_double(ServiceResponse, success?: false, message: 'some error', http_status: :bad_request) + allow(service).to receive(:execute).and_return(error) + end + end + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.body).to match(/some error/) + end + end + + context 'object storage enabled' do + before do + # Object storage + stub_uploads_object_storage(MetricImageUploader) + + allow_next_instance_of(MetricImageUploader) do |uploader| + allow(uploader).to receive(:file_storage?).and_return(true) + end + project.add_developer(user) + end + + it_behaves_like 'can_upload_metric_image' + + it 'uploads to remote storage' do + subject + + last_upload = AlertManagement::MetricImage.last.uploads.last + expect(last_upload.store).to eq(::ObjectStorage::Store::REMOTE) + end + end + end + + describe 'GET /projects/:id/alert_management_alerts/:alert_iid/metric_images' do + using RSpec::Parameterized::TableSyntax + + let!(:image) { create(:alert_metric_image, alert: alert) } + + subject { get api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images", user) } + + shared_examples 'can_read_metric_image' do + it 'can read the metric images' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first).to match( + { + id: image.id, + created_at: image.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + filename: image.filename, + file_path: image.file_path, + url: image.url, + url_text: nil + }.with_indifferent_access + ) + end + end + + shared_examples 'unauthorized_read' do + it 'cannot read the metric images' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + where(:user_role, :public_project, :expected_status) do + :not_member | false | :unauthorized_read + :not_member | true | :unauthorized_read + :guest | false | :unauthorized_read + :reporter | false | :unauthorized_read + :developer | false | :can_read_metric_image + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :not_member + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project + end + + it_behaves_like "#{params[:expected_status]}" + end + end + + describe 'PUT /projects/:id/alert_management_alerts/:alert_iid/metric_images/:metric_image_id' do + using RSpec::Parameterized::TableSyntax + + let!(:image) { create(:alert_metric_image, alert: alert) } + let(:params) { { url: 'http://test.example.com', url_text: 'Example website 123' } } + + subject do + put api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{image.id}", user), + params: params + end + + shared_examples 'can_update_metric_image' do + it 'can update the metric images' do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['url']).to eq(params[:url]) + expect(json_response['url_text']).to eq(params[:url_text]) + end + end + + shared_examples 'unauthorized_update' do + it 'cannot update the metric image' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + expect(image.reload).to eq(image) + end + end + + where(:user_role, :public_project, :expected_status) do + :not_member | false | :unauthorized_update + :not_member | true | :unauthorized_update + :guest | false | :unauthorized_update + :reporter | false | :unauthorized_update + :developer | false | :can_update_metric_image + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :not_member + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project + end + + it_behaves_like "#{params[:expected_status]}" + end + + context 'when user has access' do + before do + project.add_developer(user) + end + + context 'and metric image not found' do + subject do + put api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength + end + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('Metric image not found') + end + end + + context 'metric image cannot be updated' do + let(:params) { { url_text: 'something_long' * 100 } } + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('Metric image could not be updated') + end + end + end + end + + describe 'DELETE /projects/:id/alert_management_alerts/:alert_iid/metric_images/:metric_image_id' do + using RSpec::Parameterized::TableSyntax + + let!(:image) { create(:alert_metric_image, alert: alert) } + + subject do + delete api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{image.id}", user) + end + + shared_examples 'can delete metric image successfully' do + it 'can delete the metric images' do + subject + + expect(response).to have_gitlab_http_status(:no_content) + expect { image.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + shared_examples 'unauthorized delete' do + it 'cannot delete the metric image' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + expect(image.reload).to eq(image) + end + end + + where(:user_role, :public_project, :expected_status) do + :not_member | false | 'unauthorized delete' + :not_member | true | 'unauthorized delete' + :guest | false | 'unauthorized delete' + :reporter | false | 'unauthorized delete' + :developer | false | 'can delete metric image successfully' + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :not_member + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project + end + + it_behaves_like "#{params[:expected_status]}" + end + + context 'when user has access' do + before do + project.add_developer(user) + end + + context 'when metric image not found' do + subject do + delete api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength + end + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('Metric image not found') + end + end + + context 'when error when deleting' do + before do + allow_next_instance_of(AlertManagement::AlertsFinder) do |finder| + allow(finder).to receive(:execute).and_return([alert]) + end + + allow(alert).to receive_message_chain('metric_images.find_by_id') { image } + allow(image).to receive(:destroy).and_return(false) + end + + it 'returns an error' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('Metric image could not be deleted') + end + end + end + end +end |