From d054075713dd54c1a00a3d9de281be1a8fb686a1 Mon Sep 17 00:00:00 2001 From: Andy Schoenen <asoiron@gitlab.com> Date: Mon, 22 Jul 2024 15:36:23 +0000 Subject: [PATCH] Add CreateSecurityPolicyProjectAsync mutation This mutation allows to trigger the creation of a security policy project. We also introduce the securityPolicyProjectCreated subscription. It will be triggered as soon as the project is created. Changelog: added EE: true --- config/sidekiq_queues.yml | 2 + doc/api/graphql/reference/index.md | 45 +++++++ ee/app/graphql/ee/graphql_triggers.rb | 8 ++ ee/app/graphql/ee/types/mutation_type.rb | 1 + ee/app/graphql/ee/types/subscription_type.rb | 5 + .../create_security_policy_project_async.rb | 29 +++++ .../security/policy_project_created.rb | 29 +++++ .../security/policy_project_created.rb | 25 ++++ .../policy_project_created_status_enum.rb | 15 +++ ee/app/workers/all_queues.yml | 9 ++ .../create_security_policy_project_worker.rb | 51 ++++++++ ee/spec/graphql/graphql_triggers_spec.rb | 21 ++++ ...eate_security_policy_project_async_spec.rb | 91 ++++++++++++++ .../create_security_policy_project_spec.rb | 2 +- .../security/policy_project_created_spec.rb | 85 +++++++++++++ .../security/policy_project_created/helper.rb | 42 +++++++ ...ate_security_policy_project_worker_spec.rb | 116 ++++++++++++++++++ 17 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 ee/app/graphql/mutations/security_policy/create_security_policy_project_async.rb create mode 100644 ee/app/graphql/subscriptions/security/policy_project_created.rb create mode 100644 ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created.rb create mode 100644 ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created_status_enum.rb create mode 100644 ee/app/workers/security/create_security_policy_project_worker.rb create mode 100644 ee/spec/graphql/mutations/security_policy/create_security_policy_project_async_spec.rb create mode 100644 ee/spec/graphql/subscriptions/security/policy_project_created_spec.rb create mode 100644 ee/spec/support/helpers/graphql/subscriptions/security/policy_project_created/helper.rb create mode 100644 ee/spec/workers/security/create_security_policy_project_worker_spec.rb diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 7849507b10c6d..ba820a4eaa697 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -729,6 +729,8 @@ - 1 - - search_zoekt_project_transfer - 1 +- - security_create_security_policy_project + - 1 - - security_delete_orchestration_configuration - 1 - - security_generate_policy_violation_comment diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index eadd055e207a1..36300dbe099e7 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -8644,6 +8644,30 @@ Input type: `SecurityPolicyProjectCreateInput` | <a id="mutationsecuritypolicyprojectcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationsecuritypolicyprojectcreateproject"></a>`project` | [`Project`](#project) | Security Policy Project that was created. | +### `Mutation.securityPolicyProjectCreateAsync` + +**Status:** Alpha. Creates and assigns a security policy project for the given project or group (`full_path`) async. + +DETAILS: +**Introduced** in GitLab 17.3. +**Status**: Experiment. + +Input type: `SecurityPolicyProjectCreateAsyncInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationsecuritypolicyprojectcreateasyncclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationsecuritypolicyprojectcreateasyncfullpath"></a>`fullPath` | [`String!`](#string) | Full path of the project or group. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationsecuritypolicyprojectcreateasyncclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationsecuritypolicyprojectcreateasyncerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.securityPolicyProjectUnassign` Unassigns the security policy project for the given project (`full_path`). @@ -27990,6 +28014,18 @@ Represents policy violation for `license_scanning` report_type. | <a id="policylicensescanningviolationlicense"></a>`license` | [`String!`](#string) | License name. | | <a id="policylicensescanningviolationurl"></a>`url` | [`String`](#string) | URL of the license. | +### `PolicyProjectCreated` + +Response of security policy creation. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="policyprojectcreatederrormessage"></a>`errorMessage` | [`String`](#string) | Error message in case status is :error. | +| <a id="policyprojectcreatedproject"></a>`project` | [`Project`](#project) | Security Policy Project that was created. | +| <a id="policyprojectcreatedstatus"></a>`status` | [`PolicyProjectCreatedStatus`](#policyprojectcreatedstatus) | Status of the creation of the security policy project. | + ### `PolicyScanFindingViolation` Represents policy violation for `scan_finding` report_type. @@ -36004,6 +36040,15 @@ Pipeline security report finding sort values. | <a id="pipelinestatusenumwaiting_for_callback"></a>`WAITING_FOR_CALLBACK` | Pipeline is waiting for an external action. | | <a id="pipelinestatusenumwaiting_for_resource"></a>`WAITING_FOR_RESOURCE` | A resource (for example, a runner) that the pipeline requires to run is unavailable. | +### `PolicyProjectCreatedStatus` + +Types of security policy project created status. + +| Value | Description | +| ----- | ----------- | +| <a id="policyprojectcreatedstatuserror"></a>`ERROR` | Creating the security policy project faild. | +| <a id="policyprojectcreatedstatussuccess"></a>`SUCCESS` | Creating the security policy project was successful. | + ### `PolicyViolationErrorType` | Value | Description | diff --git a/ee/app/graphql/ee/graphql_triggers.rb b/ee/app/graphql/ee/graphql_triggers.rb index 96957d5d79cbd..6105f39ff190b 100644 --- a/ee/app/graphql/ee/graphql_triggers.rb +++ b/ee/app/graphql/ee/graphql_triggers.rb @@ -53,6 +53,14 @@ def self.workflow_events_updated(checkpoint) ::GitlabSchema.subscriptions.trigger(:workflow_events_updated, { workflow_id: checkpoint.workflow.to_gid }, checkpoint) end + + def self.security_policy_project_created(container, status, security_policy_project, error_message) + ::GitlabSchema.subscriptions.trigger( + :security_policy_project_created, + { full_path: container.full_path }, + { status: status, error_message: error_message, project: security_policy_project } + ) + end end end end diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb index 0a41d13590892..01fe14f16b783 100644 --- a/ee/app/graphql/ee/types/mutation_type.rb +++ b/ee/app/graphql/ee/types/mutation_type.rb @@ -115,6 +115,7 @@ def self.authorization_scopes mount_mutation ::Mutations::SecurityPolicy::AssignSecurityPolicyProject mount_mutation ::Mutations::SecurityPolicy::UnassignSecurityPolicyProject mount_mutation ::Mutations::SecurityPolicy::CreateSecurityPolicyProject + mount_mutation ::Mutations::SecurityPolicy::CreateSecurityPolicyProjectAsync, alpha: { milestone: '17.3' } mount_mutation ::Mutations::Security::CiConfiguration::ConfigureDependencyScanning mount_mutation ::Mutations::Security::CiConfiguration::ConfigureContainerScanning mount_mutation ::Mutations::Security::TrainingProviderUpdate diff --git a/ee/app/graphql/ee/types/subscription_type.rb b/ee/app/graphql/ee/types/subscription_type.rb index decad5529cbf0..29f572bdc9178 100644 --- a/ee/app/graphql/ee/types/subscription_type.rb +++ b/ee/app/graphql/ee/types/subscription_type.rb @@ -35,6 +35,11 @@ def self.authorization_scopes field :workflow_events_updated, subscription: ::Subscriptions::Ai::DuoWorkflows::WorkflowEventsUpdated, null: true, description: 'Triggered when the checkpoints/events of a workflow is updated.' + + field :security_policy_project_created, + subscription: Subscriptions::Security::PolicyProjectCreated, null: true, + description: 'Triggered when the security policy project is created for a specific group or project.', + alpha: { milestone: '17.3' } end end end diff --git a/ee/app/graphql/mutations/security_policy/create_security_policy_project_async.rb b/ee/app/graphql/mutations/security_policy/create_security_policy_project_async.rb new file mode 100644 index 0000000000000..074a66199ed07 --- /dev/null +++ b/ee/app/graphql/mutations/security_policy/create_security_policy_project_async.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Mutations + module SecurityPolicy # rubocop:disable Gitlab/BoundedContexts -- Matches CreateSecurityPolicyProject and should be fixed together + class CreateSecurityPolicyProjectAsync < BaseMutation + graphql_name 'SecurityPolicyProjectCreateAsync' + description '**Status:** Alpha. ' \ + 'Creates and assigns a security policy project for the given project or group (`full_path`) async' + + include FindsProjectOrGroupForSecurityPolicies + + authorize :update_security_orchestration_policy_project + + argument :full_path, GraphQL::Types::String, + required: true, + description: 'Full path of the project or group.' + + def resolve(args) + project_or_group = authorized_find!(**args) + + ::Security::CreateSecurityPolicyProjectWorker.perform_async(project_or_group.full_path, current_user.id) # rubocop:disable CodeReuse/Worker -- This is meant to be a background job + + { + errors: [] + } + end + end + end +end diff --git a/ee/app/graphql/subscriptions/security/policy_project_created.rb b/ee/app/graphql/subscriptions/security/policy_project_created.rb new file mode 100644 index 0000000000000..68f427dea2e69 --- /dev/null +++ b/ee/app/graphql/subscriptions/security/policy_project_created.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Subscriptions # rubocop:disable Gitlab/BoundedContexts -- Subscriptions is a concept of GraphQL + module Security + class PolicyProjectCreated < ::Subscriptions::BaseSubscription + include Gitlab::Graphql::Laziness + + payload_type Types::GitlabSubscriptions::Security::PolicyProjectCreated + + argument :full_path, GraphQL::Types::String, + required: true, + description: 'Full path of the project or group.' + + def authorized?(args) + container = Routable.find_by_full_path(args[:full_path]) + + Ability.allowed?(current_user, :update_security_orchestration_policy_project, container) + end + + def update(_) + { + project: object[:project], + status: object[:status], + error_message: object[:error_message] + } + end + end + end +end diff --git a/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created.rb b/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created.rb new file mode 100644 index 0000000000000..2da92d846633f --- /dev/null +++ b/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module GitlabSubscriptions + module Security + # rubocop:disable Graphql/AuthorizeTypes -- Authorization will be handled in subscription + class PolicyProjectCreated < ::Types::BaseObject + graphql_name 'PolicyProjectCreated' + description 'Response of security policy creation.' + + field :project, Types::ProjectType, + null: true, + description: 'Security Policy Project that was created.' + + field :status, Types::GitlabSubscriptions::Security::PolicyProjectCreatedStatusEnum, + description: 'Status of the creation of the security policy project.' + + field :error_message, GraphQL::Types::String, + null: true, + description: 'Error message in case status is :error.' + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created_status_enum.rb b/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created_status_enum.rb new file mode 100644 index 0000000000000..379ba6109a310 --- /dev/null +++ b/ee/app/graphql/types/gitlab_subscriptions/security/policy_project_created_status_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module GitlabSubscriptions + module Security + class PolicyProjectCreatedStatusEnum < ::Types::BaseEnum + graphql_name 'PolicyProjectCreatedStatus' + description 'Types of security policy project created status.' + + value 'SUCCESS', value: :success, description: 'Creating the security policy project was successful.' + value 'ERROR', value: :error, description: 'Creating the security policy project faild.' + end + end + end +end diff --git a/ee/app/workers/all_queues.yml b/ee/app/workers/all_queues.yml index fe247edacba07..cddb498b3df8f 100644 --- a/ee/app/workers/all_queues.yml +++ b/ee/app/workers/all_queues.yml @@ -2118,6 +2118,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: security_create_security_policy_project + :worker_name: Security::CreateSecurityPolicyProjectWorker + :feature_category: :security_policy_management + :has_external_dependencies: false + :urgency: :high + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: security_delete_orchestration_configuration :worker_name: Security::DeleteOrchestrationConfigurationWorker :feature_category: :security_policy_management diff --git a/ee/app/workers/security/create_security_policy_project_worker.rb b/ee/app/workers/security/create_security_policy_project_worker.rb new file mode 100644 index 0000000000000..015e1c33f1f2d --- /dev/null +++ b/ee/app/workers/security/create_security_policy_project_worker.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Security + class CreateSecurityPolicyProjectWorker + include ApplicationWorker + + feature_category :security_policy_management + data_consistency :sticky + urgency :high + deduplicate :until_executed # Avoid race condition in creating security policy projects + idempotent! + + def perform(project_or_group_path, current_user_id) + container = Routable.find_by_full_path(project_or_group_path) + user = User.find_by_id(current_user_id) + + errors = [].tap do |errors| + errors << 'Group or project not found.' if container.blank? + errors << 'User not found.' if user.blank? + end + + if errors.any? + error(errors.join(' '), container) + + return + end + + service_result = ::Security::SecurityOrchestrationPolicies::ProjectCreateService + .new(container: container, current_user: user) + .execute + + GraphqlTriggers.security_policy_project_created( + container, + service_result[:status], + service_result[:policy_project], + service_result[:message] + ) + end + + private + + def error(message, container = nil) + GraphqlTriggers.security_policy_project_created( + container, + :error, + nil, + message + ) + end + end +end diff --git a/ee/spec/graphql/graphql_triggers_spec.rb b/ee/spec/graphql/graphql_triggers_spec.rb index a57bfc0448cf2..ba8c75a51863e 100644 --- a/ee/spec/graphql/graphql_triggers_spec.rb +++ b/ee/spec/graphql/graphql_triggers_spec.rb @@ -153,4 +153,25 @@ ::GraphqlTriggers.workflow_events_updated(checkpoint) end end + + describe '.security_policy_project_created' do + subject(:trigger) do + described_class.security_policy_project_created(container, status, security_policy_project, error_message) + end + + let_it_be(:container) { create(:project) } + let_it_be(:security_policy_project) { create(:project) } + let(:status) { :success } + let(:error_message) { nil } + + it 'triggers the subscription' do + expect(GitlabSchema.subscriptions).to receive(:trigger).with( + :security_policy_project_created, + { full_path: container.full_path }, + { status: status, project: security_policy_project, error_message: error_message } + ) + + trigger + end + end end diff --git a/ee/spec/graphql/mutations/security_policy/create_security_policy_project_async_spec.rb b/ee/spec/graphql/mutations/security_policy/create_security_policy_project_async_spec.rb new file mode 100644 index 0000000000000..d142544d64d04 --- /dev/null +++ b/ee/spec/graphql/mutations/security_policy/create_security_policy_project_async_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProjectAsync, feature_category: :security_policy_management do + let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + + describe '#resolve' do + let_it_be(:owner) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:namespace) { create(:group) } + let_it_be(:project) { create(:project, namespace: namespace) } + + let(:current_user) { owner } + + subject(:resolve_mutation) { mutation.resolve(full_path: container.full_path) } + + shared_examples 'triggers the create security policy project worker' do + context 'when licensed feature is available' do + before do + stub_licensed_features(security_orchestration_policies: true) + end + + context 'when user is an owner of the container' do + let(:current_user) { owner } + + before_all do + namespace.add_owner(owner) + end + + it 'triggers the worker' do + expect(Security::CreateSecurityPolicyProjectWorker).to receive(:perform_async).with( + container.full_path, + current_user.id + ) + + result = resolve_mutation + + expect(result[:errors]).to be_empty + end + end + + context 'when user is not an owner' do + let(:current_user) { maintainer } + + before do + container.add_maintainer(maintainer) + end + + it 'raises exception' do + expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + context 'when feature is not licensed' do + before do + stub_licensed_features(security_orchestration_policies: false) + end + + it 'raises exception' do + expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + context 'when fullPath is not provided' do + subject(:resolve_mutation) { mutation.resolve({}) } + + before do + stub_licensed_features(security_orchestration_policies: true) + end + + it 'raises exception' do + expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + + context 'for project' do + let(:container) { project } + + it_behaves_like 'triggers the create security policy project worker' + end + + context 'for namespace' do + let(:container) { namespace } + + it_behaves_like 'triggers the create security policy project worker' + end + end +end diff --git a/ee/spec/graphql/mutations/security_policy/create_security_policy_project_spec.rb b/ee/spec/graphql/mutations/security_policy/create_security_policy_project_spec.rb index 3518b2ef96cda..bd1267d26fb34 100644 --- a/ee/spec/graphql/mutations/security_policy/create_security_policy_project_spec.rb +++ b/ee/spec/graphql/mutations/security_policy/create_security_policy_project_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do +RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject, feature_category: :security_policy_management do let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } describe '#resolve' do diff --git a/ee/spec/graphql/subscriptions/security/policy_project_created_spec.rb b/ee/spec/graphql/subscriptions/security/policy_project_created_spec.rb new file mode 100644 index 0000000000000..4be1d4727c597 --- /dev/null +++ b/ee/spec/graphql/subscriptions/security/policy_project_created_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Subscriptions::Security::PolicyProjectCreated, feature_category: :security_policy_management do + include GraphqlHelpers + include ::Graphql::Subscriptions::Security::PolicyProjectCreated::Helper + + let_it_be(:project) { create(:project) } + let_it_be(:security_policy_project) { create(:project) } + let_it_be(:user) { create(:user) } + + let(:current_user) { nil } + let(:subscribe) { security_policy_project_created_subscription(project, current_user) } + let(:status) { :success } + let(:error_message) { nil } + + let(:security_policy_project_created) do + graphql_dig_at(graphql_data(response[:result]), :securityPolicyProjectCreated) + end + + before_all do + project.add_owner(user) + end + + before do + stub_licensed_features(security_orchestration_policies: true) + + stub_const('GitlabSchema', Graphql::Subscriptions::ActionCable::MockGitlabSchema) + Graphql::Subscriptions::ActionCable::MockActionCable.clear_mocks + end + + subject(:response) do + subscription_response do + GraphqlTriggers.security_policy_project_created(project, status, security_policy_project, error_message) + end + end + + context 'when user is unauthorized' do + it 'does not receive any data' do + expect(response).to be_nil + end + end + + context 'when user is authorized' do + let(:current_user) { user } + + it 'does not receive the security policy project' do + created_response = security_policy_project_created + + expect(created_response['project']).to be_nil + expect(created_response['error_message']).to eq(nil) + expect(created_response['status']).to eq('SUCCESS') + end + + context 'and user has access to the security policy project' do + before_all do + security_policy_project.add_owner(user) + end + + it 'receives the security policy project' do + created_response = security_policy_project_created + + expect(created_response['project']['name']).to eq(security_policy_project.name) + expect(created_response['error_message']).to eq(nil) + expect(created_response['status']).to eq('SUCCESS') + end + + context 'and there is an error_message' do + let_it_be(:security_policy_project) { nil } + + let(:error_message) { 'Error' } + let(:status) { :error } + + it 'receives the error message' do + created_response = security_policy_project_created + + expect(created_response['project']).to eq(nil) + expect(created_response['errorMessage']).to eq('Error') + expect(created_response['status']).to eq('ERROR') + end + end + end + end +end diff --git a/ee/spec/support/helpers/graphql/subscriptions/security/policy_project_created/helper.rb b/ee/spec/support/helpers/graphql/subscriptions/security/policy_project_created/helper.rb new file mode 100644 index 0000000000000..27c1578bd9a58 --- /dev/null +++ b/ee/spec/support/helpers/graphql/subscriptions/security/policy_project_created/helper.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Graphql + module Subscriptions + module Security + module PolicyProjectCreated + module Helper + def subscription_response + subscription_channel = subscribe + yield + subscription_channel.mock_broadcasted_messages.first + end + + def security_policy_project_created_subscription(container, current_user) + mock_channel = Graphql::Subscriptions::ActionCable::MockActionCable.get_mock_channel + query = security_policy_project_created_subscription_query(container) + + GitlabSchema.execute(query, context: { current_user: current_user, channel: mock_channel }) + + mock_channel + end + + private + + def security_policy_project_created_subscription_query(container) + <<~SUBSCRIPTION + subscription { + securityPolicyProjectCreated(fullPath: \"#{container.full_path}\") { + project { + name + } + status + errorMessage + } + } + SUBSCRIPTION + end + end + end + end + end +end diff --git a/ee/spec/workers/security/create_security_policy_project_worker_spec.rb b/ee/spec/workers/security/create_security_policy_project_worker_spec.rb new file mode 100644 index 0000000000000..9530cc57f847f --- /dev/null +++ b/ee/spec/workers/security/create_security_policy_project_worker_spec.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CreateSecurityPolicyProjectWorker, "#perform", feature_category: :security_policy_management do + describe '#perform' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:user) { create(:user) } + + let(:user_id) { user.id } + let(:container_path) { project.full_path } + + subject(:run_worker) { described_class.new.perform(container_path, user_id) } + + before_all do + group.add_owner(user) + end + + shared_examples 'a worker that does not call the ProjectCreateService' do + it 'does not call the ProjectCreateService' do + expect(Security::SecurityOrchestrationPolicies::ProjectCreateService).not_to receive(:new) + end + end + + it 'calls the ProjectCreateService' do + expect_next_instance_of( + Security::SecurityOrchestrationPolicies::ProjectCreateService, + container: project, + current_user: user + ) do |service| + expect(service).to receive(:execute).and_call_original + end + + run_worker + end + + it 'triggers the security_policy_project_created GraphQL event' do + expect_next_instance_of( + Security::SecurityOrchestrationPolicies::ProjectCreateService, + container: project, + current_user: user + ) do |service| + expect(service).to receive(:execute).and_return({ status: :success, policy_project: 'a policy project' }) + end + + expect(GraphqlTriggers).to receive(:security_policy_project_created).with( + project, :success, "a policy project", nil) + + run_worker + end + + context 'when ProjectCreateService returns an error' do + specify do + expect_next_instance_of( + Security::SecurityOrchestrationPolicies::ProjectCreateService, + container: project, + current_user: user + ) do |service| + expect(service).to receive(:execute).and_return( + { status: :error, message: 'Security Policy project already exists.' } + ) + end + + expect(GraphqlTriggers).to receive(:security_policy_project_created).with( + project, :error, nil, 'Security Policy project already exists.' + ) + + run_worker + end + end + + context 'when user can\'t be found' do + let(:user_id) { non_existing_record_id } + + it_behaves_like 'a worker that does not call the ProjectCreateService' + + it 'triggers the subscription with an error' do + expect(GraphqlTriggers).to receive(:security_policy_project_created).with( + project, :error, nil, 'User not found.' + ) + + run_worker + end + end + + context 'when container can\'t be found' do + let(:container_path) { non_existing_record_id } + + it_behaves_like 'a worker that does not call the ProjectCreateService' + + it 'triggers the subscription with an error' do + expect(GraphqlTriggers).to receive(:security_policy_project_created).with( + nil, :error, nil, 'Group or project not found.' + ) + + run_worker + end + end + + context 'when container and user can\'t be found' do + let(:container_path) { non_existing_record_id } + let(:user_id) { non_existing_record_id } + + it_behaves_like 'a worker that does not call the ProjectCreateService' + + it 'triggers the subscription with an error' do + expect(GraphqlTriggers).to receive(:security_policy_project_created).with( + nil, :error, nil, 'Group or project not found. User not found.' + ) + + run_worker + end + end + end +end -- GitLab