diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee
index bf1fb8d89..9daed4a70 100644
--- a/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee
+++ b/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee
@@ -2,6 +2,7 @@ class App.ControllerGenericIndex extends App.Controller
events:
'click [data-type=edit]': 'edit'
'click [data-type=new]': 'new'
+ 'click [data-type=payload]': 'payload'
'click [data-type=import]': 'import'
'click .js-description': 'description'
@@ -152,6 +153,12 @@ class App.ControllerGenericIndex extends App.Controller
else
@table.update(objects: objects, pagerSelected: @pageData.pagerSelected, pagerTotalCount: @pageData.pagerTotalCount)
+ if @pageData.logFacility
+ new App.HttpLog(
+ el: @$('.page-footer')
+ facility: @pageData.logFacility
+ )
+
edit: (id, e) =>
e.preventDefault()
item = App[ @genericObject ].find(id)
@@ -181,6 +188,13 @@ class App.ControllerGenericIndex extends App.Controller
veryLarge: @veryLarge
)
+ payload: (e) ->
+ e.preventDefault()
+ new App.WidgetPayloadExample(
+ baseUrl: @payloadExampleUrl
+ container: @el.closest('.content')
+ )
+
import: (e) ->
e.preventDefault()
@importCallback()
diff --git a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee
index 42b80571b..325da089b 100644
--- a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee
+++ b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee
@@ -406,55 +406,73 @@ class App.UiElement.ticket_perform_action
selectionRecipient = columnSelectRecipient.element()
- elementTemplate = 'notification'
if notificationType is 'webhook'
- elementTemplate = 'webhook'
+ notificationElement = $( App.view('generic/ticket_perform_action/webhook')(
+ attribute: attribute
+ name: name
+ notificationType: notificationType
+ meta: meta || {}
+ ))
- notificationElement = $( App.view("generic/ticket_perform_action/#{elementTemplate}")(
- attribute: attribute
- name: name
- notificationType: notificationType
- meta: meta || {}
- ))
+ notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
- notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
+ webhookSelection = App.UiElement.select.render(
+ name: "#{name}::webhook_id"
+ multiple: false
+ null: false
+ relation: 'Webhook'
+ value: meta.webhook_id
+ translate: false
+ )
- visibilitySelection = App.UiElement.select.render(
- name: "#{name}::internal"
- multiple: false
- null: false
- options: { true: 'internal', false: 'public' }
- value: meta.internal || 'false'
- translate: true
- )
+ notificationElement.find('.js-webhooks').html(webhookSelection)
- notificationElement.find('.js-internal').html(visibilitySelection)
+ else
+ notificationElement = $( App.view('generic/ticket_perform_action/notification')(
+ attribute: attribute
+ name: name
+ notificationType: notificationType
+ meta: meta || {}
+ ))
- notificationElement.find('.js-body div[contenteditable="true"]').ce(
- mode: 'richtext'
- placeholder: 'message'
- maxlength: messageLength
- )
- new App.WidgetPlaceholder(
- el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
- objects: [
- {
- prefix: 'ticket'
- object: 'Ticket'
- display: 'Ticket'
- },
- {
- prefix: 'article'
- object: 'TicketArticle'
- display: 'Article'
- },
- {
- prefix: 'user'
- object: 'User'
- display: 'Current User'
- },
- ]
- )
+ notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
+
+ visibilitySelection = App.UiElement.select.render(
+ name: "#{name}::internal"
+ multiple: false
+ null: false
+ options: { true: 'internal', false: 'public' }
+ value: meta.internal || 'false'
+ translate: true
+ )
+
+ notificationElement.find('.js-internal').html(visibilitySelection)
+
+ notificationElement.find('.js-body div[contenteditable="true"]').ce(
+ mode: 'richtext'
+ placeholder: 'message'
+ maxlength: messageLength
+ )
+ new App.WidgetPlaceholder(
+ el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
+ objects: [
+ {
+ prefix: 'ticket'
+ object: 'Ticket'
+ display: 'Ticket'
+ },
+ {
+ prefix: 'article'
+ object: 'TicketArticle'
+ display: 'Article'
+ },
+ {
+ prefix: 'user'
+ object: 'User'
+ display: 'Current User'
+ },
+ ]
+ )
elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
diff --git a/app/assets/javascripts/app/controllers/webhook.coffee b/app/assets/javascripts/app/controllers/webhook.coffee
new file mode 100644
index 000000000..96b79cb2a
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/webhook.coffee
@@ -0,0 +1,41 @@
+class Index extends App.ControllerSubContent
+ requiredPermission: 'admin.webhook'
+ header: 'Webhooks'
+ constructor: ->
+ super
+
+ @genericController = new App.ControllerGenericIndex(
+ el: @el
+ id: @id
+ genericObject: 'Webhook'
+ defaultSortBy: 'name'
+ pageData:
+ home: 'webhooks'
+ object: 'Webhook'
+ objects: 'Webhooks'
+ pagerAjax: true
+ pagerBaseUrl: '#manage/webhook/'
+ pagerSelected: ( @page || 1 )
+ pagerPerPage: 150
+ navupdate: '#webhooks'
+ notes: [
+ 'Webhooks are ...'
+ ]
+ buttons: [
+ { name: 'Example Payload', 'data-type': 'payload', class: 'btn' }
+ { name: 'New Webhook', 'data-type': 'new', class: 'btn--success' }
+ ]
+ logFacility: 'webhook'
+ payloadExampleUrl: '/api/v1/webhooks/preview'
+ container: @el.closest('.content')
+ veryLarge: true
+ )
+
+ show: (params) =>
+ for key, value of params
+ if key isnt 'el' && key isnt 'shown' && key isnt 'match'
+ @[key] = value
+
+ @genericController.paginate( @page || 1 )
+
+App.Config.set('Webhook', { prio: 3350, name: 'Webhook', parent: '#manage', target: '#manage/webhook', controller: Index, permission: ['admin.webhook'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/widget/payload_example.coffee b/app/assets/javascripts/app/controllers/widget/payload_example.coffee
new file mode 100644
index 000000000..2597e7054
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/widget/payload_example.coffee
@@ -0,0 +1,37 @@
+class App.WidgetPayloadExample extends App.ControllerModal
+ buttonClose: true
+ buttonCancel: true
+ buttonSubmit: false
+ head: 'Example Payload'
+ large: true
+
+ content: =>
+ if !@payloadExample
+ @load()
+ return
+
+ @payloadExample
+
+ load: =>
+ @ajax(
+ id: 'example_payload'
+ type: 'get'
+ url: @baseUrl
+ processData: false
+ contentType: 'text/plain'
+ dataType: 'text'
+ cache: false
+ success: (data, status, xhr) =>
+ @payloadExample = $(App.view('widget/payload_example')(
+ payload: data
+ ))
+
+ @update()
+ error: (data) =>
+ details = data.responseJSON || {}
+ @notify
+ type: 'error'
+ msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to load example payload!')
+ timeout: 6000
+ )
+
diff --git a/app/assets/javascripts/app/models/webhook.coffee b/app/assets/javascripts/app/models/webhook.coffee
new file mode 100644
index 000000000..fead97e46
--- /dev/null
+++ b/app/assets/javascripts/app/models/webhook.coffee
@@ -0,0 +1,29 @@
+class App.Webhook extends App.Model
+ @configure 'Webhook', 'name', 'endpoint', 'signature_token', 'ssl_verify', 'note', 'active'
+ @extend Spine.Model.Ajax
+ @url: @apiPath + '/webhooks'
+ @configure_attributes = [
+ { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
+ { name: 'endpoint', display: 'Endpoint', tag: 'input', type: 'text', limit: 300, null: false, placeholder: 'https://target.example.com/webhook' },
+ { name: 'signature_token', display: 'HMAC SHA1 Signature Token', tag: 'input', type: 'text', limit: 100, null: true },
+ { name: 'ssl_verify', display: 'SSL Verify', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true },
+ { name: 'note', display: 'Note', tag: 'textarea', note: '', limit: 250, null: true },
+ { name: 'active', display: 'Active', tag: 'active', default: true },
+ { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
+ ]
+ @configure_delete = true
+ @configure_clone = true
+ @configure_overview = [
+ 'name',
+ 'endpoint',
+ ]
+
+ @description = '''
+Webhooks make it easy to send information about events within Zammad to third party systems via HTTP(S).
+
+You can use Webhooks in Zammad to send Ticket, Article and Attachment data whenever a Trigger is performed. Just create and configure your Webhook with an HTTP(S) endpoint and relevant security settings, configure a Trigger to perform it.
+'''
+
+ displayName: ->
+ return @name if !@endpoint
+ "#{@name} (#{@endpoint})"
diff --git a/app/assets/javascripts/app/views/generic/admin/index.jst.eco b/app/assets/javascripts/app/views/generic/admin/index.jst.eco
index 19012867a..ff0f5dfbb 100644
--- a/app/assets/javascripts/app/views/generic/admin/index.jst.eco
+++ b/app/assets/javascripts/app/views/generic/admin/index.jst.eco
@@ -16,4 +16,6 @@
\ No newline at end of file
+
+
+
diff --git a/app/assets/javascripts/app/views/generic/ticket_perform_action/webhook.jst.eco b/app/assets/javascripts/app/views/generic/ticket_perform_action/webhook.jst.eco
index 9cb3d179f..214c867b9 100644
--- a/app/assets/javascripts/app/views/generic/ticket_perform_action/webhook.jst.eco
+++ b/app/assets/javascripts/app/views/generic/ticket_perform_action/webhook.jst.eco
@@ -1,24 +1,6 @@
-
-
diff --git a/app/assets/javascripts/app/views/widget/payload_example.jst.eco b/app/assets/javascripts/app/views/widget/payload_example.jst.eco
new file mode 100644
index 000000000..862e4cca8
--- /dev/null
+++ b/app/assets/javascripts/app/views/widget/payload_example.jst.eco
@@ -0,0 +1,10 @@
+
+<%- @T('Header') %>
+
+X-Zammad-Trigger: Name of the Trigger
+X-Zammad-Delivery: 6d600811-06a3-40af-aebd-a2d8213e85aa
+X-Hub-Signature: sha1=06007ef23c38e435f49091cdfa3c770b3d85d7be
+
+<%- @T('Body') %>
+
<%= @payload %>
+
diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb
new file mode 100644
index 000000000..9a0ac2f5e
--- /dev/null
+++ b/app/controllers/webhooks_controller.rb
@@ -0,0 +1,37 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class WebhooksController < ApplicationController
+ prepend_before_action { authentication_check && authorize! }
+
+ def preview
+ access_condition = Ticket.access_condition(current_user, 'read')
+
+ ticket = Ticket.where(access_condition).last
+
+ render json: JSON.pretty_generate({
+ ticket: TriggerWebhookJob::RecordPayload.generate(ticket),
+ article: TriggerWebhookJob::RecordPayload.generate(ticket.articles.last),
+ }),
+ status: :ok
+ end
+
+ def index
+ model_index_render(Webhook, params)
+ end
+
+ def show
+ model_show_render(Webhook, params)
+ end
+
+ def create
+ model_create_render(Webhook, params)
+ end
+
+ def update
+ model_update_render(Webhook, params)
+ end
+
+ def destroy
+ model_destroy_render(Webhook, params)
+ end
+end
diff --git a/app/jobs/trigger_webhook_job.rb b/app/jobs/trigger_webhook_job.rb
index 3ffe65cf6..8122589c8 100644
--- a/app/jobs/trigger_webhook_job.rb
+++ b/app/jobs/trigger_webhook_job.rb
@@ -27,6 +27,7 @@ class TriggerWebhookJob < ApplicationJob
@ticket = ticket
@article = article
+ return if abort?
return if request.success?
raise TriggerWebhookJob::RequestError
@@ -34,9 +35,42 @@ class TriggerWebhookJob < ApplicationJob
private
+ def abort?
+ if webhook_id.blank?
+ log_wrong_trigger_config
+ return true
+ elsif webhook.blank?
+ log_not_existing_webhook
+ return true
+ end
+
+ false
+ end
+
+ def webhook_id
+ @webhook_id ||= trigger.perform.dig('notification.webhook', 'webhook_id')
+ end
+
+ def webhook
+ @webhook ||= begin
+ Webhook.find_by(
+ id: webhook_id,
+ active: true
+ )
+ end
+ end
+
+ def log_wrong_trigger_config
+ Rails.logger.error "Can't find webhook_id for Trigger '#{trigger.name}' with ID #{trigger.id}"
+ end
+
+ def log_not_existing_webhook
+ Rails.logger.error "Can't find Webhook for ID #{webhook_id} configured in Trigger '#{trigger.name}' with ID #{trigger.id}"
+ end
+
def request
UserAgent.post(
- config['endpoint'],
+ webhook.endpoint,
payload,
{
json: true,
@@ -45,8 +79,8 @@ class TriggerWebhookJob < ApplicationJob
read_timeout: 30,
total_timeout: 60,
headers: headers,
- signature_token: config['token'],
- verify_ssl: verify_ssl?,
+ signature_token: webhook.signature_token,
+ verify_ssl: webhook.ssl_verify,
log: {
facility: 'webhook',
},
@@ -54,14 +88,6 @@ class TriggerWebhookJob < ApplicationJob
)
end
- def config
- @config ||= trigger.perform['notification.webhook']
- end
-
- def verify_ssl?
- config.fetch('verify_ssl', false).present?
- end
-
def headers
{
'X-Zammad-Trigger' => trigger.name,
diff --git a/app/models/concerns/checks_perform_validation.rb b/app/models/concerns/checks_perform_validation.rb
index 8cdb4487d..d856a0e66 100644
--- a/app/models/concerns/checks_perform_validation.rb
+++ b/app/models/concerns/checks_perform_validation.rb
@@ -15,7 +15,7 @@ module ChecksPerformValidation
'article.note' => %w[body subject internal],
'notification.email' => %w[body recipient subject],
'notification.sms' => %w[body recipient],
- 'notification.webhook' => %w[endpoint],
+ 'notification.webhook' => %w[webhook_id],
}
check_present.each do |key, values|
diff --git a/app/models/trigger/assets.rb b/app/models/trigger/assets.rb
index 4b9a21fd9..1ec395cdb 100644
--- a/app/models/trigger/assets.rb
+++ b/app/models/trigger/assets.rb
@@ -39,6 +39,12 @@ returns
data = calendar.assets(data)
end
+ app_model_webhook = Webhook.to_app_model
+ data[ app_model_webhook ] ||= {}
+ Webhook.find_each do |webhook|
+ data = webhook.assets(data)
+ end
+
app_model_user = User.to_app_model
data[ app_model_user ] ||= {}
diff --git a/app/models/webhook.rb b/app/models/webhook.rb
new file mode 100644
index 000000000..4285ec1a3
--- /dev/null
+++ b/app/models/webhook.rb
@@ -0,0 +1,23 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class Webhook < ApplicationModel
+ include ChecksClientNotification
+ include ChecksLatestChangeObserved
+ include HasCollectionUpdate
+
+ before_create :validate_endpoint
+ before_update :validate_endpoint
+
+ validates :name, presence: true
+
+ private
+
+ def validate_endpoint
+ uri = URI.parse(endpoint)
+ raise Exceptions::UnprocessableEntity, 'Invalid endpoint (no http/https)!' if !uri.is_a?(URI::HTTP)
+ raise Exceptions::UnprocessableEntity, 'Invalid endpoint (no hostname)!' if uri.host.nil?
+ rescue URI::InvalidURIError
+ raise Exceptions::UnprocessableEntity, 'Invalid endpoint!'
+ end
+
+end
diff --git a/app/policies/controllers/webhooks_controller_policy.rb b/app/policies/controllers/webhooks_controller_policy.rb
new file mode 100644
index 000000000..a962c198d
--- /dev/null
+++ b/app/policies/controllers/webhooks_controller_policy.rb
@@ -0,0 +1,3 @@
+class Controllers::WebhooksControllerPolicy < Controllers::ApplicationControllerPolicy
+ default_permit!('admin.webhook')
+end
diff --git a/config/routes/webhook.rb b/config/routes/webhook.rb
new file mode 100644
index 000000000..40bff17ae
--- /dev/null
+++ b/config/routes/webhook.rb
@@ -0,0 +1,12 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ # webhooks
+ match api_path + '/webhooks/preview', to: 'webhooks#preview', via: :get
+ match api_path + '/webhooks', to: 'webhooks#index', via: :get
+ match api_path + '/webhooks/:id', to: 'webhooks#show', via: :get
+ match api_path + '/webhooks', to: 'webhooks#create', via: :post
+ match api_path + '/webhooks/:id', to: 'webhooks#update', via: :put
+ match api_path + '/webhooks/:id', to: 'webhooks#destroy', via: :delete
+
+end
diff --git a/db/migrate/20120101000010_create_ticket.rb b/db/migrate/20120101000010_create_ticket.rb
index a661222d9..832463fa1 100644
--- a/db/migrate/20120101000010_create_ticket.rb
+++ b/db/migrate/20120101000010_create_ticket.rb
@@ -586,6 +586,19 @@ class CreateTicket < ActiveRecord::Migration[4.2]
add_index :karma_activity_logs, %i[o_id object_lookup_id]
add_foreign_key :karma_activity_logs, :users
add_foreign_key :karma_activity_logs, :karma_activities, column: :activity_id
+
+ create_table :webhooks do |t|
+ t.column :name, :string, limit: 250, null: false
+ t.column :endpoint, :string, limit: 300, null: false
+ t.column :signature_token, :string, limit: 200, null: true
+ t.column :ssl_verify, :boolean, null: false, default: true
+ t.column :note, :string, limit: 500, null: true
+ t.column :active, :boolean, null: false, default: true
+ t.column :updated_by_id, :integer, null: false
+ t.column :created_by_id, :integer, null: false
+ t.timestamps limit: 3, null: false
+ end
+
end
def self.down
@@ -623,5 +636,6 @@ class CreateTicket < ActiveRecord::Migration[4.2]
drop_table :ticket_priorities
drop_table :ticket_states
drop_table :ticket_state_types
+ drop_table :webhooks
end
end
diff --git a/db/migrate/20210118095820_issue_3372_webhooks_admin_view.rb b/db/migrate/20210118095820_issue_3372_webhooks_admin_view.rb
new file mode 100644
index 000000000..e56b99f76
--- /dev/null
+++ b/db/migrate/20210118095820_issue_3372_webhooks_admin_view.rb
@@ -0,0 +1,58 @@
+class Issue3372WebhooksAdminView < ActiveRecord::Migration[5.2]
+
+ def up
+ return if !Setting.exists?(name: 'system_init_done')
+
+ create_webhooks_table
+
+ record_upgrade
+
+ Permission.create_if_not_exists(
+ name: 'admin.webhook',
+ note: 'Manage %s',
+ preferences: {
+ translations: ['Webhooks']
+ },
+ )
+ end
+
+ def create_webhooks_table
+ create_table :webhooks do |t|
+ t.column :name, :string, limit: 250, null: false
+ t.column :endpoint, :string, limit: 300, null: false
+ t.column :signature_token, :string, limit: 200, null: true
+ t.column :ssl_verify, :boolean, null: false, default: true
+ t.column :note, :string, limit: 500, null: true
+ t.column :active, :boolean, null: false, default: true
+ t.column :updated_by_id, :integer, null: false
+ t.column :created_by_id, :integer, null: false
+ t.timestamps limit: 3, null: false
+ end
+ end
+
+ def record_upgrade
+ Trigger.all.find_each do |trigger|
+ next if trigger.perform.dig('notification.webhook', 'endpoint').blank?
+
+ webhook = webhook_create(
+ source: trigger.name,
+ config: trigger.perform['notification.webhook'],
+ )
+ trigger.perform['notification.webhook'] = { webhook_id: webhook.id }
+ trigger.save!
+ end
+ end
+
+ def webhook_create(source:, config:)
+ Webhook.create!(
+ name: "Webhook '#{source}'",
+ endpoint: config['endpoint'],
+ signature_token: config['token'],
+ ssl_verify: config['verify_ssl'],
+ active: true,
+ created_by_id: 1,
+ updated_by_id: 1,
+ )
+ end
+
+end
diff --git a/db/seeds/permissions.rb b/db/seeds/permissions.rb
index 0d81da3bc..88d67ae2d 100644
--- a/db/seeds/permissions.rb
+++ b/db/seeds/permissions.rb
@@ -262,6 +262,13 @@ Permission.create_if_not_exists(
translations: ['Sessions']
},
)
+Permission.create_if_not_exists(
+ name: 'admin.webhook',
+ note: 'Manage %s',
+ preferences: {
+ translations: ['Webhooks']
+ },
+)
Permission.create_if_not_exists(
name: 'user_preferences',
note: 'User Preferences',
diff --git a/spec/db/migrate/issue_3372_webhooks_admin_view_spec.rb b/spec/db/migrate/issue_3372_webhooks_admin_view_spec.rb
new file mode 100644
index 000000000..44ee228c7
--- /dev/null
+++ b/spec/db/migrate/issue_3372_webhooks_admin_view_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+RSpec.describe Issue3372WebhooksAdminView, type: :db_migration do
+
+ let(:trigger_webhook_config) do
+ {
+ 'endpoint' => 'https://example.com/webhook',
+ 'token' => '53Cr3T',
+ 'verify_ssl' => false,
+ }
+ end
+
+ let(:webhook_attributes) do
+ {
+ endpoint: trigger_webhook_config['endpoint'],
+ signature_token: trigger_webhook_config['token'],
+ ssl_verify: trigger_webhook_config['verify_ssl'],
+ }
+ end
+
+ let!(:trigger) do
+ Trigger.without_callback(:create, :before, :validate_perform) do
+ create(:trigger, perform: {
+ 'notification.webhook' => trigger_webhook_config
+ })
+ end
+ end
+
+ it 'Creates Webhook object from mapped Trigger configuration' do
+ migrate do |migration|
+ allow(migration).to receive(:create_webhooks_table)
+ end
+
+ expect(Webhook.last).to have_attributes(**webhook_attributes)
+ end
+
+ it 'Migrates Trigger#perform Webhook configuration to new structure' do
+ migrate do |migration|
+ allow(migration).to receive(:create_webhooks_table)
+ end
+
+ expect(trigger.reload.perform['notification.webhook']['webhook_id']).to eq(Webhook.last.id)
+ end
+end
diff --git a/spec/factories/webhook.rb b/spec/factories/webhook.rb
new file mode 100644
index 000000000..4230500d5
--- /dev/null
+++ b/spec/factories/webhook.rb
@@ -0,0 +1,9 @@
+FactoryBot.define do
+ factory :webhook do
+ sequence(:name) { |n| "Test webhook #{n}" }
+ ssl_verify { true }
+ active { true }
+ created_by_id { 1 }
+ updated_by_id { 1 }
+ end
+end
diff --git a/spec/jobs/trigger_webhook_job_spec.rb b/spec/jobs/trigger_webhook_job_spec.rb
index 4d8e78d3c..3b71085c9 100644
--- a/spec/jobs/trigger_webhook_job_spec.rb
+++ b/spec/jobs/trigger_webhook_job_spec.rb
@@ -62,13 +62,12 @@ RSpec.describe TriggerWebhookJob, type: :job do
let!(:ticket) { create(:ticket) }
let!(:article) { create(:'ticket/article') }
+ let(:webhook) { create(:webhook, endpoint: endpoint, signature_token: token) }
+
let(:trigger) do
create(:trigger,
perform: {
- 'notification.webhook' => {
- endpoint: endpoint,
- token: token
- }
+ 'notification.webhook' => { 'webhook_id' => webhook.id }
})
end
diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb
index c21061802..313fe7413 100644
--- a/spec/models/ticket_spec.rb
+++ b/spec/models/ticket_spec.rb
@@ -502,13 +502,11 @@ RSpec.describe Ticket, type: :model do
end
context 'with a "notification.webhook" trigger', performs_jobs: true do
+ let(:webhook) { create(:webhook, endpoint: 'http://api.example.com/webhook', signature_token: '53CR3t') }
let(:trigger) do
create(:trigger,
perform: {
- 'notification.webhook' => {
- endpoint: 'http://api.example.com/webhook',
- token: '53CR3t'
- }
+ 'notification.webhook' => { 'webhook_id' => webhook.id }
})
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bde1a3711..fcbf74a07 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -870,6 +870,7 @@ RSpec.describe User, type: :model do
'Channel' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
'Role' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
'History' => { 'created_by_id' => 1 },
+ 'Webhook' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
'Overview' => { 'created_by_id' => 1, 'updated_by_id' => 0 },
'ActivityStream' => { 'created_by_id' => 0 },
'StatsStore' => { 'created_by_id' => 0 },
diff --git a/spec/models/webhook_spec.rb b/spec/models/webhook_spec.rb
new file mode 100644
index 000000000..f19522ef4
--- /dev/null
+++ b/spec/models/webhook_spec.rb
@@ -0,0 +1,41 @@
+require 'rails_helper'
+
+RSpec.describe Webhook, type: :model do
+
+ describe 'check endpoint' do
+ subject(:webhook) { create(:webhook, endpoint: endpoint) }
+
+ let(:endpoint) { 'example.com' }
+
+ context 'with missing http type' do
+ it 'raise an error' do
+ expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint (no http/https)!')
+ end
+ end
+
+ context 'with spaces in invalid hostname' do
+ let(:endpoint) { 'http:// example.com' }
+
+ it 'raise an error' do
+ expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint!')
+ end
+ end
+
+ context 'with ? in hostname' do
+ let(:endpoint) { 'http://?example.com' }
+
+ it 'raise an error' do
+ expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint (no hostname)!')
+ end
+ end
+
+ context 'with nil in endpoint' do
+ let(:endpoint) { nil }
+
+ it 'raise an error' do
+ expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint!')
+ end
+ end
+
+ end
+end