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