Commit c280acb0 authored by Robert Schilling's avatar Robert Schilling

Backport API to V3

parent f2dd2604
...@@ -5,10 +5,13 @@ module API ...@@ -5,10 +5,13 @@ module API
version %w(v3 v4), using: :path version %w(v3 v4), using: :path
version 'v3', using: :path do version 'v3', using: :path do
mount ::API::V3::AwardEmoji
mount ::API::V3::Boards mount ::API::V3::Boards
mount ::API::V3::Branches mount ::API::V3::Branches
mount ::API::V3::BroadcastMessages
mount ::API::V3::Commits mount ::API::V3::Commits
mount ::API::V3::DeployKeys mount ::API::V3::DeployKeys
mount ::API::V3::Environments
mount ::API::V3::Files mount ::API::V3::Files
mount ::API::V3::Groups mount ::API::V3::Groups
mount ::API::V3::Issues mount ::API::V3::Issues
...@@ -21,12 +24,16 @@ module API ...@@ -21,12 +24,16 @@ module API
mount ::API::V3::Projects mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets mount ::API::V3::ProjectSnippets
mount ::API::V3::Repositories mount ::API::V3::Repositories
mount ::API::V3::Runners
mount ::API::V3::Services
mount ::API::V3::Subscriptions mount ::API::V3::Subscriptions
mount ::API::V3::SystemHooks mount ::API::V3::SystemHooks
mount ::API::V3::Tags mount ::API::V3::Tags
mount ::API::V3::Todos
mount ::API::V3::Templates mount ::API::V3::Templates
mount ::API::V3::Todos
mount ::API::V3::Triggers
mount ::API::V3::Users mount ::API::V3::Users
mount ::API::V3::Variables
end end
before { allow_access_with_scope :api } before { allow_access_with_scope :api }
......
module API
module V3
class AwardEmoji < Grape::API
include PaginationParams
before { authenticate! }
AWARDABLES = %w[issue merge_request snippet].freeze
resource :projects do
AWARDABLES.each do |awardable_type|
awardable_string = awardable_type.pluralize
awardable_id_string = "#{awardable_type}_id"
params do
requires :id, type: String, desc: 'The ID of a project'
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
[":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"].each do |endpoint|
desc 'Delete a +awardables+ award emoji' do
detail 'This feature was introduced in 8.9'
success ::API::Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji'
end
delete "#{endpoint}/:award_id" do
award = awardable.award_emoji.find(params[:award_id])
unauthorized! unless award.user == current_user || current_user.admin?
present award.destroy, with: ::API::Entities::AwardEmoji
end
end
end
end
helpers do
def awardable
@awardable ||=
begin
if params.include?(:note_id)
note_id = params.delete(:note_id)
awardable.notes.find(note_id)
elsif params.include?(:issue_id)
user_project.issues.find(params[:issue_id])
elsif params.include?(:merge_request_id)
user_project.merge_requests.find(params[:merge_request_id])
else
user_project.snippets.find(params[:snippet_id])
end
end
end
end
end
end
end
...@@ -44,6 +44,27 @@ module API ...@@ -44,6 +44,27 @@ module API
authorize!(:read_board, user_project) authorize!(:read_board, user_project)
present board_lists, with: ::API::Entities::List present board_lists, with: ::API::Entities::List
end end
desc 'Delete a board list' do
detail 'This feature was introduced in 8.13'
success ::API::Entities::List
end
params do
requires :list_id, type: Integer, desc: 'The ID of a board list'
end
delete "/lists/:list_id" do
authorize!(:admin_list, user_project)
list = board_lists.find(params[:list_id])
service = ::Boards::Lists::DestroyService.new(user_project, current_user)
if service.execute(list)
present list, with: ::API::Entities::List
else
render_api_error!({ error: 'List could not be deleted!' }, 400)
end
end
end end
end end
end end
......
...@@ -19,6 +19,26 @@ module API ...@@ -19,6 +19,26 @@ module API
present branches, with: ::API::Entities::RepoBranch, project: user_project present branches, with: ::API::Entities::RepoBranch, project: user_project
end end
desc 'Delete a branch'
params do
requires :branch, type: String, desc: 'The name of the branch'
end
delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
authorize_push_project
result = DeleteBranchService.new(user_project, current_user).
execute(params[:branch])
if result[:status] == :success
status(200)
{
branch_name: params[:branch]
}
else
render_api_error!(result[:message], result[:return_code])
end
end
desc 'Delete all merged branches' desc 'Delete all merged branches'
delete ":id/repository/merged_branches" do delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute DeleteMergedBranchesService.new(user_project, current_user).async_execute
......
module API
module V3
class BroadcastMessages < Grape::API
include PaginationParams
before { authenticate! }
before { authenticated_as_admin! }
resource :broadcast_messages do
helpers do
def find_message
BroadcastMessage.find(params[:id])
end
end
desc 'Delete a broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success ::API::Entities::BroadcastMessage
end
params do
requires :id, type: Integer, desc: 'Broadcast message ID'
end
delete ':id' do
message = find_message
present message.destroy, with: ::API::Entities::BroadcastMessage
end
end
end
end
end
module API
module V3
class Environments < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The project ID'
end
resource :projects do
desc 'Deletes an existing environment' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::Entities::Environment
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
end
delete ':id/environments/:environment_id' do
authorize! :update_environment, user_project
environment = user_project.environments.find(params[:environment_id])
present environment.destroy, with: ::API::Entities::Environment
end
end
end
end
end
...@@ -226,6 +226,8 @@ module API ...@@ -226,6 +226,8 @@ module API
not_found!('Issue') unless issue not_found!('Issue') unless issue
authorize!(:destroy_issue, issue) authorize!(:destroy_issue, issue)
status(200)
issue.destroy issue.destroy
end end
end end
......
...@@ -13,6 +13,21 @@ module API ...@@ -13,6 +13,21 @@ module API
get ':id/labels' do get ':id/labels' do
present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
end end
desc 'Delete an existing label' do
success ::API::Entities::Label
end
params do
requires :name, type: String, desc: 'The name of the label to be deleted'
end
delete ':id/labels' do
authorize! :admin_label, user_project
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
present label.destroy, with: ::API::Entities::Label, current_user: current_user, project: user_project
end
end end
end end
end end
......
...@@ -119,6 +119,7 @@ module API ...@@ -119,6 +119,7 @@ module API
# This is to ensure back-compatibility but 204 behavior should be used # This is to ensure back-compatibility but 204 behavior should be used
# for all DELETE endpoints in 9.0! # for all DELETE endpoints in 9.0!
if member.nil? if member.nil?
status(200 )
{ message: "Access revoked", id: params[:user_id].to_i } { message: "Access revoked", id: params[:user_id].to_i }
else else
::Members::DestroyService.new(source, current_user, declared_params).execute ::Members::DestroyService.new(source, current_user, declared_params).execute
......
...@@ -103,6 +103,8 @@ module API ...@@ -103,6 +103,8 @@ module API
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_project_merge_request(params[:merge_request_id])
authorize!(:destroy_merge_request, merge_request) authorize!(:destroy_merge_request, merge_request)
status(200)
merge_request.destroy merge_request.destroy
end end
......
...@@ -121,6 +121,8 @@ module API ...@@ -121,6 +121,8 @@ module API
authorize! :admin_project_snippet, snippet authorize! :admin_project_snippet, snippet
snippet.destroy snippet.destroy
status(200)
end end
desc 'Get a raw project snippet' desc 'Get a raw project snippet'
......
...@@ -359,6 +359,8 @@ module API ...@@ -359,6 +359,8 @@ 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
status(200)
::Projects::DestroyService.new(user_project, current_user, {}).async_execute ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end end
...@@ -384,6 +386,7 @@ module API ...@@ -384,6 +386,7 @@ module API
authorize! :remove_fork_project, user_project authorize! :remove_fork_project, user_project
if user_project.forked? if user_project.forked?
status(200)
user_project.forked_project_link.destroy user_project.forked_project_link.destroy
else else
not_modified! not_modified!
......
module API
module V3
class Runners < Grape::API
include PaginationParams
before { authenticate! }
resource :runners do
desc 'Remove a runner' do
success ::API::Entities::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
delete ':id' do
runner = Ci::Runner.find(params[:id])
not_found!('Runner') unless runner
authenticate_delete_runner!(runner)
status(200)
runner.destroy
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
before { authorize_admin_project }
desc "Disable project's runner" do
success ::API::Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
runner_project.destroy
present runner, with: ::API::Entities::Runner
end
end
helpers do
def authenticate_delete_runner!(runner)
return if current_user.is_admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def user_can_access_runner?(runner)
current_user.ci_authorized_runners.exists?(runner.id)
end
end
end
end
end
module API
module V3
class Services < Grape::API
services = {
'asana' => [
{
required: true,
name: :api_key,
type: String,
desc: 'User API token'
},
{
required: false,
name: :restrict_to_branch,
type: String,
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
}
],
'assembla' => [
{
required: true,
name: :token,
type: String,
desc: 'The authentication token'
},
{
required: false,
name: :subdomain,
type: String,
desc: 'Subdomain setting'
}
],
'bamboo' => [
{
required: true,
name: :bamboo_url,
type: String,
desc: 'Bamboo root URL like https://bamboo.example.com'
},
{
required: true,
name: :build_key,
type: String,
desc: 'Bamboo build plan key like'
},
{
required: true,
name: :username,
type: String,
desc: 'A user with API access, if applicable'
},
{
required: true,
name: :password,
type: String,
desc: 'Passord of the user'
}
],
'bugzilla' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'New issue URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'Issues URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'Project URL'
},
{
required: false,
name: :description,
type: String,
desc: 'Description'
},
{
required: false,
name: :title,
type: String,
desc: 'Title'
}
],
'buildkite' => [
{
required: true,
name: :token,
type: String,
desc: 'Buildkite project GitLab token'
},
{
required: true,
name: :project_url,
type: String,
desc: 'The buildkite project URL'
},
{
required: false,
name: :enable_ssl_verification,
type: Boolean,
desc: 'Enable SSL verification for communication'
}
],
'builds-email' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :add_pusher,
type: Boolean,
desc: 'Add pusher to recipients list'
},
{
required: false,
name: :notify_only_broken_builds,
type: Boolean,
desc: 'Notify only broken builds'
}
],
'campfire' => [
{
required: true,
name: :token,
type: String,
desc: 'Campfire token'
},
{
required: false,
name: :subdomain,
type: String,
desc: 'Campfire subdomain'
},
{
required: false,
name: :room,
type: String,
desc: 'Campfire room'
}
],
'custom-issue-tracker' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'New issue URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'Issues URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'Project URL'
},
{
required: false,
name: :description,
type: String,
desc: 'Description'
},
{
required: false,
name: :title,
type: String,
desc: 'Title'
}
],
'drone-ci' => [
{
required: true,
name: :token,
type: String,
desc: 'Drone CI token'
},
{
required: true,
name: :drone_url,
type: String,
desc: 'Drone CI URL'
},
{
required: false,
name: :enable_ssl_verification,
type: Boolean,
desc: 'Enable SSL verification for communication'
}
],
'emails-on-push' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :disable_diffs,
type: Boolean,
desc: 'Disable code diffs'
},
{
required: false,
name: :send_from_committer_email,
type: Boolean,
desc: 'Send from committer'
}
],
'external-wiki' => [
{
required: true,
name: :external_wiki_url,
type: String,
desc: 'The URL of the external Wiki'
}
],
'flowdock' => [
{
required: true,
name: :token,
type: String,
desc: 'Flowdock token'
}
],
'gemnasium' => [
{
required: true,
name: :api_key,
type: String,
desc: 'Your personal API key on gemnasium.com'
},
{
required: true,
name: :token,
type: String,
desc: "The project's slug on gemnasium.com"
}
],
'hipchat' => [
{
required: true,
name: :token,
type: String,
desc: 'The room token'
},
{
required: false,
name: :room,
type: String,
desc: 'The room name or ID'
},
{
required: false,
name: :color,
type: String,
desc: 'The room color'
},
{
required: false,
name: :notify,
type: Boolean,
desc: 'Enable notifications'
},
{
required: false,
name: :api_version,
type: String,
desc: 'Leave blank for default (v2)'
},
{
required: false,
name: :server,
type: String,
desc: 'Leave blank for default. https://hipchat.example.com'
}
],
'irker' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Recipients/channels separated by whitespaces'
},
{
required: false,
name: :default_irc_uri,
type: String,
desc: 'Default: irc://irc.network.net:6697'
},
{
required: false,
name: :server_host,
type: String,
desc: 'Server host. Default localhost'
},
{
required: false,
name: :server_port,
type: Integer,
desc: 'Server port. Default 6659'
},
{
required: false,
name: :colorize_messages,
type: Boolean,
desc: 'Colorize messages'
}
],
'jira' => [
{
required: true,
name: :url,
type: String,
desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
},
{
required: true,
name: :project_key,
type: String,
desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
},
{
required: false,
name: :username,
type: String,
desc: 'The username of the user created to be used with GitLab/JIRA'
},
{
required: false,
name: :password,
type: String,
desc: 'The password of the user created to be used with GitLab/JIRA'
},
{
required: false,
name: :jira_issue_transition_id,
type: Integer,
desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
'kubernetes' => [
{
required: true,
name: :namespace,
type: String,
desc: 'The Kubernetes namespace to use'
},
{
required: true,
name: :api_url,
type: String,
desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
},
{
required: true,
name: :token,
type: String,
desc: 'The service token to authenticate against the Kubernetes cluster with'
},
{
required: false,
name: :ca_pem,
type: String,
desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
},
],
'mattermost-slash-commands' => [
{
required: true,
name: :token,
type: String,
desc: 'The Mattermost token'
}
],
'slack-slash-commands' => [
{
required: true,
name: :token,
type: String,
desc: 'The Slack token'
}
],
'pipelines-email' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :notify_only_broken_builds,
type: Boolean,
desc: 'Notify only broken builds'
}
],
'pivotaltracker' => [
{
required: true,
name: :token,
type: String,
desc: 'The Pivotaltracker token'
},
{
required: false,
name: :restrict_to_branch,
type: String,
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
}
],
'pushover' => [
{
required: true,
name: :api_key,
type: String,
desc: 'The application key'
},
{
required: true,
name: :user_key,
type: String,
desc: 'The user key'
},
{
required: true,
name: :priority,
type: String,
desc: 'The priority'
},
{
required: true,
name: :device,
type: String,
desc: 'Leave blank for all active devices'
},
{
required: true,
name: :sound,
type: String,
desc: 'The sound of the notification'
}
],
'redmine' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'The new issue URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'The project URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'The issues URL'
},
{
required: false,
name: :description,
type: String,
desc: 'The description of the tracker'
}
],
'slack' => [
{
required: true,
name: :webhook,
type: String,
desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...'
},
{
required: false,
name: :new_issue_url,
type: String,
desc: 'The user name'
},
{
required: false,
name: :channel,
type: String,
desc: 'The channel name'
}
],
'mattermost' => [
{
required: true,
name: :webhook,
type: String,
desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...'
}
],
'teamcity' => [
{
required: true,
name: :teamcity_url,
type: String,
desc: 'TeamCity root URL like https://teamcity.example.com'
},
{
required: true,
name: :build_type,
type: String,
desc: 'Build configuration ID'
},
{
required: true,
name: :username,
type: String,
desc: 'A user with permissions to trigger a manual build'
},
{
required: true,
name: :password,
type: String,
desc: 'The password of the user'
}
]
}
resource :projects do
before { authenticate! }
before { authorize_admin_project }
helpers do
def service_attributes(service)
service.fields.inject([]) do |arr, hash|
arr << hash[:name].to_sym
end
end
end
desc "Delete a service for project"
params do
requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
end
delete ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
attrs = service_attributes(service).inject({}) do |hash, key|
hash.merge!(key => nil)
end
if service.update_attributes(attrs.merge(active: false))
status(200)
true
else
render_api_error!('400 Bad Request', 400)
end
end
end
end
end
end
...@@ -13,6 +13,19 @@ module API ...@@ -13,6 +13,19 @@ module API
get do get do
present SystemHook.all, with: ::API::Entities::Hook present SystemHook.all, with: ::API::Entities::Hook
end end
desc 'Delete a hook' do
success ::API::Entities::Hook
end
params do
requires :id, type: Integer, desc: 'The ID of the system hook'
end
delete ":id" do
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
present hook.destroy, with: ::API::Entities::Hook
end
end end
end end
end end
......
...@@ -14,6 +14,26 @@ module API ...@@ -14,6 +14,26 @@ module API
tags = user_project.repository.tags.sort_by(&:name).reverse tags = user_project.repository.tags.sort_by(&:name).reverse
present tags, with: ::API::Entities::RepoTag, project: user_project present tags, with: ::API::Entities::RepoTag, project: user_project
end end
desc 'Delete a repository tag'
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
authorize_push_project
result = ::Tags::DestroyService.new(user_project, current_user).
execute(params[:tag_name])
if result[:status] == :success
status(200)
{
tag_name: params[:tag_name]
}
else
render_api_error!(result[:message], result[:return_code])
end
end
end end
end end
end end
......
...@@ -19,6 +19,8 @@ module API ...@@ -19,6 +19,8 @@ module API
desc 'Mark all todos as done' desc 'Mark all todos as done'
delete do delete do
status(200)
todos = TodosFinder.new(current_user, params).execute todos = TodosFinder.new(current_user, params).execute
TodoService.new.mark_todos_as_done(todos, current_user) TodoService.new.mark_todos_as_done(todos, current_user)
end end
......
module API
module V3
class Triggers < Grape::API
include PaginationParams
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
desc 'Delete a trigger' do
success ::API::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
end
delete ':id/triggers/:token' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
trigger.destroy
present trigger, with: ::API::Entities::Trigger
end
end
end
end
end
...@@ -92,6 +92,25 @@ module API ...@@ -92,6 +92,25 @@ module API
present paginate(events), with: ::API::V3::Entities::Event present paginate(events), with: ::API::V3::Entities::Event
end end
desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
success ::API::Entities::SSHKey
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
delete ':id/keys/:key_id' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
present key.destroy, with: ::API::Entities::SSHKey
end
end end
resource :user do resource :user do
...@@ -111,6 +130,19 @@ module API ...@@ -111,6 +130,19 @@ module API
get "emails" do get "emails" do
present current_user.emails, with: ::API::Entities::Email present current_user.emails, with: ::API::Entities::Email
end end
desc 'Delete an SSH key from the currently authenticated user' do
success ::API::Entities::SSHKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
delete "keys/:key_id" do
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
present key.destroy, with: ::API::Entities::SSHKey
end
end end
end end
end end
......
module API
module V3
class Variables < Grape::API
include PaginationParams
before { authenticate! }
before { authorize! :admin_build, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
desc 'Delete an existing variable from a project' do
success ::API::Entities::Variable
end
params do
requires :key, type: String, desc: 'The key of the variable'
end
delete ':id/variables/:key' do
variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable
present variable.destroy, with: ::API::Entities::Variable
end
end
end
end
end
module API module API
# Projects variables API
class Variables < Grape::API class Variables < Grape::API
include PaginationParams include PaginationParams
...@@ -81,7 +80,7 @@ module API ...@@ -81,7 +80,7 @@ module API
end end
delete ':id/variables/:key' do delete ':id/variables/:key' do
variable = user_project.variables.find_by(key: params[:key]) variable = user_project.variables.find_by(key: params[:key])
return not_found!('Variable') unless variable not_found!('Variable') unless variable
variable.destroy variable.destroy
end end
......
...@@ -212,7 +212,7 @@ describe API::ProjectSnippets, api: true do ...@@ -212,7 +212,7 @@ describe API::ProjectSnippets, api: true do
end end
it 'returns 404 for invalid snippet id' do it 'returns 404 for invalid snippet id' do
get api("/projects/#{snippet.project.id}/snippets/1234", admin) get api("/projects/#{snippet.project.id}/snippets/1234/raw", admin)
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')
......
require 'spec_helper'
describe API::V3::AwardEmoji, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) }
let!(:note) { create(:note, project: project, noteable: issue) }
before { project.team << [user, :master] }
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
context 'when the awardable is an Issue' do
it 'deletes the award' do
expect do
delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(200)
end.to change { issue.award_emoji.count }.from(1).to(0)
end
it 'returns a 404 error when the award emoji can not be found' do
delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user)
expect(response).to have_http_status(404)
end
end
context 'when the awardable is a Merge Request' do
it 'deletes the award' do
expect do
delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(200)
end.to change { merge_request.award_emoji.count }.from(1).to(0)
end
it 'returns a 404 error when note id not found' do
delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user)
expect(response).to have_http_status(404)
end
end
context 'when the awardable is a Snippet' do
let(:snippet) { create(:project_snippet, :public, project: project) }
let!(:award) { create(:award_emoji, awardable: snippet, user: user) }
it 'deletes the award' do
expect do
delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
expect(response).to have_http_status(200)
end.to change { snippet.award_emoji.count }.from(1).to(0)
end
end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket', user: user) }
it 'deletes the award' do
expect do
delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(200)
end.to change { note.award_emoji.count }.from(1).to(0)
end
end
end
...@@ -5,6 +5,7 @@ describe API::V3::Boards, api: true do ...@@ -5,6 +5,7 @@ describe API::V3::Boards, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:guest) { create(:user) } let(:guest) { create(:user) }
let(:non_member) { create(:user) }
let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) }
let!(:dev_label) do let!(:dev_label) do
...@@ -76,4 +77,37 @@ describe API::V3::Boards, api: true do ...@@ -76,4 +77,37 @@ describe API::V3::Boards, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
describe "DELETE /projects/:id/board/lists/:list_id" do
let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" }
it "rejects a non member from deleting a list" do
delete v3_api("#{base_url}/#{dev_list.id}", non_member)
expect(response).to have_http_status(403)
end
it "rejects a user with guest role from deleting a list" do
delete v3_api("#{base_url}/#{dev_list.id}", guest)
expect(response).to have_http_status(403)
end
it "returns 404 error if list id not found" do
delete v3_api("#{base_url}/44444", user)
expect(response).to have_http_status(404)
end
context "when the user is project owner" do
let(:owner) { create(:user) }
let(:project) { create(:empty_project, namespace: owner.namespace) }
it "deletes the list if an admin requests it" do
delete v3_api("#{base_url}/#{dev_list.id}", owner)
expect(response).to have_http_status(200)
end
end
end
end end
...@@ -5,8 +5,12 @@ describe API::V3::Branches, api: true do ...@@ -5,8 +5,12 @@ describe API::V3::Branches, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, :repository, creator: user) } let!(:project) { create(:project, :repository, creator: user) }
let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:master) { create(:project_member, :master, user: user, project: project) }
let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:branch_name) { 'feature' }
let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") }
describe "GET /projects/:id/repository/branches" do describe "GET /projects/:id/repository/branches" do
it "returns an array of project branches" do it "returns an array of project branches" do
...@@ -21,6 +25,44 @@ describe API::V3::Branches, api: true do ...@@ -21,6 +25,44 @@ describe API::V3::Branches, api: true do
end end
end end
describe "DELETE /projects/:id/repository/branches/:branch" do
before do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
it "removes branch" do
delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(200)
expect(json_response['branch_name']).to eq(branch_name)
end
it "removes a branch with dots in the branch name" do
delete v3_api("/projects/#{project.id}/repository/branches/with.1.2.3", user)
expect(response).to have_http_status(200)
expect(json_response['branch_name']).to eq("with.1.2.3")
end
it 'returns 404 if branch not exists' do
delete v3_api("/projects/#{project.id}/repository/branches/foobar", user)
expect(response).to have_http_status(404)
end
it "removes protected branch" do
create(:protected_branch, project: project, name: branch_name)
delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('Protected branch cant be removed')
end
it "does not remove HEAD branch" do
delete v3_api("/projects/#{project.id}/repository/branches/master", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('Cannot remove HEAD branch')
end
end
describe "DELETE /projects/:id/repository/merged_branches" do describe "DELETE /projects/:id/repository/merged_branches" do
before do before do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
...@@ -33,10 +75,7 @@ describe API::V3::Branches, api: true do ...@@ -33,10 +75,7 @@ describe API::V3::Branches, api: true do
end end
it 'returns a 403 error if guest' do it 'returns a 403 error if guest' do
user_b = create :user delete v3_api("/projects/#{project.id}/repository/merged_branches", user2)
create(:project_member, :guest, user: user_b, project: project)
delete v3_api("/projects/#{project.id}/repository/merged_branches", user_b)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
......
require 'spec_helper'
describe API::V3::BroadcastMessages, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe 'DELETE /broadcast_messages/:id' do
let!(:message) { create(:broadcast_message) }
it 'returns a 401 for anonymous users' do
delete v3_api("/broadcast_messages/#{message.id}"),
attributes_for(:broadcast_message)
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
delete v3_api("/broadcast_messages/#{message.id}", user),
attributes_for(:broadcast_message)
expect(response).to have_http_status(403)
end
it 'deletes the broadcast message for admins' do
expect do
delete v3_api("/broadcast_messages/#{message.id}", admin)
expect(response).to have_http_status(200)
end.to change { BroadcastMessage.count }.by(-1)
end
end
end
require 'spec_helper'
describe API::V3::Environments, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:project) { create(:empty_project, :private, namespace: user.namespace) }
let!(:environment) { create(:environment, project: project) }
before do
project.team << [user, :master]
end
describe 'DELETE /projects/:id/environments/:environment_id' do
context 'as a master' do
it 'returns a 200 for an existing environment' do
delete v3_api("/projects/#{project.id}/environments/#{environment.id}", user)
expect(response).to have_http_status(200)
end
it 'returns a 404 for non existing id' do
delete v3_api("/projects/#{project.id}/environments/12345", user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
end
context 'a non member' do
it 'rejects the request' do
delete v3_api("/projects/#{project.id}/environments/#{environment.id}", non_member)
expect(response).to have_http_status(404)
end
end
end
end
...@@ -2,17 +2,6 @@ require 'spec_helper' ...@@ -2,17 +2,6 @@ require 'spec_helper'
describe API::V3::Files, api: true do describe API::V3::Files, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
let(:file_path) { 'files/ruby/popen.rb' }
let(:params) do
{
file_path: file_path,
ref: 'master'
}
end
let(:author_email) { FFaker::Internet.email }
# I have to remove periods from the end of the name # I have to remove periods from the end of the name
# This happened when the user's name had a suffix (i.e. "Sr.") # This happened when the user's name had a suffix (i.e. "Sr.")
...@@ -26,6 +15,18 @@ describe API::V3::Files, api: true do ...@@ -26,6 +15,18 @@ describe API::V3::Files, api: true do
# ... # ...
# Author: Foo Sr <foo@example.com> # Author: Foo Sr <foo@example.com>
# ... # ...
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
let(:file_path) { 'files/ruby/popen.rb' }
let(:params) do
{
file_path: file_path,
ref: 'master'
}
end
let(:author_email) { FFaker::Internet.email }
let(:author_name) { FFaker::Name.name.chomp("\.") } let(:author_name) { FFaker::Name.name.chomp("\.") }
before { project.team << [user, :developer] } before { project.team << [user, :developer] }
......
...@@ -149,4 +149,23 @@ describe API::V3::Labels, api: true do ...@@ -149,4 +149,23 @@ describe API::V3::Labels, api: true do
end end
end end
end end
describe 'DELETE /projects/:id/labels' do
it 'returns 200 for existing label' do
delete v3_api("/projects/#{project.id}/labels", user), name: 'label1'
expect(response).to have_http_status(200)
end
it 'returns 404 for non existing label' do
delete v3_api("/projects/#{project.id}/labels", user), name: 'label2'
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
it 'returns 400 for wrong parameters' do
delete v3_api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(400)
end
end
end end
require 'spec_helper' require 'spec_helper'
describe API::Members, api: true do describe API::V3::Members, api: true do
include ApiHelpers include ApiHelpers
let(:master) { create(:user) } let(:master) { create(:user) }
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe API::V3::Notes, api: true do describe API::V3::Notes, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:empty_project, :public, namespace: user.namespace) } let!(:project) { create(:empty_project, :public, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, author: user) } let!(:issue) { create(:issue, project: project, author: user) }
......
require 'spec_helper'
describe API::V3::Runners, api: true do
include ApiHelpers
let(:admin) { create(:user, :admin) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:project) { create(:empty_project, creator_id: user.id) }
let(:project2) { create(:empty_project, creator_id: user.id) }
let!(:shared_runner) { create(:ci_runner, :shared) }
let!(:unused_specific_runner) { create(:ci_runner) }
let!(:specific_runner) do
create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
end
end
let!(:two_projects_runner) do
create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
create(:ci_runner_project, runner: runner, project: project2)
end
end
before do
# Set project access for users
create(:project_member, :master, user: user, project: project)
create(:project_member, :reporter, user: user2, project: project)
end
describe 'DELETE /runners/:id' do
context 'admin user' do
context 'when runner is shared' do
it 'deletes runner' do
expect do
delete v3_api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.shared.count }.by(-1)
end
end
context 'when runner is not shared' do
it 'deletes unused runner' do
expect do
delete v3_api("/runners/#{unused_specific_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
expect do
delete v3_api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end
end
it 'returns 404 if runner does not exists' do
delete v3_api('/runners/9999', admin)
expect(response).to have_http_status(404)
end
end
context 'authorized user' do
context 'when runner is shared' do
it 'does not delete runner' do
delete v3_api("/runners/#{shared_runner.id}", user)
expect(response).to have_http_status(403)
end
end
context 'when runner is not shared' do
it 'does not delete runner without access to it' do
delete v3_api("/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
end
it 'does not delete runner with more than one associated project' do
delete v3_api("/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(403)
end
it 'deletes runner for one owned project' do
expect do
delete v3_api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end
end
end
context 'unauthorized user' do
it 'does not delete runner' do
delete v3_api("/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
end
end
end
describe 'DELETE /projects/:id/runners/:runner_id' do
context 'authorized user' do
context 'when runner have more than one associated projects' do
it "disables project's runner" do
expect do
delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(200)
end.to change{ project.runners.count }.by(-1)
end
end
context 'when runner have one associated projects' do
it "does not disable project's runner" do
expect do
delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
end.to change{ project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
end
it 'returns 404 is runner is not found' do
delete v3_api("/projects/#{project.id}/runners/9999", user)
expect(response).to have_http_status(404)
end
end
context 'authorized user without permissions' do
it "does not disable project's runner" do
delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthorized user' do
it "does not disable project's runner" do
delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
end
end
end
end
require "spec_helper"
describe API::V3::Services, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
Service.available_services_names.each do |service|
describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
it "deletes #{service}" do
delete v3_api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_http_status(200)
project.send(service_method).reload
expect(project.send(service_method).activated?).to be_falsey
end
end
end
end
...@@ -38,4 +38,20 @@ describe API::V3::SystemHooks, api: true do ...@@ -38,4 +38,20 @@ describe API::V3::SystemHooks, api: true do
end end
end end
end end
describe "DELETE /hooks/:id" do
it "deletes a hook" do
expect do
delete v3_api("/hooks/#{hook.id}", admin)
expect(response).to have_http_status(200)
end.to change { SystemHook.count }.by(-1)
end
it 'returns 404 if the system hook does not exist' do
delete v3_api('/hooks/12345', admin)
expect(response).to have_http_status(404)
end
end
end end
...@@ -64,4 +64,26 @@ describe API::V3::Tags, api: true do ...@@ -64,4 +64,26 @@ describe API::V3::Tags, api: true do
end end
end end
end end
describe 'DELETE /projects/:id/repository/tags/:tag_name' do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
before do
allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
end
context 'delete tag' do
it 'deletes an existing tag' do
delete v3_api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
expect(response).to have_http_status(200)
expect(json_response['tag_name']).to eq(tag_name)
end
it 'raises 404 if the tag does not exist' do
delete v3_api("/projects/#{project.id}/repository/tags/foobar", user)
expect(response).to have_http_status(404)
end
end
end
end end
require 'spec_helper'
describe API::V3::Triggers do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:trigger_token) { 'secure_token' }
let!(:project) { create(:project, :repository, creator: user) }
let!(:master) { create(:project_member, :master, user: user, project: project) }
let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
describe 'DELETE /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
expect do
delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
end.to change{project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
delete v3_api("/projects/#{project.id}/triggers/abcdef012345", user)
expect(response).to have_http_status(404)
end
end
context 'authenticated user with invalid permissions' do
it 'does not delete trigger' do
delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not delete trigger' do
delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}")
expect(response).to have_http_status(401)
end
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