Commit f11049ab authored by Sean McGivern's avatar Sean McGivern

Merge branch 'api-delete-respect-headers' into 'master'

API: Respect the 'If-Unmodified-Since' for delete endpoints

See merge request !9621
parents a16854ff ee4820a5
---
title: 'API: Respect the "If-Unmodified-Since" header when delting a resource'
merge_request: 9621
author: Robert Schilling
type: added
...@@ -263,6 +263,7 @@ The following table shows the possible return codes for API requests. ...@@ -263,6 +263,7 @@ The following table shows the possible return codes for API requests.
| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. | | `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
| `405 Method Not Allowed` | The request is not supported. | | `405 Method Not Allowed` | The request is not supported. |
| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. | | `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
| `412` | Indicates the request was denied. May happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. |
| `422 Unprocessable` | The entity could not be processed. | | `422 Unprocessable` | The entity could not be processed. |
| `500 Server Error` | While handling the request something went wrong server-side. | | `500 Server Error` | While handling the request something went wrong server-side. |
......
...@@ -67,12 +67,14 @@ module API ...@@ -67,12 +67,14 @@ module API
end end
delete ":id/access_requests/:user_id" do delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
member = source.requesters.find_by!(user_id: params[:user_id])
status 204 destroy_conditionally!(member) do
::Members::DestroyService.new(source, current_user, params) ::Members::DestroyService.new(source, current_user, params)
.execute(:requesters) .execute(:requesters)
end end
end end
end end
end end
end
end end
...@@ -88,8 +88,7 @@ module API ...@@ -88,8 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin? unauthorized! unless award.user == current_user || current_user.admin?
status 204 destroy_conditionally!(award)
award.destroy
end end
end end
end end
......
...@@ -122,11 +122,10 @@ module API ...@@ -122,11 +122,10 @@ module API
end end
delete "/lists/:list_id" do delete "/lists/:list_id" do
authorize!(:admin_list, user_project) authorize!(:admin_list, user_project)
list = board_lists.find(params[:list_id]) list = board_lists.find(params[:list_id])
destroy_conditionally!(list) do |list|
service = ::Boards::Lists::DestroyService.new(user_project, current_user) service = ::Boards::Lists::DestroyService.new(user_project, current_user)
unless service.execute(list) unless service.execute(list)
render_api_error!({ error: 'List could not be deleted!' }, 400) render_api_error!({ error: 'List could not be deleted!' }, 400)
end end
...@@ -134,4 +133,5 @@ module API ...@@ -134,4 +133,5 @@ module API
end end
end end
end end
end
end end
...@@ -125,6 +125,12 @@ module API ...@@ -125,6 +125,12 @@ module API
delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project authorize_push_project
branch = user_project.repository.find_branch(params[:branch])
not_found!('Branch') unless branch
commit = user_project.repository.commit(branch.dereferenced_target)
destroy_conditionally!(commit, last_update_field: :authored_date) do
result = DeleteBranchService.new(user_project, current_user) result = DeleteBranchService.new(user_project, current_user)
.execute(params[:branch]) .execute(params[:branch])
...@@ -132,6 +138,7 @@ module API ...@@ -132,6 +138,7 @@ module API
render_api_error!(result[:message], result[:return_code]) render_api_error!(result[:message], result[:return_code])
end end
end end
end
desc 'Delete all merged branches' desc 'Delete all merged branches'
delete ':id/repository/merged_branches' do delete ':id/repository/merged_branches' do
......
...@@ -91,8 +91,7 @@ module API ...@@ -91,8 +91,7 @@ module API
delete ':id' do delete ':id' do
message = find_message message = find_message
status 204 destroy_conditionally!(message)
message.destroy
end end
end end
end end
......
...@@ -125,8 +125,7 @@ module API ...@@ -125,8 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id]) key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key not_found!('Deploy Key') unless key
status 204 destroy_conditionally!(key)
key.destroy
end end
end end
end end
......
...@@ -79,8 +79,7 @@ module API ...@@ -79,8 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id]) environment = user_project.environments.find(params[:environment_id])
status 204 destroy_conditionally!(environment)
environment.destroy
end end
desc 'Stops an existing environment' do desc 'Stops an existing environment' do
......
...@@ -88,8 +88,7 @@ module API ...@@ -88,8 +88,7 @@ module API
variable = user_group.variables.find_by(key: params[:key]) variable = user_group.variables.find_by(key: params[:key])
not_found!('GroupVariable') unless variable not_found!('GroupVariable') unless variable
status 204 destroy_conditionally!(variable)
variable.destroy
end end
end end
end end
......
...@@ -118,9 +118,10 @@ module API ...@@ -118,9 +118,10 @@ module API
group = find_group!(params[:id]) group = find_group!(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
status 204 destroy_conditionally!(group) do |group|
::Groups::DestroyService.new(group, current_user).execute ::Groups::DestroyService.new(group, current_user).execute
end end
end
desc 'Get a list of projects in this group.' do desc 'Get a list of projects in this group.' do
success Entities::Project success Entities::Project
......
...@@ -11,6 +11,25 @@ module API ...@@ -11,6 +11,25 @@ module API
declared(params, options).to_h.symbolize_keys declared(params, options).to_h.symbolize_keys
end end
def check_unmodified_since!(last_modified)
if_unmodified_since = Time.parse(headers['If-Unmodified-Since']) rescue nil
if if_unmodified_since && last_modified && last_modified > if_unmodified_since
render_api_error!('412 Precondition Failed', 412)
end
end
def destroy_conditionally!(resource, last_update_field: :updated_at)
check_unmodified_since!(resource.public_send(last_update_field))
status 204
if block_given?
yield resource
else
resource.destroy
end
end
def current_user def current_user
return @current_user if defined?(@current_user) return @current_user if defined?(@current_user)
......
...@@ -230,8 +230,8 @@ module API ...@@ -230,8 +230,8 @@ module API
not_found!('Issue') unless issue not_found!('Issue') unless issue
authorize!(:destroy_issue, issue) authorize!(:destroy_issue, issue)
status 204
issue.destroy destroy_conditionally!(issue)
end end
desc 'List merge requests closing issue' do desc 'List merge requests closing issue' do
......
...@@ -56,8 +56,7 @@ module API ...@@ -56,8 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name]) label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label not_found!('Label') unless label
status 204 destroy_conditionally!(label)
label.destroy
end end
desc 'Update an existing label. At least one optional parameter is required.' do desc 'Update an existing label. At least one optional parameter is required.' do
......
...@@ -93,13 +93,13 @@ module API ...@@ -93,13 +93,13 @@ module API
end end
delete ":id/members/:user_id" do delete ":id/members/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
# Ensure that memeber exists member = source.members.find_by!(user_id: params[:user_id])
source.members.find_by!(user_id: params[:user_id])
status 204 destroy_conditionally!(member) do
::Members::DestroyService.new(source, current_user, declared_params).execute ::Members::DestroyService.new(source, current_user, declared_params).execute
end end
end end
end end
end end
end
end end
...@@ -164,8 +164,8 @@ module API ...@@ -164,8 +164,8 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request) authorize!(:destroy_merge_request, merge_request)
status 204
merge_request.destroy destroy_conditionally!(merge_request)
end end
params do params do
......
...@@ -129,13 +129,15 @@ module API ...@@ -129,13 +129,15 @@ module API
end end
delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
note = user_project.notes.find(params[:note_id]) note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note authorize! :admin_note, note
status 204 destroy_conditionally!(note) do |note|
::Notes::DestroyService.new(user_project, current_user).execute(note) ::Notes::DestroyService.new(user_project, current_user).execute(note)
end end
end end
end end
end
helpers do helpers do
def find_project_noteable(noteables_str, noteable_id) def find_project_noteable(noteables_str, noteable_id)
......
...@@ -117,8 +117,7 @@ module API ...@@ -117,8 +117,7 @@ module API
not_found!('PipelineSchedule') unless pipeline_schedule not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :admin_pipeline_schedule, pipeline_schedule authorize! :admin_pipeline_schedule, pipeline_schedule
status :accepted destroy_conditionally!(pipeline_schedule)
present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
end end
end end
......
...@@ -96,8 +96,7 @@ module API ...@@ -96,8 +96,7 @@ module API
delete ":id/hooks/:hook_id" do delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id)) hook = user_project.hooks.find(params.delete(:hook_id))
status 204 destroy_conditionally!(hook)
hook.destroy
end end
end end
end end
......
...@@ -116,8 +116,8 @@ module API ...@@ -116,8 +116,8 @@ module API
not_found!('Snippet') unless snippet not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet authorize! :admin_project_snippet, snippet
status 204
snippet.destroy destroy_conditionally!(snippet)
end end
desc 'Get a raw project snippet' desc 'Get a raw project snippet'
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
module API module API
# Projects API
class Projects < Grape::API class Projects < Grape::API
include PaginationParams include PaginationParams
...@@ -334,7 +333,10 @@ module API ...@@ -334,7 +333,10 @@ module API
desc 'Remove a project' desc 'Remove a project'
delete ":id" do delete ":id" do
authorize! :remove_project, user_project authorize! :remove_project, user_project
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end
accepted! accepted!
end end
...@@ -363,8 +365,7 @@ module API ...@@ -363,8 +365,7 @@ module API
authorize! :remove_fork_project, user_project authorize! :remove_fork_project, user_project
if user_project.forked? if user_project.forked?
status 204 destroy_conditionally!(user_project.forked_project_link)
user_project.forked_project_link.destroy
else else
not_modified! not_modified!
end end
...@@ -408,8 +409,7 @@ module API ...@@ -408,8 +409,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id]) link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link not_found!('Group Link') unless link
status 204 destroy_conditionally!(link)
link.destroy
end end
desc 'Upload a file' desc 'Upload a file'
......
...@@ -76,9 +76,7 @@ module API ...@@ -76,9 +76,7 @@ module API
delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
protected_branch = user_project.protected_branches.find_by!(name: params[:name]) protected_branch = user_project.protected_branches.find_by!(name: params[:name])
protected_branch.destroy destroy_conditionally!(protected_branch)
status 204
end end
end end
end end
......
...@@ -45,8 +45,10 @@ module API ...@@ -45,8 +45,10 @@ module API
end end
delete '/' do delete '/' do
authenticate_runner! authenticate_runner!
status 204
Ci::Runner.find_by_token(params[:token]).destroy runner = Ci::Runner.find_by_token(params[:token])
destroy_conditionally!(runner)
end end
desc 'Validates authentication credentials' do desc 'Validates authentication credentials' do
......
...@@ -77,10 +77,10 @@ module API ...@@ -77,10 +77,10 @@ module API
end end
delete ':id' do delete ':id' do
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_delete_runner!(runner) authenticate_delete_runner!(runner)
status 204 destroy_conditionally!(runner)
runner.destroy!
end end
end end
...@@ -135,8 +135,7 @@ module API ...@@ -135,8 +135,7 @@ module API
runner = runner_project.runner runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
status 204 destroy_conditionally!(runner_project)
runner_project.destroy
end end
end end
......
...@@ -656,6 +656,7 @@ module API ...@@ -656,6 +656,7 @@ module API
delete ":id/services/:service_slug" do delete ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore) service = user_project.find_or_initialize_service(params[:service_slug].underscore)
destroy_conditionally!(service) do
attrs = service_attributes(service).inject({}) do |hash, key| attrs = service_attributes(service).inject({}) do |hash, key|
hash.merge!(key => nil) hash.merge!(key => nil)
end end
...@@ -664,6 +665,7 @@ module API ...@@ -664,6 +665,7 @@ module API
render_api_error!('400 Bad Request', 400) render_api_error!('400 Bad Request', 400)
end end
end end
end
desc 'Get the service settings for project' do desc 'Get the service settings for project' do
success Entities::ProjectService success Entities::ProjectService
......
...@@ -123,8 +123,7 @@ module API ...@@ -123,8 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet authorize! :destroy_personal_snippet, snippet
status 204 destroy_conditionally!(snippet)
snippet.destroy
end end
desc 'Get a raw snippet' do desc 'Get a raw snippet' do
......
...@@ -66,8 +66,7 @@ module API ...@@ -66,8 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id]) hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook not_found!('System hook') unless hook
status 204 destroy_conditionally!(hook)
hook.destroy
end end
end end
end end
......
...@@ -65,6 +65,12 @@ module API ...@@ -65,6 +65,12 @@ module API
delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project authorize_push_project
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
commit = user_project.repository.commit(tag.dereferenced_target)
destroy_conditionally!(commit, last_update_field: :authored_date) do
result = ::Tags::DestroyService.new(user_project, current_user) result = ::Tags::DestroyService.new(user_project, current_user)
.execute(params[:tag_name]) .execute(params[:tag_name])
...@@ -72,6 +78,7 @@ module API ...@@ -72,6 +78,7 @@ module API
render_api_error!(result[:message], result[:return_code]) render_api_error!(result[:message], result[:return_code])
end end
end end
end
desc 'Add a release note to a tag' do desc 'Add a release note to a tag' do
success Entities::Release success Entities::Release
......
...@@ -140,8 +140,7 @@ module API ...@@ -140,8 +140,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger return not_found!('Trigger') unless trigger
status 204 destroy_conditionally!(trigger)
trigger.destroy
end end
end end
end end
......
...@@ -230,8 +230,7 @@ module API ...@@ -230,8 +230,7 @@ module API
key = user.keys.find_by(id: params[:key_id]) key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
status 204 destroy_conditionally!(key)
key.destroy
end end
desc 'Add an email address to a specified user. Available only for admins.' do desc 'Add an email address to a specified user. Available only for admins.' do
...@@ -287,7 +286,11 @@ module API ...@@ -287,7 +286,11 @@ module API
email = user.emails.find_by(id: params[:email_id]) email = user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email not_found!('Email') unless email
Emails::DestroyService.new(user, email: email.email).execute destroy_conditionally!(email) do |email|
Emails::DestroyService.new(current_user, email: email.email).execute
end
user.update_secondary_emails!
end end
desc 'Delete a user. Available only for admins.' do desc 'Delete a user. Available only for admins.' do
...@@ -299,12 +302,14 @@ module API ...@@ -299,12 +302,14 @@ module API
end end
delete ":id" do delete ":id" do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
status 204 destroy_conditionally!(user) do
user.delete_async(deleted_by: current_user, params: params) user.delete_async(deleted_by: current_user, params: params)
end end
end
desc 'Block a user. Available only for admins.' desc 'Block a user. Available only for admins.'
params do params do
...@@ -403,8 +408,11 @@ module API ...@@ -403,8 +408,11 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token' requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end end
delete ':impersonation_token_id' do delete ':impersonation_token_id' do
status 204 token = find_impersonation_token
find_impersonation_token.revoke!
destroy_conditionally!(token) do
token.revoke!
end
end end
end end
end end
...@@ -481,8 +489,7 @@ module API ...@@ -481,8 +489,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id]) key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
status 204 destroy_conditionally!(key)
key.destroy
end end
desc "Get the currently authenticated user's email addresses" do desc "Get the currently authenticated user's email addresses" do
...@@ -533,10 +540,13 @@ module API ...@@ -533,10 +540,13 @@ module API
email = current_user.emails.find_by(id: params[:email_id]) email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email not_found!('Email') unless email
status 204 destroy_conditionally!(email) do |email|
Emails::DestroyService.new(current_user, email: email.email).execute Emails::DestroyService.new(current_user, email: email.email).execute
end end
current_user.update_secondary_emails!
end
desc 'Get a list of user activities' desc 'Get a list of user activities'
params do params do
optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY' optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY'
......
...@@ -88,6 +88,7 @@ module API ...@@ -88,6 +88,7 @@ module API
variable = user_project.variables.find_by(key: params[:key]) variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable not_found!('Variable') unless variable
# Variables don't have any timestamp. Therfore, destroy unconditionally.
status 204 status 204
variable.destroy variable.destroy
end end
......
...@@ -253,6 +253,10 @@ describe API::AwardEmoji do ...@@ -253,6 +253,10 @@ describe API::AwardEmoji do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user) }
end
end end
context 'when the awardable is a Merge Request' do context 'when the awardable is a Merge Request' do
...@@ -269,6 +273,10 @@ describe API::AwardEmoji do ...@@ -269,6 +273,10 @@ describe API::AwardEmoji do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user) }
end
end end
context 'when the awardable is a Snippet' do context 'when the awardable is a Snippet' do
...@@ -282,6 +290,10 @@ describe API::AwardEmoji do ...@@ -282,6 +290,10 @@ describe API::AwardEmoji do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { snippet.award_emoji.count }.from(1).to(0) end.to change { snippet.award_emoji.count }.from(1).to(0)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) }
end
end end
end end
...@@ -295,5 +307,9 @@ describe API::AwardEmoji do ...@@ -295,5 +307,9 @@ describe API::AwardEmoji do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { note.award_emoji.count }.from(1).to(0) end.to change { note.award_emoji.count }.from(1).to(0)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user) }
end
end end
end end
...@@ -195,6 +195,10 @@ describe API::Boards do ...@@ -195,6 +195,10 @@ describe API::Boards do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end end
it_behaves_like '412 response' do
let(:request) { api("#{base_url}/#{dev_list.id}", owner) }
end
end end
end end
end end
...@@ -499,6 +499,10 @@ describe API::Branches do ...@@ -499,6 +499,10 @@ describe API::Branches do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/repository/branches/#{branch_name}", user) }
end
end end
describe 'DELETE /projects/:id/repository/merged_branches' do describe 'DELETE /projects/:id/repository/merged_branches' do
......
...@@ -171,6 +171,10 @@ describe API::BroadcastMessages do ...@@ -171,6 +171,10 @@ describe API::BroadcastMessages do
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it_behaves_like '412 response' do
let(:request) { api("/broadcast_messages/#{message.id}", admin) }
end
it 'deletes the broadcast message for admins' do it 'deletes the broadcast message for admins' do
expect do expect do
delete api("/broadcast_messages/#{message.id}", admin) delete api("/broadcast_messages/#{message.id}", admin)
......
...@@ -190,6 +190,10 @@ describe API::DeployKeys do ...@@ -190,6 +190,10 @@ describe API::DeployKeys do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) }
end
end end
describe 'POST /projects/:id/deploy_keys/:key_id/enable' do describe 'POST /projects/:id/deploy_keys/:key_id/enable' do
......
...@@ -138,6 +138,10 @@ describe API::Environments do ...@@ -138,6 +138,10 @@ describe API::Environments do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found') expect(json_response['message']).to eq('404 Not found')
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/environments/#{environment.id}", user) }
end
end end
context 'a non member' do context 'a non member' do
......
...@@ -200,6 +200,10 @@ describe API::GroupVariables do ...@@ -200,6 +200,10 @@ describe API::GroupVariables do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/groups/#{group.id}/variables/#{variable.key}", user) }
end
end end
context 'authorized user with invalid permissions' do context 'authorized user with invalid permissions' do
......
...@@ -488,6 +488,10 @@ describe API::Groups do ...@@ -488,6 +488,10 @@ describe API::Groups do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end end
it_behaves_like '412 response' do
let(:request) { api("/groups/#{group1.id}", user1) }
end
it "does not remove a group if not an owner" do it "does not remove a group if not an owner" do
user4 = create(:user) user4 = create(:user)
group1.add_master(user4) group1.add_master(user4)
......
...@@ -1304,6 +1304,10 @@ describe API::Issues, :mailer do ...@@ -1304,6 +1304,10 @@ describe API::Issues, :mailer do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) }
end
end end
context 'when issue does not exist' do context 'when issue does not exist' do
......
...@@ -189,6 +189,11 @@ describe API::Labels do ...@@ -189,6 +189,11 @@ describe API::Labels do
delete api("/projects/#{project.id}/labels", user) delete api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/labels", user) }
let(:params) { { name: 'label1' } }
end
end end
describe 'PUT /projects/:id/labels' do describe 'PUT /projects/:id/labels' do
......
...@@ -284,6 +284,10 @@ describe API::Members do ...@@ -284,6 +284,10 @@ describe API::Members do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { source.members.count }.by(-1) end.to change { source.members.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) }
end
end end
it 'returns 404 if member does not exist' do it 'returns 404 if member does not exist' do
......
...@@ -698,6 +698,10 @@ describe API::MergeRequests do ...@@ -698,6 +698,10 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user) }
end
end end
end end
......
...@@ -390,6 +390,10 @@ describe API::Notes do ...@@ -390,6 +390,10 @@ describe API::Notes do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user) }
end
end end
context 'when noteable is a Snippet' do context 'when noteable is a Snippet' do
...@@ -410,6 +414,10 @@ describe API::Notes do ...@@ -410,6 +414,10 @@ describe API::Notes do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) }
end
end end
context 'when noteable is a Merge Request' do context 'when noteable is a Merge Request' do
...@@ -430,6 +438,10 @@ describe API::Notes do ...@@ -430,6 +438,10 @@ describe API::Notes do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/#{merge_request_note.id}", user) }
end
end end
end end
end end
...@@ -267,8 +267,7 @@ describe API::PipelineSchedules do ...@@ -267,8 +267,7 @@ describe API::PipelineSchedules do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master) delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
end.to change { project.pipeline_schedules.count }.by(-1) end.to change { project.pipeline_schedules.count }.by(-1)
expect(response).to have_http_status(:accepted) expect(response).to have_http_status(204)
expect(response).to match_response_schema('pipeline_schedule')
end end
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
...@@ -276,6 +275,10 @@ describe API::PipelineSchedules do ...@@ -276,6 +275,10 @@ describe API::PipelineSchedules do
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master) }
end
end end
context 'authenticated user with invalid permissions' do context 'authenticated user with invalid permissions' do
......
...@@ -212,5 +212,9 @@ describe API::ProjectHooks, 'ProjectHooks' do ...@@ -212,5 +212,9 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(WebHook.exists?(hook.id)).to be_truthy expect(WebHook.exists?(hook.id)).to be_truthy
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/hooks/#{hook.id}", user) }
end
end end
end end
...@@ -228,9 +228,6 @@ describe API::ProjectSnippets do ...@@ -228,9 +228,6 @@ describe API::ProjectSnippets do
let(:snippet) { create(:project_snippet, author: admin) } let(:snippet) { create(:project_snippet, author: admin) }
it 'deletes snippet' do it 'deletes snippet' do
admin = create(:admin)
snippet = create(:project_snippet, author: admin)
delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
...@@ -242,6 +239,10 @@ describe API::ProjectSnippets do ...@@ -242,6 +239,10 @@ describe API::ProjectSnippets do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found') expect(json_response['message']).to eq('404 Snippet Not Found')
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) }
end
end end
describe 'GET /projects/:project_id/snippets/:id/raw' do describe 'GET /projects/:project_id/snippets/:id/raw' do
......
...@@ -1029,6 +1029,10 @@ describe API::Projects do ...@@ -1029,6 +1029,10 @@ describe API::Projects do
delete api("/projects/#{project.id}/snippets/1234", user) delete api("/projects/#{project.id}/snippets/1234", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}", user) }
end
end end
describe 'GET /projects/:id/snippets/:snippet_id/raw' do describe 'GET /projects/:id/snippets/:snippet_id/raw' do
...@@ -1104,17 +1108,15 @@ describe API::Projects do ...@@ -1104,17 +1108,15 @@ describe API::Projects do
project_fork_target.group.add_developer user2 project_fork_target.group.add_developer user2
end end
it 'is forbidden to non-owner users' do context 'for a forked project' do
delete api("/projects/#{project_fork_target.id}/fork", user2) before do
expect(response).to have_http_status(403)
end
it 'makes forked project unforked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload project_fork_target.reload
expect(project_fork_target.forked_from_project).not_to be_nil expect(project_fork_target.forked_from_project).not_to be_nil
expect(project_fork_target.forked?).to be_truthy expect(project_fork_target.forked?).to be_truthy
end
it 'makes forked project unforked' do
delete api("/projects/#{project_fork_target.id}/fork", admin) delete api("/projects/#{project_fork_target.id}/fork", admin)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
...@@ -1123,6 +1125,16 @@ describe API::Projects do ...@@ -1123,6 +1125,16 @@ describe API::Projects do
expect(project_fork_target.forked?).not_to be_truthy expect(project_fork_target.forked?).not_to be_truthy
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project_fork_target.id}/fork", admin) }
end
end
it 'is forbidden to non-owner users' do
delete api("/projects/#{project_fork_target.id}/fork", user2)
expect(response).to have_http_status(403)
end
it 'is idempotent if not forked' do it 'is idempotent if not forked' do
expect(project_fork_target.forked_from_project).to be_nil expect(project_fork_target.forked_from_project).to be_nil
delete api("/projects/#{project_fork_target.id}/fork", admin) delete api("/projects/#{project_fork_target.id}/fork", admin)
...@@ -1188,16 +1200,25 @@ describe API::Projects do ...@@ -1188,16 +1200,25 @@ describe API::Projects do
end end
describe 'DELETE /projects/:id/share/:group_id' do describe 'DELETE /projects/:id/share/:group_id' do
it 'returns 204 when deleting a group share' do context 'for a valid group' do
group = create(:group, :public) let(:group) { create(:group, :public) }
before do
create(:project_group_link, group: group, project: project) create(:project_group_link, group: group, project: project)
end
it 'returns 204 when deleting a group share' do
delete api("/projects/#{project.id}/share/#{group.id}", user) delete api("/projects/#{project.id}/share/#{group.id}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
expect(project.project_group_links).to be_empty expect(project.project_group_links).to be_empty
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
end
end
it 'returns a 400 when group id is not an integer' do it 'returns a 400 when group id is not an integer' do
delete api("/projects/#{project.id}/share/foo", user) delete api("/projects/#{project.id}/share/foo", user)
...@@ -1519,6 +1540,11 @@ describe API::Projects do ...@@ -1519,6 +1540,11 @@ describe API::Projects do
expect(json_response['message']).to eql('202 Accepted') expect(json_response['message']).to eql('202 Accepted')
end end
it_behaves_like '412 response' do
let(:success_status) { 202 }
let(:request) { api("/projects/#{project.id}", user) }
end
it 'does not remove a project if not an owner' do it 'does not remove a project if not an owner' do
user3 = create(:user) user3 = create(:user)
project.team << [user3, :developer] project.team << [user3, :developer]
...@@ -1549,6 +1575,11 @@ describe API::Projects do ...@@ -1549,6 +1575,11 @@ describe API::Projects do
delete api('/projects/1328', admin) delete api('/projects/1328', admin)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:success_status) { 202 }
let(:request) { api("/projects/#{project.id}", admin) }
end
end end
end end
......
...@@ -213,6 +213,10 @@ describe API::ProtectedBranches do ...@@ -213,6 +213,10 @@ describe API::ProtectedBranches do
expect(response).to have_gitlab_http_status(204) expect(response).to have_gitlab_http_status(204)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/protected_branches/#{branch_name}", user) }
end
it "returns 404 if branch does not exist" do it "returns 404 if branch does not exist" do
delete api("/projects/#{project.id}/protected_branches/barfoo", user) delete api("/projects/#{project.id}/protected_branches/barfoo", user)
......
...@@ -149,6 +149,11 @@ describe API::Runner do ...@@ -149,6 +149,11 @@ describe API::Runner do
expect(response).to have_http_status 204 expect(response).to have_http_status 204
expect(Ci::Runner.count).to eq(0) expect(Ci::Runner.count).to eq(0)
end end
it_behaves_like '412 response' do
let(:request) { api('/runners') }
let(:params) { { token: runner.token } }
end
end end
end end
......
...@@ -279,6 +279,10 @@ describe API::Runners do ...@@ -279,6 +279,10 @@ describe API::Runners do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { Ci::Runner.shared.count }.by(-1) end.to change { Ci::Runner.shared.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/runners/#{shared_runner.id}", admin) }
end
end end
context 'when runner is not shared' do context 'when runner is not shared' do
...@@ -332,6 +336,10 @@ describe API::Runners do ...@@ -332,6 +336,10 @@ describe API::Runners do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1) end.to change { Ci::Runner.specific.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/runners/#{specific_runner.id}", user) }
end
end end
end end
...@@ -463,6 +471,10 @@ describe API::Runners do ...@@ -463,6 +471,10 @@ describe API::Runners do
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { project.runners.count }.by(-1) end.to change { project.runners.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) }
end
end end
context 'when runner have one associated projects' do context 'when runner have one associated projects' do
......
...@@ -270,6 +270,10 @@ describe API::Snippets do ...@@ -270,6 +270,10 @@ describe API::Snippets do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found') expect(json_response['message']).to eq('404 Snippet Not Found')
end end
it_behaves_like '412 response' do
let(:request) { api("/snippets/#{public_snippet.id}", user) }
end
end end
describe "GET /snippets/:id/user_agent_detail" do describe "GET /snippets/:id/user_agent_detail" do
......
...@@ -102,5 +102,9 @@ describe API::SystemHooks do ...@@ -102,5 +102,9 @@ describe API::SystemHooks do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/hooks/#{hook.id}", admin) }
end
end end
end end
...@@ -278,12 +278,16 @@ describe API::Tags do ...@@ -278,12 +278,16 @@ describe API::Tags do
expect(response).to have_gitlab_http_status(204) expect(response).to have_gitlab_http_status(204)
end end
it_behaves_like '412 response' do
let(:request) { api(route, current_user) }
end
context 'when tag does not exist' do context 'when tag does not exist' do
let(:tag_name) { 'unknown' } let(:tag_name) { 'unknown' }
it_behaves_like '404 response' do it_behaves_like '404 response' do
let(:request) { delete api(route, current_user) } let(:request) { delete api(route, current_user) }
let(:message) { 'No such tag' } let(:message) { '404 Tag Not Found' }
end end
end end
......
...@@ -309,6 +309,10 @@ describe API::Triggers do ...@@ -309,6 +309,10 @@ describe API::Triggers do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/triggers/#{trigger.id}", user) }
end
end end
context 'authenticated user with invalid permissions' do context 'authenticated user with invalid permissions' do
......
...@@ -733,6 +733,10 @@ describe API::Users do ...@@ -733,6 +733,10 @@ describe API::Users do
end.to change { user.keys.count }.by(-1) end.to change { user.keys.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/users/#{user.id}/keys/#{key.id}", admin) }
end
it 'returns 404 error if user not found' do it 'returns 404 error if user not found' do
user.keys << key user.keys << key
user.save user.save
...@@ -838,6 +842,10 @@ describe API::Users do ...@@ -838,6 +842,10 @@ describe API::Users do
end.to change { user.emails.count }.by(-1) end.to change { user.emails.count }.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/users/#{user.id}/emails/#{email.id}", admin) }
end
it 'returns 404 error if user not found' do it 'returns 404 error if user not found' do
user.emails << email user.emails << email
user.save user.save
...@@ -876,6 +884,10 @@ describe API::Users do ...@@ -876,6 +884,10 @@ describe API::Users do
expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound
end end
it_behaves_like '412 response' do
let(:request) { api("/users/#{user.id}", admin) }
end
it "does not delete for unauthenticated user" do it "does not delete for unauthenticated user" do
Sidekiq::Testing.inline! { delete api("/users/#{user.id}") } Sidekiq::Testing.inline! { delete api("/users/#{user.id}") }
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
...@@ -1116,6 +1128,10 @@ describe API::Users do ...@@ -1116,6 +1128,10 @@ describe API::Users do
end.to change { user.keys.count}.by(-1) end.to change { user.keys.count}.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/user/keys/#{key.id}", user) }
end
it "returns 404 if key ID not found" do it "returns 404 if key ID not found" do
delete api("/user/keys/42", user) delete api("/user/keys/42", user)
...@@ -1239,6 +1255,10 @@ describe API::Users do ...@@ -1239,6 +1255,10 @@ describe API::Users do
end.to change { user.emails.count}.by(-1) end.to change { user.emails.count}.by(-1)
end end
it_behaves_like '412 response' do
let(:request) { api("/user/emails/#{email.id}", user) }
end
it "returns 404 if email ID not found" do it "returns 404 if email ID not found" do
delete api("/user/emails/42", user) delete api("/user/emails/42", user)
...@@ -1551,6 +1571,10 @@ describe API::Users do ...@@ -1551,6 +1571,10 @@ describe API::Users do
expect(json_response['message']).to eq('403 Forbidden') expect(json_response['message']).to eq('403 Forbidden')
end end
it_behaves_like '412 response' do
let(:request) { api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) }
end
it 'revokes a impersonation token' do it 'revokes a impersonation token' do
delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin)
......
...@@ -40,3 +40,28 @@ shared_examples_for '404 response' do ...@@ -40,3 +40,28 @@ shared_examples_for '404 response' do
end end
end end
end end
shared_examples_for '412 response' do
let(:params) { nil }
let(:success_status) { 204 }
context 'for a modified ressource' do
before do
delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' }
end
it 'returns 412' do
expect(response).to have_gitlab_http_status(412)
end
end
context 'for an unmodified ressource' do
before do
delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now }
end
it 'returns accepted' do
expect(response).to have_gitlab_http_status(success_status)
end
end
end
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