Commit a66eaff7 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Mayra Cabrera

Add a package destroy GraphQL mutation

parent 266f84ad
# frozen_string_literal: true
module Mutations
module Packages
class Destroy < ::Mutations::BaseMutation
graphql_name 'DestroyPackage'
authorize :destroy_package
argument :id,
::Types::GlobalIDType[::Packages::Package],
required: true,
description: 'ID of the Package.'
def resolve(id:)
package = authorized_find!(id: id)
result = ::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute
errors = result.error? ? Array.wrap(result[:message]) : []
{
errors: errors
}
end
private
def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Packages::Package].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
......@@ -104,6 +104,7 @@ module Types
mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query
mount_mutation Mutations::Namespace::PackageSettings::Update
mount_mutation Mutations::UserCallouts::Create
mount_mutation Mutations::Packages::Destroy
end
end
......
# frozen_string_literal: true
module Packages
class DestroyPackageService < BaseContainerService
alias_method :package, :container
def execute
return service_response_error("You don't have access to this package", 403) unless user_can_delete_package?
package.destroy!
package.sync_maven_metadata(current_user)
service_response_success('Package was successfully deleted')
rescue StandardError
service_response_error('Failed to remove the package', 400)
end
private
def service_response_error(message, http_status)
ServiceResponse.error(message: message, http_status: http_status)
end
def service_response_success(message)
ServiceResponse.success(message: message)
end
def user_can_delete_package?
can?(current_user, :destroy_package, package.project)
end
end
end
......@@ -1835,6 +1835,24 @@ Input type: `DestroyNoteInput`
| <a id="mutationdestroynoteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdestroynotenote"></a>`note` | [`Note`](#note) | The note after mutation. |
### `Mutation.destroyPackage`
Input type: `DestroyPackageInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdestroypackageclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdestroypackageid"></a>`id` | [`PackagesPackageID!`](#packagespackageid) | ID of the Package. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdestroypackageclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdestroypackageerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.destroySnippet`
Input type: `DestroySnippetInput`
......
......@@ -71,9 +71,7 @@ module API
.new(user_project, params[:package_id]).execute
destroy_conditionally!(package) do |package|
if package.destroy
package.sync_maven_metadata(current_user)
end
::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Destroying a package' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be_with_reload(:package) { create(:package) }
let_it_be(:user) { create(:user) }
let(:project) { package.project }
let(:id) { package.to_global_id.to_s }
let(:query) do
<<~GQL
errors
GQL
end
let(:params) { { id: id } }
let(:mutation) { graphql_mutation(:destroy_package, params, query) }
let(:mutation_response) { graphql_mutation_response(:destroyPackage) }
shared_examples 'destroying the package' do
it 'destroy the package' do
expect(::Packages::DestroyPackageService)
.to receive(:new).with(container: package, current_user: user).and_call_original
expect { mutation_request }.to change { ::Packages::Package.count }.by(-1)
end
it_behaves_like 'returning response status', :success
end
shared_examples 'denying the mutation request' do
it 'does not destroy the package' do
expect(::Packages::DestroyPackageService)
.not_to receive(:new).with(container: package, current_user: user)
expect { mutation_request }.not_to change { ::Packages::Package.count }
expect(mutation_response).to be_nil
end
it_behaves_like 'returning response status', :success
end
describe 'post graphql mutation' do
subject(:mutation_request) { post_graphql_mutation(mutation, current_user: user) }
context 'with valid id' do
where(:user_role, :shared_examples_name) do
:maintainer | 'destroying the package'
:developer | 'denying the mutation request'
:reporter | 'denying the mutation request'
:guest | 'denying the mutation request'
:anonymous | 'denying the mutation request'
end
with_them do
before do
project.send("add_#{user_role}", user) unless user_role == :anonymous
end
it_behaves_like params[:shared_examples_name]
end
end
context 'with invalid id' do
let(:params) { { id: 'gid://gitlab/Packages::Package/5555' } }
it_behaves_like 'denying the mutation request'
end
context 'when an error occures' do
before do
project.add_maintainer(user)
end
it 'returns the errors in the response' do
allow_next_found_instance_of(::Packages::Package) do |package|
allow(package).to receive(:destroy!).and_raise(StandardError)
end
mutation_request
expect(mutation_response['errors']).to eq(['Failed to remove the package'])
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::DestroyPackageService do
let_it_be(:user) { create(:user) }
let!(:package) { create(:npm_package) }
describe '#execute' do
subject(:service) { described_class.new(container: package, current_user: user) }
context 'when the user is authorized' do
before do
package.project.add_maintainer(user)
end
context 'when the destroy is successfull' do
it 'destroy the package' do
expect(package).to receive(:sync_maven_metadata).and_call_original
expect { service.execute }.to change { Packages::Package.count }.by(-1)
end
it 'returns a success ServiceResponse' do
response = service.execute
expect(response).to be_a(ServiceResponse)
expect(response).to be_success
expect(response.message).to eq("Package was successfully deleted")
end
end
context 'when the destroy is not successful' do
before do
allow(package).to receive(:destroy!).and_raise(StandardError, "test")
end
it 'returns an error ServiceResponse' do
response = service.execute
expect(package).not_to receive(:sync_maven_metadata)
expect(response).to be_a(ServiceResponse)
expect(response).to be_error
expect(response.message).to eq("Failed to remove the package")
expect(response.status).to eq(:error)
end
end
end
context 'when the user is not authorized' do
it 'returns an error ServiceResponse' do
response = service.execute
expect(response).to be_a(ServiceResponse)
expect(response).to be_error
expect(response.message).to eq("You don't have access to this package")
expect(response.status).to eq(:error)
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