Commit 10807286 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'add-delete-mutation-for-dast-site-profiles-231271' into 'master'

Add GraphQL mutation to delete DastSiteProfile

See merge request gitlab-org/gitlab!38257
parents e0d07579 9a81daba
...@@ -2359,6 +2359,46 @@ type DastSiteProfileCreatePayload { ...@@ -2359,6 +2359,46 @@ type DastSiteProfileCreatePayload {
id: ID id: ID
} }
"""
Autogenerated input type of DastSiteProfileDelete
"""
input DastSiteProfileDeleteInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The project the site profile belongs to.
"""
fullPath: ID!
"""
ID of the site profile to be deleted.
"""
id: DastSiteProfileID!
}
"""
Autogenerated return type of DastSiteProfileDelete
"""
type DastSiteProfileDeletePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Identifier of DastSiteProfile
"""
scalar DastSiteProfileID
""" """
Autogenerated input type of DeleteAnnotation Autogenerated input type of DeleteAnnotation
""" """
...@@ -8496,6 +8536,7 @@ type Mutation { ...@@ -8496,6 +8536,7 @@ type Mutation {
dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload
dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload
dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload
dastSiteProfileDelete(input: DastSiteProfileDeleteInput!): DastSiteProfileDeletePayload
deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
......
...@@ -6340,6 +6340,118 @@ ...@@ -6340,6 +6340,118 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "DastSiteProfileDeleteInput",
"description": "Autogenerated input type of DastSiteProfileDelete",
"fields": null,
"inputFields": [
{
"name": "fullPath",
"description": "The project the site profile belongs to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "id",
"description": "ID of the site profile to be deleted.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastSiteProfileID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastSiteProfileDeletePayload",
"description": "Autogenerated return type of DastSiteProfileDelete",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "DastSiteProfileID",
"description": "Identifier of DastSiteProfile",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "DeleteAnnotationInput", "name": "DeleteAnnotationInput",
...@@ -24452,6 +24564,33 @@ ...@@ -24452,6 +24564,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "dastSiteProfileDelete",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DastSiteProfileDeleteInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastSiteProfileDeletePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "deleteAnnotation", "name": "deleteAnnotation",
"description": null, "description": null,
...@@ -412,6 +412,15 @@ Autogenerated return type of DastSiteProfileCreate ...@@ -412,6 +412,15 @@ Autogenerated return type of DastSiteProfileCreate
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `id` | ID | ID of the site profile. | | `id` | ID | ID of the site profile. |
## DastSiteProfileDeletePayload
Autogenerated return type of DastSiteProfileDelete
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
## DeleteAnnotationPayload ## DeleteAnnotationPayload
Autogenerated return type of DeleteAnnotation Autogenerated return type of DeleteAnnotation
......
...@@ -24,6 +24,7 @@ module EE ...@@ -24,6 +24,7 @@ module EE
mount_mutation ::Mutations::Pipelines::RunDastScan mount_mutation ::Mutations::Pipelines::RunDastScan
mount_mutation ::Mutations::DastOnDemandScans::Create mount_mutation ::Mutations::DastOnDemandScans::Create
mount_mutation ::Mutations::DastSiteProfiles::Create mount_mutation ::Mutations::DastSiteProfiles::Create
mount_mutation ::Mutations::DastSiteProfiles::Delete
mount_mutation ::Mutations::DastScannerProfiles::Create mount_mutation ::Mutations::DastScannerProfiles::Create
mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily
end end
......
# frozen_string_literal: true
module Mutations
module DastSiteProfiles
class Delete < BaseMutation
include ResolvesProject
graphql_name 'DastSiteProfileDelete'
argument :full_path, GraphQL::ID_TYPE,
required: true,
description: 'The project the site profile belongs to.'
argument :id, ::Types::GlobalIDType[::DastSiteProfile],
required: true,
description: 'ID of the site profile to be deleted.'
authorize :run_ondemand_dast_scan
def resolve(full_path:, id:)
project = authorized_find!(full_path: full_path)
raise_resource_not_available_error! unless Feature.enabled?(:security_on_demand_scans_feature_flag, project)
dast_site_profile = find_dast_site_profile(project: project, global_id: id)
return { errors: dast_site_profile.errors.full_messages } unless dast_site_profile.destroy
{ errors: [] }
end
private
def find_object(full_path:)
resolve_project(full_path: full_path)
end
def find_dast_site_profile(project:, global_id:)
project.dast_site_profiles.find(global_id.model_id)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::DastSiteProfiles::Delete do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
let(:full_path) { project.full_path }
let!(:dast_site_profile) { create(:dast_site_profile, project: project) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
describe '#resolve' do
subject do
mutation.resolve(
full_path: full_path,
id: dast_site_profile.to_global_id
)
end
context 'when on demand scan feature is enabled' do
context 'when the project does not exist' do
let(:full_path) { SecureRandom.hex }
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a maintainer' do
it 'has no errors' do
project.add_maintainer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a developer' do
it 'has no errors' do
project.add_developer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a reporter' do
it 'raises an exception' do
project.add_reporter(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is a guest' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do
before do
project.add_developer(user)
end
it 'deletes the dast_site_profile' do
expect { subject }.to change { DastSiteProfile.count }.by(-1)
end
context 'when there is an issue deleting the dast_site_profile' do
it 'returns an error' do
allow(mutation).to receive(:find_dast_site_profile).and_return(dast_site_profile)
allow(dast_site_profile).to receive(:destroy).and_return(false)
dast_site_profile.errors.add(:name, 'is weird')
expect(subject[:errors]).to include('Name is weird')
end
end
context 'when on demand scan feature is not enabled' do
it 'raises an exception' do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a DAST Site Profile' do
include GraphqlHelpers
let(:project) { create(:project) }
let(:current_user) { create(:user) }
let(:full_path) { project.full_path }
let!(:dast_site_profile) { create(:dast_site_profile, project: project) }
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: full_path,
id: dast_site_profile.to_global_id.to_s
)
end
def mutation_response
graphql_mutation_response(:dast_site_profile_delete)
end
subject { post_graphql_mutation(mutation, current_user: current_user) }
context 'when a user does not have access to the project' do
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
context 'when a user does not have access to run a dast scan on the project' do
before do
project.add_guest(current_user)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
context 'when a user has access to run a dast scan on the project' do
before do
project.add_developer(current_user)
end
it 'returns an empty errors array' do
subject
expect(mutation_response["errors"]).to be_empty
end
it 'deletes the dast_site_profile' do
expect { subject }.to change { DastSiteProfile.count }.by(-1)
end
context 'when there is an issue deleting the dast_site_profile' do
before do
mutation_klass = Mutations::DastSiteProfiles::Delete
allow_any_instance_of(mutation_klass).to receive(:find_dast_site_profile).and_return(dast_site_profile)
allow(dast_site_profile).to receive(:destroy).and_return(false)
dast_site_profile.errors.add(:name, 'is weird')
end
it_behaves_like 'a mutation that returns errors in the response', errors: ['Name is weird']
end
context 'when the dast_site_profile does not exist' do
before do
dast_site_profile.destroy!
end
it_behaves_like 'a mutation that returns top-level errors', errors: ['Internal server error']
end
context 'when wrong type of global id is passed' do
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: full_path,
id: dast_site_profile.dast_site.to_global_id.to_s
)
end
it 'returns a top-level error' do
subject
expect(graphql_errors.dig(0, 'message')).to include('does not represent an instance of DastSiteProfile')
end
end
context 'when the dast_site_profile belongs to a different project' do
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: create(:project).full_path,
id: dast_site_profile.to_global_id.to_s
)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
end
context 'when on demand scan feature is disabled' do
before do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
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