From dadc459b8e1bdaa87cd97192132b3caa66602d52 Mon Sep 17 00:00:00 2001 From: Hitesh Raghuvanshi Date: Wed, 6 Nov 2024 22:11:53 +0530 Subject: [PATCH 1/4] Destroy mutation for compliance requirements Changelog: added EE: true --- doc/api/graphql/reference/index.md | 28 ++++++++ doc/user/compliance/audit_event_types.md | 1 + ee/app/graphql/ee/types/mutation_type.rb | 2 + .../compliance_requirements/destroy.rb | 27 ++++++++ .../compliance_requirement_policy.rb | 9 +++ .../destroy_service.rb | 50 ++++++++++++++ .../destroyed_compliance_requirement.yml | 10 +++ .../compliance_requirements/destroy_spec.rb | 68 +++++++++++++++++++ .../compliance_requirements/destroy_spec.rb | 64 +++++++++++++++++ .../destroy_service_spec.rb | 65 ++++++++++++++++++ locale/gitlab.pot | 9 +++ 11 files changed, 333 insertions(+) create mode 100644 ee/app/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy.rb create mode 100644 ee/app/policies/compliance_management/compliance_framework/compliance_requirement_policy.rb create mode 100644 ee/app/services/compliance_management/compliance_framework/compliance_requirements/destroy_service.rb create mode 100644 ee/config/audit_events/types/destroyed_compliance_requirement.yml create mode 100644 ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb create mode 100644 ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb create mode 100644 ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 69fbd45ac535cd..e50bde3fb7a620 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4689,6 +4689,28 @@ Input type: `DestroyComplianceFrameworkInput` | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.destroyComplianceRequirement` + +DETAILS: +**Introduced** in GitLab 17.6. +**Status**: Experiment. + +Input type: `DestroyComplianceRequirementInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `id` | [`ComplianceManagementComplianceFrameworkComplianceRequirementID!`](#compliancemanagementcomplianceframeworkcompliancerequirementid) | Global ID of the compliance requirement to destroy. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.destroyContainerRepository` Input type: `DestroyContainerRepositoryInput` @@ -41232,6 +41254,12 @@ Color represented as a hex code or named color. For example: "#fefefe". +### `ComplianceManagementComplianceFrameworkComplianceRequirementID` + +A `ComplianceManagementComplianceFrameworkComplianceRequirementID` is a global ID. It is encoded as a string. + +An example `ComplianceManagementComplianceFrameworkComplianceRequirementID` is: `"gid://gitlab/ComplianceManagement::ComplianceFramework::ComplianceRequirement/1"`. + ### `ComplianceManagementFrameworkID` A `ComplianceManagementFrameworkID` is a global ID. It is encoded as a string. diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 41be7a31163360..60ba4ebf5e2d4a 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -149,6 +149,7 @@ Audit event types belong to the following product categories. | [`created_compliance_requirement`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169485) | Triggered when a requirement is added to a compliance framework | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/470695) | Group | | [`delete_status_check`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84624) | Triggered when an external status check is deleted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/355805) | Project | | [`destroy_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74292) | Triggered when a compliance framework is successfully deleted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/340649) | Group | +| [`destroyed_compliance_requirement`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170380) | Triggered when a compliance framework requirement is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/470695) | Group | | [`email_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | Triggered when an email is created | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`email_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | Triggered when an email is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`external_status_check_name_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106095) | Triggered when the name of an external status check is updated | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369333) | Project | diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb index efe930e5b7145f..50e2b9cebbf33e 100644 --- a/ee/app/graphql/ee/types/mutation_type.rb +++ b/ee/app/graphql/ee/types/mutation_type.rb @@ -255,6 +255,8 @@ def self.authorization_scopes mount_mutation ::Mutations::Projects::TargetBranchRules::Destroy mount_mutation ::Mutations::ComplianceManagement::ComplianceFramework::ComplianceRequirements::Create, experiment: { milestone: '17.6' } + mount_mutation ::Mutations::ComplianceManagement::ComplianceFramework::ComplianceRequirements::Destroy, + experiment: { milestone: '17.6' } prepend(Types::DeprecatedMutations) end diff --git a/ee/app/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy.rb b/ee/app/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy.rb new file mode 100644 index 00000000000000..e6f5aed25a1989 --- /dev/null +++ b/ee/app/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module ComplianceManagement + module ComplianceFramework + module ComplianceRequirements + class Destroy < BaseMutation + graphql_name 'DestroyComplianceRequirement' + authorize :admin_compliance_framework + + argument :id, ::Types::GlobalIDType[::ComplianceManagement::ComplianceFramework::ComplianceRequirement], + required: true, + description: 'Global ID of the compliance requirement to destroy.' + + def resolve(id:) + requirement = authorized_find!(id: id) + + result = ::ComplianceManagement::ComplianceFramework::ComplianceRequirements::DestroyService.new( + requirement: requirement, current_user: current_user).execute + + { errors: result.success? ? [] : Array.wrap(result.message) } + end + end + end + end + end +end diff --git a/ee/app/policies/compliance_management/compliance_framework/compliance_requirement_policy.rb b/ee/app/policies/compliance_management/compliance_framework/compliance_requirement_policy.rb new file mode 100644 index 00000000000000..278f97a87c616c --- /dev/null +++ b/ee/app/policies/compliance_management/compliance_framework/compliance_requirement_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ComplianceManagement + module ComplianceFramework + class ComplianceRequirementPolicy < BasePolicy + delegate { @subject.framework } + end + end +end diff --git a/ee/app/services/compliance_management/compliance_framework/compliance_requirements/destroy_service.rb b/ee/app/services/compliance_management/compliance_framework/compliance_requirements/destroy_service.rb new file mode 100644 index 00000000000000..f6c77494eca267 --- /dev/null +++ b/ee/app/services/compliance_management/compliance_framework/compliance_requirements/destroy_service.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ComplianceManagement + module ComplianceFramework + module ComplianceRequirements + class DestroyService < BaseService + attr_reader :current_user, :requirement + + def initialize(requirement:, current_user:) + @requirement = requirement + @current_user = current_user + end + + def execute + return ServiceResponse.error(message: _('Not permitted to destroy requirement')) unless permitted? + + requirement.destroy ? success : error + end + + private + + def permitted? + can? current_user, :admin_compliance_framework, requirement.framework + end + + def success + audit_destroy + + ServiceResponse.success(message: _('Compliance requirement successfully deleted')) + end + + def audit_destroy + audit_context = { + name: 'destroyed_compliance_requirement', + author: current_user, + scope: requirement.framework.namespace, + target: requirement, + message: "Destroyed compliance requirement #{requirement.name}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + + def error + ServiceResponse.error(message: _('Failed to destroy compliance requirement'), payload: requirement.errors) + end + end + end + end +end diff --git a/ee/config/audit_events/types/destroyed_compliance_requirement.yml b/ee/config/audit_events/types/destroyed_compliance_requirement.yml new file mode 100644 index 00000000000000..2d22775e0be3b0 --- /dev/null +++ b/ee/config/audit_events/types/destroyed_compliance_requirement.yml @@ -0,0 +1,10 @@ +--- +name: destroyed_compliance_requirement +description: Triggered when a compliance framework requirement is destroyed +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/470695 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170380 +milestone: '17.6' +feature_category: compliance_management +saved_to_database: true +streamed: true +scope: [Group] diff --git a/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb b/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb new file mode 100644 index 00000000000000..3f660a8f7ab021 --- /dev/null +++ b/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::ComplianceManagement::ComplianceFramework::ComplianceRequirements::Destroy, + feature_category: :compliance_management do + include GraphqlHelpers + + let_it_be(:namespace) { create(:group) } + let_it_be(:framework) { create(:compliance_framework, namespace: namespace) } + let_it_be(:requirement) { create(:compliance_requirement, framework: framework) } + + let_it_be(:current_user) { create(:user) } + let(:mutation) { described_class.new(object: nil, context: query_context, field: nil) } + + subject { mutation.resolve(id: global_id_of(requirement)) } + + before_all do + namespace.add_owner(current_user) + end + + shared_examples 'a compliance requirement that cannot be found' do + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + shared_examples 'one compliance requirement was destroyed' do + it 'destroys a compliance requirement' do + expect { subject }.to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + end + + it 'expects zero errors in the response' do + expect(subject[:errors]).to be_empty + end + end + + context 'when feature is unlicensed' do + before do + stub_licensed_features(custom_compliance_frameworks: false) + end + + it_behaves_like 'a compliance requirement that cannot be found' + end + + context 'when feature is licensed' do + before do + stub_licensed_features(custom_compliance_frameworks: true) + end + + context 'when current_user is namespace owner' do + it_behaves_like 'one compliance requirement was destroyed' + end + + context 'when current_user is group owner' do + let_it_be(:group) { create(:group) } + let_it_be(:current_user) { create(:user) } + let_it_be(:framework) { create(:compliance_framework, namespace: group) } + let_it_be(:requirement) { create(:compliance_requirement, framework: framework) } + + before_all do + group.add_owner(current_user) + end + + it_behaves_like 'one compliance requirement was destroyed' + end + end +end diff --git a/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb b/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb new file mode 100644 index 00000000000000..94c4c18fa54304 --- /dev/null +++ b/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Destroy a Compliance Requirement', feature_category: :compliance_management do + include GraphqlHelpers + + let_it_be(:namespace) { create(:group) } + let_it_be(:framework) { create(:compliance_framework, namespace: namespace) } + let_it_be(:requirement) { create(:compliance_requirement, framework: framework) } + + let_it_be(:current_user) { create(:user) } + let(:mutation) { graphql_mutation(:destroy_compliance_requirement, { id: global_id_of(requirement) }) } + + subject(:mutate) { post_graphql_mutation(mutation, current_user: current_user) } + + def mutation_response + graphql_mutation_response(:destroy_compliance_requirement) + end + + context 'when feature is unlicensed' do + before do + stub_licensed_features(custom_compliance_frameworks: false) + end + + it 'does not destroy a compliance requirement' do + expect { mutate }.not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + + context 'when licensed' do + before do + stub_licensed_features(custom_compliance_frameworks: true) + end + + context 'when current_user is namespace owner' do + before_all do + namespace.add_owner(current_user) + end + + it 'has no errors' do + mutate + + expect(mutation_response['errors']).to be_empty + end + + it 'destroys a compliance requirement' do + expect { mutate }.to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + end + end + + context 'when current_user is not namespace owner' do + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it 'does not destroy a compliance requirement' do + expect { mutate }.not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } + end + end + end +end diff --git a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb new file mode 100644 index 00000000000000..6bb17220c50357 --- /dev/null +++ b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ComplianceManagement::ComplianceFramework::ComplianceRequirements::DestroyService, + feature_category: :compliance_management do + let_it_be_with_refind(:namespace) { create(:group) } + let_it_be_with_refind(:framework) { create(:compliance_framework, namespace: namespace) } + let_it_be_with_refind(:requirement) { create(:compliance_requirement, framework: framework) } + let_it_be(:user) { create(:user, owner_of: namespace) } + + context 'when feature is disabled' do + before do + stub_licensed_features(custom_compliance_frameworks: false) + end + + subject(:service) { described_class.new(requirement: requirement, current_user: user) } + + it 'does not destroy the compliance requirement' do + expect { service.execute } + .not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } + end + + it 'is unsuccessful' do + expect(service.execute.success?).to be false + end + end + + context 'when feature is enabled' do + before do + stub_licensed_features(custom_compliance_frameworks: true) + end + + context 'when current user is namespace owner' do + subject(:service) { described_class.new(requirement: requirement, current_user: user) } + + it 'destroys the compliance requirement' do + expect { service.execute } + .to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + end + + it 'is successful' do + expect(service.execute.success?).to be true + end + + it 'audits the destruction' do + expect { service.execute } + .to change { AuditEvent.where("details LIKE ?", "%destroyed_compliance_requirement%").count }.by(1) + end + end + + context 'when current user is not the namespace owner' do + subject(:service) { described_class.new(requirement: requirement, current_user: create(:user)) } + + it 'does not destroy the compliance requirement' do + expect { service.execute } + .not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } + end + + it 'is unsuccessful' do + expect(service.execute.success?).to be false + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c796bf5f4bdfca..ecd997be206e0d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14157,6 +14157,9 @@ msgstr "" msgid "Compliance frameworks" msgstr "" +msgid "Compliance requirement successfully deleted" +msgstr "" + msgid "ComplianceChainOfCustody| Chain of custody export" msgstr "" @@ -23131,6 +23134,9 @@ msgstr "" msgid "Failed to deploy to" msgstr "" +msgid "Failed to destroy compliance requirement" +msgstr "" + msgid "Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later." msgstr "" @@ -36797,6 +36803,9 @@ msgstr "" msgid "Not permitted to destroy framework" msgstr "" +msgid "Not permitted to destroy requirement" +msgstr "" + msgid "Not permitted to reset user feed token" msgstr "" -- GitLab From 953fafc6a15a6cb46f2de774966191f2aa060c30 Mon Sep 17 00:00:00 2001 From: Harsimar Sandhu Date: Wed, 20 Nov 2024 17:43:01 +0000 Subject: [PATCH 2/4] Apply 2 suggestion(s) to 2 file(s) Co-authored-by: Rutger Wessels --- .../compliance_requirements/destroy_spec.rb | 4 +++- .../compliance_requirements/destroy_service_spec.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb b/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb index 3f660a8f7ab021..34c70781db8ce9 100644 --- a/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb +++ b/ee/spec/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb @@ -27,7 +27,9 @@ shared_examples 'one compliance requirement was destroyed' do it 'destroys a compliance requirement' do - expect { subject }.to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + expect { subject }.to change { + ComplianceManagement::ComplianceFramework::ComplianceRequirement.exists?(id: requirement.id) + }.from(true).to(false) end it 'expects zero errors in the response' do diff --git a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb index 6bb17220c50357..3d7a7c2b8eedad 100644 --- a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb +++ b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb @@ -22,7 +22,9 @@ end it 'is unsuccessful' do - expect(service.execute.success?).to be false + result = service.execute + expect(result.success?).to be false + expect(result.message).to eq _('Failed to destroy compliance requirement') end end -- GitLab From 8de88c0acaf5e690001decd161bc6d9c138ed646 Mon Sep 17 00:00:00 2001 From: harsimarsandhu Date: Wed, 20 Nov 2024 23:47:49 +0530 Subject: [PATCH 3/4] Update specs and milestone --- doc/api/graphql/reference/index.md | 2 +- doc/user/compliance/audit_event_types.md | 2 +- ee/app/graphql/ee/types/mutation_type.rb | 2 +- .../destroyed_compliance_requirement.yml | 2 +- .../compliance_requirements/destroy_spec.rb | 4 +- .../destroy_service_spec.rb | 69 ++++++++++++++----- 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e50bde3fb7a620..80120964dffad1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4692,7 +4692,7 @@ Input type: `DestroyComplianceFrameworkInput` ### `Mutation.destroyComplianceRequirement` DETAILS: -**Introduced** in GitLab 17.6. +**Introduced** in GitLab 17.7. **Status**: Experiment. Input type: `DestroyComplianceRequirementInput` diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 60ba4ebf5e2d4a..6bb9ad7f500e99 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -149,7 +149,7 @@ Audit event types belong to the following product categories. | [`created_compliance_requirement`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169485) | Triggered when a requirement is added to a compliance framework | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/470695) | Group | | [`delete_status_check`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84624) | Triggered when an external status check is deleted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/355805) | Project | | [`destroy_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74292) | Triggered when a compliance framework is successfully deleted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/340649) | Group | -| [`destroyed_compliance_requirement`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170380) | Triggered when a compliance framework requirement is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/470695) | Group | +| [`destroyed_compliance_requirement`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170380) | Triggered when a compliance framework requirement is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/470695) | Group | | [`email_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | Triggered when an email is created | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`email_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | Triggered when an email is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`external_status_check_name_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106095) | Triggered when the name of an external status check is updated | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369333) | Project | diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb index 50e2b9cebbf33e..f187eba8a62745 100644 --- a/ee/app/graphql/ee/types/mutation_type.rb +++ b/ee/app/graphql/ee/types/mutation_type.rb @@ -256,7 +256,7 @@ def self.authorization_scopes mount_mutation ::Mutations::ComplianceManagement::ComplianceFramework::ComplianceRequirements::Create, experiment: { milestone: '17.6' } mount_mutation ::Mutations::ComplianceManagement::ComplianceFramework::ComplianceRequirements::Destroy, - experiment: { milestone: '17.6' } + experiment: { milestone: '17.7' } prepend(Types::DeprecatedMutations) end diff --git a/ee/config/audit_events/types/destroyed_compliance_requirement.yml b/ee/config/audit_events/types/destroyed_compliance_requirement.yml index 2d22775e0be3b0..82ee7bb03d85c5 100644 --- a/ee/config/audit_events/types/destroyed_compliance_requirement.yml +++ b/ee/config/audit_events/types/destroyed_compliance_requirement.yml @@ -3,7 +3,7 @@ name: destroyed_compliance_requirement description: Triggered when a compliance framework requirement is destroyed introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/470695 introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170380 -milestone: '17.6' +milestone: '17.7' feature_category: compliance_management saved_to_database: true streamed: true diff --git a/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb b/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb index 94c4c18fa54304..985d5e93ed5577 100644 --- a/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb +++ b/ee/spec/requests/api/graphql/mutations/compliance_management/compliance_framework/compliance_requirements/destroy_spec.rb @@ -48,7 +48,9 @@ def mutation_response end it 'destroys a compliance requirement' do - expect { mutate }.to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + expect { mutate }.to change { + ComplianceManagement::ComplianceFramework::ComplianceRequirement.exists?(id: requirement.id) + }.from(true).to(false) end end diff --git a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb index 3d7a7c2b8eedad..59b5bcb345d04a 100644 --- a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb +++ b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb @@ -7,15 +7,10 @@ let_it_be_with_refind(:namespace) { create(:group) } let_it_be_with_refind(:framework) { create(:compliance_framework, namespace: namespace) } let_it_be_with_refind(:requirement) { create(:compliance_requirement, framework: framework) } - let_it_be(:user) { create(:user, owner_of: namespace) } - - context 'when feature is disabled' do - before do - stub_licensed_features(custom_compliance_frameworks: false) - end - - subject(:service) { described_class.new(requirement: requirement, current_user: user) } + let_it_be(:owner) { create(:user, owner_of: namespace) } + let_it_be(:non_owner) { create(:user) } + shared_examples 'unsuccessful destruction' do |error_message| it 'does not destroy the compliance requirement' do expect { service.execute } .not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } @@ -23,45 +18,81 @@ it 'is unsuccessful' do result = service.execute + expect(result.success?).to be false - expect(result.message).to eq _('Failed to destroy compliance requirement') + expect(result.message).to eq _(error_message) + end + + it 'does not audit the destruction' do + service.execute + + expect(::Gitlab::Audit::Auditor).not_to have_received(:audit) + end + end + + context 'when feature is disabled' do + before do + stub_licensed_features(custom_compliance_frameworks: false) + allow(::Gitlab::Audit::Auditor).to receive(:audit) + end + + context 'when current user is namespace owner' do + subject(:service) { described_class.new(requirement: requirement, current_user: owner) } + + it_behaves_like 'unsuccessful destruction', 'Not permitted to destroy requirement' + end + + context 'when current user is not the namespace owner' do + subject(:service) { described_class.new(requirement: requirement, current_user: non_owner) } + + it_behaves_like 'unsuccessful destruction', 'Not permitted to destroy requirement' end end context 'when feature is enabled' do before do stub_licensed_features(custom_compliance_frameworks: true) + allow(::Gitlab::Audit::Auditor).to receive(:audit) end context 'when current user is namespace owner' do - subject(:service) { described_class.new(requirement: requirement, current_user: user) } + subject(:service) { described_class.new(requirement: requirement, current_user: owner) } it 'destroys the compliance requirement' do - expect { service.execute } - .to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count }.by(-1) + expect { service.execute }.to change { + ComplianceManagement::ComplianceFramework::ComplianceRequirement.exists?(id: requirement.id) + }.from(true).to(false) end it 'is successful' do - expect(service.execute.success?).to be true + result = service.execute + + expect(result.success?).to be true + expect(result.message).to eq _('Compliance requirement successfully deleted') end it 'audits the destruction' do - expect { service.execute } - .to change { AuditEvent.where("details LIKE ?", "%destroyed_compliance_requirement%").count }.by(1) + service.execute + + expect(::Gitlab::Audit::Auditor).to have_received(:audit).with( + name: 'destroyed_compliance_requirement', + author: owner, + scope: requirement.framework.namespace, + target: requirement, + message: "Destroyed compliance requirement #{requirement.name}" + ) end end context 'when current user is not the namespace owner' do - subject(:service) { described_class.new(requirement: requirement, current_user: create(:user)) } + subject(:service) { described_class.new(requirement: requirement, current_user: non_owner) } it 'does not destroy the compliance requirement' do expect { service.execute } .not_to change { ComplianceManagement::ComplianceFramework::ComplianceRequirement.count } end - it 'is unsuccessful' do - expect(service.execute.success?).to be false - end + it_behaves_like 'unsuccessful destruction', 'Not permitted to destroy requirement' end end end -- GitLab From 4a031bf0e5dbc92dc4222481c056b2cf63a0a358 Mon Sep 17 00:00:00 2001 From: harsimarsandhu Date: Wed, 20 Nov 2024 23:53:38 +0530 Subject: [PATCH 4/4] Fix under coverage --- .../destroy_service_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb index 59b5bcb345d04a..a0d80907315f34 100644 --- a/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb +++ b/ee/spec/services/compliance_management/compliance_framework/compliance_requirements/destroy_service_spec.rb @@ -82,6 +82,21 @@ message: "Destroyed compliance requirement #{requirement.name}" ) end + + context 'when destruction fails' do + before do + allow_next_found_instance_of(ComplianceManagement::ComplianceFramework::ComplianceRequirement) do |instance| + allow(instance).to receive(:destroy).and_return(false) + end + end + + it 'is unsuccessful' do + result = service.execute + + expect(result.success?).to be false + expect(result.message).to eq _('Failed to destroy compliance requirement') + end + end end context 'when current user is not the namespace owner' do -- GitLab