diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index f43c7e75feeed6eba3649432a0b73ad679c36ec0..0cd16f9fdc30a77f7ba729adb7dc8e5aa9797a21 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -1,8 +1,9 @@
 # frozen_string_literal: true
 
 class Projects::TriggersController < Projects::ApplicationController
-  before_action :authorize_admin_build!
+  before_action :authorize_manage_trigger_on_project!
   before_action :authorize_manage_trigger!, except: [:index, :create]
+
   before_action :authorize_admin_trigger!, only: [:edit, :update]
   before_action :trigger, only: [:edit, :update, :destroy]
 
@@ -16,12 +17,18 @@ def index
   end
 
   def create
-    @trigger = project.triggers.create(trigger_params.merge(owner: current_user))
+    response = ::Ci::PipelineTriggers::CreateService.new(
+      project: project,
+      user: current_user,
+      description: trigger_params[:description]
+    ).execute
+
+    @trigger = response.payload[:trigger]
 
-    if @trigger.valid?
-      flash[:notice] = _('Trigger was created successfully.')
+    if response.success?
+      flash[:notice] = _('Trigger token was created successfully.')
     else
-      flash[:alert] = _('You could not create a new trigger.')
+      flash[:alert] = response.message
     end
 
     redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -54,6 +61,10 @@ def authorize_manage_trigger!
     access_denied! unless can?(current_user, :manage_trigger, trigger)
   end
 
+  def authorize_manage_trigger_on_project!
+    access_denied! unless can?(current_user, :manage_trigger, project)
+  end
+
   def authorize_admin_trigger!
     access_denied! unless can?(current_user, :admin_trigger, trigger)
   end
diff --git a/app/graphql/mutations/ci/pipeline_trigger/create.rb b/app/graphql/mutations/ci/pipeline_trigger/create.rb
index 042f9b26dd093c49a64e06fb1d44c2b2faef11d1..556f60373f7479a806107f3431c967e9993eb643 100644
--- a/app/graphql/mutations/ci/pipeline_trigger/create.rb
+++ b/app/graphql/mutations/ci/pipeline_trigger/create.rb
@@ -8,7 +8,7 @@ class Create < BaseMutation
 
         include FindsProject
 
-        authorize :admin_build
+        authorize :manage_trigger
 
         argument :project_path, GraphQL::Types::ID,
           required: true,
@@ -25,7 +25,10 @@ class Create < BaseMutation
         def resolve(project_path:, description:)
           project = authorized_find!(project_path)
 
-          trigger = project.triggers.create(owner: current_user, description: description)
+          response = ::Ci::PipelineTriggers::CreateService.new(project: project, user: current_user,
+            description: description).execute
+
+          trigger = response.payload[:trigger]
 
           {
             pipeline_trigger: trigger,
diff --git a/app/graphql/types/ci/pipeline_trigger_type.rb b/app/graphql/types/ci/pipeline_trigger_type.rb
index 81345c14ba0b72e0d7bcfa9bc75a6d6c52fb02eb..5c40e0862cd7fbfbd4868afe15828bc1c365d6e6 100644
--- a/app/graphql/types/ci/pipeline_trigger_type.rb
+++ b/app/graphql/types/ci/pipeline_trigger_type.rb
@@ -8,7 +8,7 @@ class PipelineTriggerType < BaseObject
       present_using ::Ci::TriggerPresenter
       connection_type_class Types::CountableConnectionType
 
-      authorize :admin_build
+      authorize :manage_trigger
 
       field :can_access_project, GraphQL::Types::Boolean,
         null: false,
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index e26f96a4b2b11102754aa084b144e77d7425bd46..f6cb66372b2932a47ad1fd18a6aa8a0d28267a44 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -9,9 +9,7 @@ class TriggerPolicy < BasePolicy
     with_score 0
     condition(:is_owner) { @user && @subject.owner_id == @user.id }
 
-    rule { ~can?(:admin_build) }.prevent :admin_trigger
+    rule { ~can?(:manage_trigger) }.prevent :admin_trigger
     rule { is_owner }.enable :admin_trigger
-
-    rule { can?(:admin_build) }.enable :manage_trigger
   end
 end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index a26758974d6150f74d83af967441560fe62987b1..01c9d94e0e69cb9f48e10e097e4a6b1dcd53ce9a 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -595,6 +595,8 @@ class ProjectPolicy < BasePolicy
     enable :read_import_error
   end
 
+  rule { can?(:admin_build) }.enable :manage_trigger
+
   rule { public_project & metrics_dashboard_allowed }.policy do
     enable :metrics_dashboard
   end
diff --git a/app/services/ci/pipeline_triggers/create_service.rb b/app/services/ci/pipeline_triggers/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..13c581e2988cb4a7ecf6917444543e61dde32fa9
--- /dev/null
+++ b/app/services/ci/pipeline_triggers/create_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Ci
+  module PipelineTriggers
+    class CreateService
+      include Gitlab::Allowable
+
+      attr_reader :project, :current_user, :description
+
+      def initialize(project:, user:, description:)
+        @project = project
+        @current_user = user
+        @description = description
+      end
+
+      def execute
+        unless can?(current_user, :manage_trigger, project)
+          return ServiceResponse.error(
+            message: _('The current user is not authorized to create a pipeline trigger token'),
+            payload: { trigger: nil },
+            reason: :forbidden
+          )
+        end
+
+        trigger = project.triggers.create(**create_params)
+
+        if trigger.present? && trigger.persisted?
+          ServiceResponse.success(payload: { trigger: trigger })
+        elsif trigger.present? && trigger.errors.any?
+          ServiceResponse.error(
+            message: trigger.errors.to_json,
+            payload: { trigger: trigger },
+            reason: :validation_error
+          )
+        else
+          raise "Unexpected Ci::Trigger creation failure. Description: #{@description}"
+        end
+      end
+
+      private
+
+      def create_params
+        { description: description, owner: current_user }
+      end
+    end
+  end
+end
diff --git a/app/services/ci/pipeline_triggers/destroy_service.rb b/app/services/ci/pipeline_triggers/destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..36b174a5d58fad80629b6d3a61244c8451b09cde
--- /dev/null
+++ b/app/services/ci/pipeline_triggers/destroy_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Ci
+  module PipelineTriggers
+    class DestroyService
+      include Gitlab::Allowable
+
+      attr_reader :project, :current_user, :description, :trigger
+
+      def initialize(user:, trigger:)
+        @current_user = user
+        @trigger = trigger
+      end
+
+      def execute
+        unless can?(current_user, :manage_trigger, trigger)
+          return ServiceResponse.error(
+            message: _('The current user is not authorized to manage the pipeline trigger token'),
+            reason: :forbidden
+          )
+        end
+
+        trigger.destroy
+
+        unless trigger.destroyed?
+          return ServiceResponse.error(
+            message: _('Attempted to destroy the pipeline trigger token but failed')
+          )
+        end
+
+        ServiceResponse.success
+      end
+    end
+  end
+end
diff --git a/app/services/ci/pipeline_triggers/update_service.rb b/app/services/ci/pipeline_triggers/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ffcd781ea8709e357d673bbec22fbfab34f711d7
--- /dev/null
+++ b/app/services/ci/pipeline_triggers/update_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Ci
+  module PipelineTriggers
+    class UpdateService
+      include Gitlab::Allowable
+
+      attr_reader :current_user, :description, :trigger
+
+      def initialize(user:, trigger:, description:)
+        @current_user = user
+        @description = description
+        @trigger = trigger
+      end
+
+      def execute
+        unless can?(current_user, :admin_trigger, trigger)
+          return ServiceResponse.error(
+            message: _('The current user is not authorized to update the pipeline trigger token'),
+            payload: { trigger: trigger },
+            reason: :forbidden
+          )
+        end
+
+        if trigger.update(**update_params)
+          ServiceResponse.success(payload: { trigger: trigger })
+        else
+          ServiceResponse.error(
+            message: _('Attempted to update the pipeline trigger token but failed'),
+            payload: { trigger: trigger }
+          )
+        end
+      end
+
+      private
+
+      def update_params
+        { description: description }
+      end
+    end
+  end
+end
diff --git a/app/services/environments/destroy_service.rb b/app/services/environments/destroy_service.rb
index f1530489a40e4a5b7cf4ed7204c616f1f601655d..db9faf8d8acc2805d6a748c78188f17a0f40e205 100644
--- a/app/services/environments/destroy_service.rb
+++ b/app/services/environments/destroy_service.rb
@@ -13,7 +13,7 @@ def execute(environment)
 
       unless environment.destroyed?
         return ServiceResponse.error(
-          message: 'Attemped to destroy the environment but failed'
+          message: 'Attempted to destroy the environment but failed'
         )
       end
 
diff --git a/app/services/environments/stop_service.rb b/app/services/environments/stop_service.rb
index 1b2e7ef3cf93919603896eabb9fca8f3a7eac07f..ee7ad1d814270c0bccf7f7e47e5aeb2aa2bcedca 100644
--- a/app/services/environments/stop_service.rb
+++ b/app/services/environments/stop_service.rb
@@ -20,7 +20,7 @@ def execute(environment)
 
       unless environment.saved_change_to_attribute?(:state)
         return ServiceResponse.error(
-          message: 'Attemped to stop the environment but failed to change the status',
+          message: 'Attempted to stop the environment but failed to change the status',
           payload: { environment: environment }
         )
       end
diff --git a/lib/api/ci/triggers.rb b/lib/api/ci/triggers.rb
index c202d188e43618f1c37c9ec8dec65f9132ddca79..c73812d63fb7c8c2e70108e78289e0206db94c2e 100644
--- a/lib/api/ci/triggers.rb
+++ b/lib/api/ci/triggers.rb
@@ -56,7 +56,7 @@ class Triggers < ::API::Base
           end
         end
 
-        desc 'Get triggers list' do
+        desc 'Get trigger tokens list' do
           success code: 200, model: Entities::Trigger
           failure [
             { code: 401, message: 'Unauthorized' },
@@ -79,7 +79,7 @@ class Triggers < ::API::Base
         end
         # rubocop: enable CodeReuse/ActiveRecord
 
-        desc 'Get specific trigger of a project' do
+        desc 'Get specific trigger token of a project' do
           success code: 200, model: Entities::Trigger
           failure [
             { code: 401, message: 'Unauthorized' },
@@ -88,7 +88,7 @@ class Triggers < ::API::Base
           ]
         end
         params do
-          requires :trigger_id, type: Integer, desc: 'The trigger ID', documentation: { example: 10 }
+          requires :trigger_id, type: Integer, desc: 'The trigger token ID', documentation: { example: 10 }
         end
         get ':id/triggers/:trigger_id' do
           authenticate!
@@ -100,7 +100,7 @@ class Triggers < ::API::Base
           present trigger, with: Entities::Trigger, current_user: current_user
         end
 
-        desc 'Create a trigger' do
+        desc 'Create a trigger token' do
           success code: 201, model: Entities::Trigger
           failure [
             { code: 400, message: 'Bad request' },
@@ -110,20 +110,26 @@ class Triggers < ::API::Base
           ]
         end
         params do
-          requires :description, type: String, desc: 'The trigger description',
-                                 documentation: { example: 'my trigger description' }
+          requires :description, type: String, desc: 'The trigger token description',
+                                 documentation: { example: 'my trigger token description' }
         end
         post ':id/triggers' do
           authenticate!
-          authorize! :admin_build, user_project
-
-          trigger = user_project.triggers.create(
-            declared_params(include_missing: false).merge(owner: current_user))
-
-          if trigger.valid?
-            present trigger, with: Entities::Trigger, current_user: current_user
+          authorize! :manage_trigger, user_project
+
+          response =
+            ::Ci::PipelineTriggers::CreateService.new(
+              project: user_project,
+              user: current_user,
+              description: declared_params(include_missing: false)[:description]
+            ).execute
+
+          if response.success?
+            present response.payload[:trigger], with: Entities::Trigger, current_user: current_user
+          elsif response.reason == :forbidden
+            forbidden!(response.message)
           else
-            render_validation_error!(trigger)
+            bad_request!(response.message)
           end
         end
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index eca95aac0ec7b619dca04c93ebb59b843f8f6f38..a7d81ee62a53f63696ed0926cf5061b6dc6d691e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6954,6 +6954,12 @@ msgstr ""
 msgid "Attempted sign in to %{host} using an incorrect verification code"
 msgstr ""
 
+msgid "Attempted to destroy the pipeline trigger token but failed"
+msgstr ""
+
+msgid "Attempted to update the pipeline trigger token but failed"
+msgstr ""
+
 msgid "Audit Events"
 msgstr ""
 
@@ -49479,12 +49485,18 @@ msgstr ""
 msgid "The current user is not authorized to access the job log."
 msgstr ""
 
+msgid "The current user is not authorized to create a pipeline trigger token"
+msgstr ""
+
 msgid "The current user is not authorized to create the pipeline schedule"
 msgstr ""
 
 msgid "The current user is not authorized to create the pipeline schedule variables"
 msgstr ""
 
+msgid "The current user is not authorized to manage the pipeline trigger token"
+msgstr ""
+
 msgid "The current user is not authorized to set pipeline schedule variables"
 msgstr ""
 
@@ -49494,6 +49506,9 @@ msgstr ""
 msgid "The current user is not authorized to update the pipeline schedule variables"
 msgstr ""
 
+msgid "The current user is not authorized to update the pipeline trigger token"
+msgstr ""
+
 msgid "The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data."
 msgstr ""
 
@@ -52267,13 +52282,13 @@ msgstr ""
 msgid "Trigger repository check"
 msgstr ""
 
-msgid "Trigger token:"
+msgid "Trigger token was created successfully."
 msgstr ""
 
-msgid "Trigger variables"
+msgid "Trigger token:"
 msgstr ""
 
-msgid "Trigger was created successfully."
+msgid "Trigger variables"
 msgstr ""
 
 msgid "Trigger was successfully updated."
@@ -56823,9 +56838,6 @@ msgstr ""
 msgid "You can’t edit files directly in this project. Fork this project and submit a merge request with your changes."
 msgstr ""
 
-msgid "You could not create a new trigger."
-msgstr ""
-
 msgid "You currently have no custom domains."
 msgstr ""
 
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 52df4bfece27a132f36aac299345bed5124c16e8..cfbcb47b1fc74dff1792b6f68d48cbb792ada095 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -40,11 +40,35 @@
         click_button 'Create pipeline trigger token'
 
         aggregate_failures 'display creation notice and trigger is created' do
-          expect(page.find('[data-testid="alert-info"]')).to have_content 'Trigger was created successfully.'
+          expect(page.find('[data-testid="alert-info"]')).to have_content 'Trigger token was created successfully.'
           expect(page.find('.triggers-list')).to have_content 'trigger desc'
           expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
         end
       end
+
+      context 'when trigger is not saved' do
+        before do
+          allow_next_instance_of(Ci::PipelineTriggers::CreateService) do |instance|
+            allow(instance).to receive(:execute).and_return(
+              ServiceResponse.error(
+                message: 'Validation error',
+                payload: { trigger: { description: ['is missing'] } },
+                reason: :validation_error
+              )
+            )
+          end
+        end
+
+        it 'trigger.errors has an error' do
+          click_button 'Add new token'
+          fill_in 'trigger_description', with: 'trigger desc'
+          click_button 'Create pipeline trigger token'
+
+          expect(page.find('.flash-container')).to(
+            have_content("Validation error")
+          )
+        end
+      end
     end
 
     describe 'edit trigger workflow' do
diff --git a/spec/graphql/mutations/environments/delete_spec.rb b/spec/graphql/mutations/environments/delete_spec.rb
index 4c2de3751bf231ac521e4913132aa6e5e2a1c267..49248408d2cffcc1a2ceeb044e57b1c17dc49ee7 100644
--- a/spec/graphql/mutations/environments/delete_spec.rb
+++ b/spec/graphql/mutations/environments/delete_spec.rb
@@ -56,7 +56,7 @@
       end
 
       it 'returns errors' do
-        expect(subject[:errors]).to include("Attemped to destroy the environment but failed")
+        expect(subject[:errors]).to include("Attempted to destroy the environment but failed")
       end
     end
 
diff --git a/spec/graphql/mutations/environments/stop_spec.rb b/spec/graphql/mutations/environments/stop_spec.rb
index 085d168bc534a2a29734479f109a4bcde5d23530..d61aff817259aa1d4b0641525bf23b7cf0d98013 100644
--- a/spec/graphql/mutations/environments/stop_spec.rb
+++ b/spec/graphql/mutations/environments/stop_spec.rb
@@ -42,7 +42,7 @@
         expect(subject)
           .to eq({
             environment: environment,
-            errors: ['Attemped to stop the environment but failed to change the status']
+            errors: ['Attempted to stop the environment but failed to change the status']
           })
       end
     end
diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb
index a6e50479963d22f0637908ee26b09d44005ddd45..4348325661b364fb5cdd48c03927c6d9fe7545f3 100644
--- a/spec/requests/api/ci/triggers_spec.rb
+++ b/spec/requests/api/ci/triggers_spec.rb
@@ -251,9 +251,49 @@
 
       context 'without required parameters' do
         it 'does not create trigger' do
-          post api("/projects/#{project.id}/triggers", user)
+          expect do
+            post api("/projects/#{project.id}/triggers", user)
+          end.not_to change { project.triggers.count }
+
+          expect(response).to have_gitlab_http_status(:bad_request)
+        end
+      end
+
+      context 'when the CreateService returns a permissions error' do
+        before do
+          failure_response = instance_double(ServiceResponse, success?: false, reason: :forbidden, message: "Permissions error message")
+
+          allow_next_instance_of(::Ci::PipelineTriggers::CreateService) do |instance|
+            allow(instance).to receive(:execute)
+                      .and_return(failure_response)
+          end
+        end
+
+        it 'returns forbidden' do
+          post api("/projects/#{project.id}/triggers", user),
+            params: { description: 'trigger' }
+
+          expect(response).to have_gitlab_http_status(:forbidden)
+          expect(json_response['message']).to eq('403 Forbidden - Permissions error message')
+        end
+      end
+
+      context 'when trigger fails to save' do
+        before do
+          failure_response = instance_double(ServiceResponse, success?: false, reason: :validation_error, message: "Unexpected Ci::Trigger creation failure")
+
+          allow_next_instance_of(::Ci::PipelineTriggers::CreateService) do |instance|
+            allow(instance).to receive(:execute)
+                      .and_return(failure_response)
+          end
+        end
+
+        it 'returns bad request' do
+          post api("/projects/#{project.id}/triggers", user),
+            params: { description: 'trigger' }
 
           expect(response).to have_gitlab_http_status(:bad_request)
+          expect(json_response['message']).to eq('400 Bad request - Unexpected Ci::Trigger creation failure')
         end
       end
     end
diff --git a/spec/services/ci/pipeline_triggers/create_service_spec.rb b/spec/services/ci/pipeline_triggers/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eed5c0b529e44970169a03fd2d98ee2e29f5c61a
--- /dev/null
+++ b/spec/services/ci/pipeline_triggers/create_service_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineTriggers::CreateService, feature_category: :continuous_integration do
+  let_it_be(:developer) { create(:user) }
+  let_it_be_with_reload(:user) { create(:user) }
+  let_it_be_with_reload(:project) { create(:project, :public) }
+
+  subject(:service) { described_class.new(project: project, user: user, description: description) }
+
+  before_all do
+    project.add_maintainer(user)
+    project.add_developer(developer)
+  end
+
+  describe "execute" do
+    context 'when user does not have permission' do
+      subject(:service) { described_class.new(project: project, user: developer, description: {}) }
+
+      it 'returns ServiceResponse.error' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.error?).to be(true)
+
+        error_message = _('The current user is not authorized to create a pipeline trigger token')
+        expect(response.message).to eq(error_message)
+        expect(response.errors).to match_array([error_message])
+      end
+    end
+
+    context 'when user has permission' do
+      let(:description) { "My snazzy pipeline trigger token" }
+
+      it 'creates a pipeline trigger token' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.success?).to be(true)
+        trigger = response.payload[:trigger]
+        expect(trigger).to be_a(Ci::Trigger)
+        expect(trigger).to be_persisted
+        expect(trigger.description).to eq(description)
+        expect(trigger.owner).to eq(user)
+        expect(trigger.project).to eq(project)
+      end
+
+      context 'when create fails' do
+        before do
+          allow(project.triggers).to receive(:create).and_return(nil)
+        end
+
+        it 'raises a RuntimeError' do
+          expect { service.execute }.to raise_error(RuntimeError, /Unexpected Ci::Trigger creation failure/)
+        end
+      end
+
+      context 'when trigger exists but has errors' do
+        before do
+          trigger_with_errors = instance_double('Ci::Trigger', present?: true, persisted?: false,
+            errors: ['Validation error'])
+          allow(project.triggers).to receive(:create).and_return(trigger_with_errors)
+        end
+
+        it 'returns ServiceResponse.error' do
+          response = service.execute
+
+          expect(response).to be_a(ServiceResponse)
+          expect(response.error?).to be(true)
+          expect(response.message).to eq('["Validation error"]')
+          expect(response.reason).to eq(:validation_error)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/ci/pipeline_triggers/destroy_service_spec.rb b/spec/services/ci/pipeline_triggers/destroy_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7fd93c04ace9ba7435176f883f9e7ded5860e25
--- /dev/null
+++ b/spec/services/ci/pipeline_triggers/destroy_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineTriggers::DestroyService, feature_category: :continuous_integration do
+  describe '#execute' do
+    let_it_be(:developer) { create(:user) }
+    let_it_be_with_reload(:user) { create(:user) }
+    let_it_be_with_reload(:project) { create(:project) }
+
+    let(:pipeline_trigger) { create(:ci_trigger, project: project, owner: user) }
+
+    subject(:service) { described_class.new(user: user, trigger: pipeline_trigger) }
+
+    before_all do
+      project.add_maintainer(user)
+      project.add_developer(developer)
+    end
+
+    context 'when user does not have permission' do
+      subject(:service) { described_class.new(user: developer, trigger: pipeline_trigger) }
+
+      it 'returns an error' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.error?).to be(true)
+
+        error_message = _('The current user is not authorized to manage the pipeline trigger token')
+        expect(response.message).to eq(error_message)
+        expect(response.errors).to match_array([error_message])
+      end
+    end
+
+    context 'when user has permission' do
+      it 'deletes the pipeline trigger token' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.success?).to be(true)
+        expect(Ci::Trigger.find_by(id: pipeline_trigger.id)).to be_nil
+      end
+
+      context 'when destroy fails' do
+        before do
+          allow(pipeline_trigger).to receive(:destroy).and_return(false)
+        end
+
+        it 'returns ServiceResponse.error' do
+          result = service.execute
+
+          expect(result).to be_a(ServiceResponse)
+          expect(result.error?).to be(true)
+          expect(result.message).to eq('Attempted to destroy the pipeline trigger token but failed')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/ci/pipeline_triggers/update_service_spec.rb b/spec/services/ci/pipeline_triggers/update_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0d2bedf0a967c114c1d2b04187cb29accaab562e
--- /dev/null
+++ b/spec/services/ci/pipeline_triggers/update_service_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineTriggers::UpdateService, feature_category: :continuous_integration do
+  let_it_be_with_reload(:user) { create(:user) }
+  let_it_be_with_reload(:project) { create(:project, :public) }
+  let_it_be_with_reload(:pipeline_trigger) do
+    create(:ci_trigger, project: project, owner: user, description: "Old description")
+  end
+
+  let_it_be(:another_maintainer) { create(:user) }
+
+  subject(:service) { described_class.new(user: user, trigger: pipeline_trigger, description: description) }
+
+  before_all do
+    project.add_maintainer(user)
+    project.add_maintainer(another_maintainer)
+
+    pipeline_trigger.reload
+  end
+
+  describe "execute" do
+    context 'when user does not have permission' do
+      subject(:service) { described_class.new(trigger: pipeline_trigger, user: another_maintainer, description: {}) }
+
+      it 'returns ServiceResponse.error' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.error?).to be(true)
+
+        error_message = _('The current user is not authorized to update the pipeline trigger token')
+        expect(response.message).to eq(error_message)
+        expect(response.errors).to match_array([error_message])
+      end
+    end
+
+    context 'when user has permission' do
+      let(:description) { 'My updated description' }
+
+      it 'updates database values with passed description param' do
+        expect { service.execute }
+          .to change { pipeline_trigger.reload.description }.from('Old description').to('My updated description')
+      end
+
+      it 'returns ServiceResponse.success' do
+        response = service.execute
+
+        expect(response).to be_a(ServiceResponse)
+        expect(response.success?).to be(true)
+        expect(response.payload[:trigger].description).to eq('My updated description')
+      end
+
+      context 'when update fails' do
+        before do
+          allow(pipeline_trigger).to receive(:update).and_return(false)
+        end
+
+        it 'returns ServiceResponse.error' do
+          response = service.execute
+
+          expect(response).to be_a(ServiceResponse)
+          expect(response.error?).to be(true)
+          expect(response.message).to eq('Attempted to update the pipeline trigger token but failed')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/environments/destroy_service_spec.rb b/spec/services/environments/destroy_service_spec.rb
index 26efb93718bf199905cac2515b0736220b4e4977..e95dd8c24930b161090fce02af1cc29dbc1a895d 100644
--- a/spec/services/environments/destroy_service_spec.rb
+++ b/spec/services/environments/destroy_service_spec.rb
@@ -43,7 +43,7 @@
       end
 
       it 'returns errors' do
-        expect(subject.message).to include("Attemped to destroy the environment but failed")
+        expect(subject.message).to include("Attempted to destroy the environment but failed")
       end
     end
   end