123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- RSpec.describe TriggerWebhookJob::CustomPayload do
- # rubocop:disable Lint/InterpolationCheck
- describe '.generate' do
- subject(:generate) { described_class.generate(record, { ticket:, article:, notification:, config: }) }
- let(:ticket) { create(:ticket) }
- let(:article) { create(:ticket_article, body: "Text with\nnew line.") }
- let(:event) do
- {
- type: 'info',
- execution: 'trigger',
- changes: { 'state' => %w[open closed] },
- user_id: 1,
- }
- end
- let(:notification) { TriggerWebhookJob::CustomPayload::Track::Notification.generate({ ticket:, article: }, { event: }) }
- let(:config) { TriggerWebhookJob::CustomPayload::Track::Config.generate({ ticket:, article: }, {}) }
- context 'when the payload is empty' do
- let(:record) { {}.to_json }
- let(:json_data) { {} }
- it 'returns an empty JSON object' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder is empty' do
- let(:record) { { 'ticket' => '#{}' }.to_json }
- let(:json_data) { { 'ticket' => '#{}' } }
- it 'returns the placeholder' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder is invalid' do
- let(:record) { { 'ticket' => '#{ticket.title', 'article' => '#{article.id article.note}' }.to_json }
- let(:json_data) { { 'ticket' => '#{ticket.title', 'article' => '#{article.id article.note}' } }
- it 'returns the placeholder' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder base object ticket or article is missing' do
- let(:record) { { 'ticket' => '#{.title}' }.to_json }
- let(:json_data) { { 'ticket' => '#{no object provided}' } }
- it 'returns the placeholder reporting "no object provided"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder base object is other than ticket or article' do
- let(:record) { { 'user' => '#{user}' }.to_json }
- let(:json_data) { { 'user' => '#{user / no such object}' } }
- it 'returns the placehodler reporting "no such object"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains only base object ticket or article' do
- let(:record) { { 'ticket' => '#{ticket}', 'Article' => '#{article}' }.to_json }
- let(:json_data) { { 'ticket' => '#{ticket / missing method}', 'Article' => '#{article / missing method}' } }
- it 'returns the placeholder reporting "missing method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains denied method' do
- let(:record) { { 'ticket' => '#{ticket.articles}' }.to_json }
- let(:json_data) { { 'ticket' => '#{ticket.articles / no such method}' } }
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains denied attribute' do
- let(:record) { { 'ticket.owner' => '#{ticket.owner.password}' }.to_json }
- let(:json_data) { { 'ticket.owner' => '#{ticket.owner.password / no such method}' } }
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains danger method' do
- let(:record) { { 'ticket.owner' => '#{ticket.destroy!}' }.to_json }
- let(:json_data) { { 'ticket.owner' => '#{ticket.destroy! / no such method}' } }
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder ends with complex object' do
- let(:record) { { 'ticket' => '#{ticket.group}' }.to_json }
- let(:json_data) { { 'ticket' => '#{ticket.group / no such method}' } }
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains valid object and invalid method' do
- let(:record) { { 'ticket' => '#{ticket.1_article}' }.to_json }
- let(:json_data) { { 'ticket' => '#{ticket.1_article / no such method}' } }
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains valid object and method' do
- let(:record) { { 'ticket.id' => '#{ticket.id}' }.to_json }
- let(:json_data) { { 'ticket.id' => ticket.id } }
- it 'returns the determined value' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains valid object and method, but the value is nil' do
- let(:record) do
- {
- 'ticket.organization.name' => '#{ticket.organization.name}',
- 'ticket.title' => '#{ticket.title}'
- }.to_json
- end
- let(:json_data) do
- {
- 'ticket.organization.name' => '',
- 'ticket.title' => ticket.title
- }
- end
- it 'returns an empty string' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains multiple valid object and method' do
- let(:record) do
- {
- 'ticket' => { 'owner' => '#{ticket.owner.fullname}' },
- 'article' => { 'created_at' => '#{article.created_at}' }
- }.to_json
- end
- let(:json_data) do
- {
- 'ticket' => { 'owner' => ticket.owner.fullname.to_s },
- 'article' => { 'created_at' => article.created_at.to_s }
- }
- end
- it 'returns the determined value' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the placeholder contains multiple attributes' do
- let(:record) do
- {
- 'my_field' => 'Test #{ticket.id} // #{ticket.group.name} Test',
- 'my_field2' => '#{ticket.id} // #{ticket.group.name} Test',
- 'my_field3' => '#{ticket.id}',
- 'my_field4' => '#{ticket.group.name}',
- }.to_json
- end
- let(:json_data) do
- {
- 'my_field' => "Test #{ticket.id} // #{ticket.group.name} Test",
- 'my_field2' => "#{ticket.id} // #{ticket.group.name} Test",
- 'my_field3' => ticket.id,
- 'my_field4' => ticket.group.name.to_s,
- }
- end
- it 'returns the placeholder reporting "no such method"' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the payload contains a complex structure' do
- let(:record) do
- {
- 'current_user' => '#{current_user.fullname}',
- 'fqdn' => '#{config.fqdn}',
- 'ticket' => {
- 'id' => '#{ticket.id}',
- 'owner' => '#{ticket.owner.fullname}',
- 'group' => '#{ticket.group.name}',
- 'article' => {
- 'id' => '#{article.id}',
- 'created_at' => '#{article.created_at}',
- 'subject' => '#{article.subject}',
- 'body' => '#{article.body}',
- 'attachments' => '#{article.attachments}',
- 'internal' => '#{article.internal}',
- }
- }
- }.to_json
- end
- let(:json_data) do
- {
- 'current_user' => '#{current_user / no such object}',
- 'fqdn' => Setting.get('fqdn'),
- 'ticket' => {
- 'id' => ticket.id,
- 'owner' => ticket.owner.fullname.to_s,
- 'group' => ticket.group.name.to_s,
- 'article' => {
- 'id' => article.id,
- 'created_at' => article.created_at.to_s,
- 'subject' => article.subject.to_s,
- 'body' => article.body.to_s,
- 'attachments' => '#{article.attachments / no such method}',
- 'internal' => article.internal
- }
- }
- }
- end
- it 'returns a valid JSON payload' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when the replacement value contains double quotes' do
- let(:ticket) { create(:ticket, title: 'Test "Title"') }
- let(:record) { { 'ticket.title' => '#{ticket.title}' }.to_json }
- let(:json_data) { { 'ticket.title' => 'Test "Title"' } }
- it 'returns the determined value' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when object attributes are used in the placeholder', db_strategy: :reset do
- let(:ticket) { create(:ticket, object_manager_attribute_name => object_manager_attribute_value) }
- before do
- create_object_manager_attribute
- ObjectManager::Attribute.migration_execute
- end
- shared_examples 'check different usage' do
- context 'when used in string context' do
- let(:record) do
- {
- "ticket.#{object_manager_attribute_name}" => "Test \#{ticket.#{object_manager_attribute_name}}"
- }.to_json
- end
- let(:json_data) do
- {
- "ticket.#{object_manager_attribute_name}" => "Test #{object_manager_attribute_value}"
- }
- end
- it 'returns the determined value' do
- expect(generate).to eq(json_data)
- end
- end
- context 'when used in direct context' do
- let(:record) do
- {
- "ticket.#{object_manager_attribute_name}" => "\#{ticket.#{object_manager_attribute_name}}"
- }.to_json
- end
- let(:json_data) do
- {
- "ticket.#{object_manager_attribute_name}" => object_manager_attribute_value
- }
- end
- it 'returns the determined value' do
- expect(generate).to eq(json_data)
- end
- end
- end
- context 'when multiselect is used inside the ticket' do
- let(:object_manager_attribute_name) { 'multiselect_keys_001' }
- let(:object_manager_attribute_value) { %w[key_1 key_3] }
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect, name: object_manager_attribute_name)
- end
- include_examples 'check different usage'
- end
- context 'when external data source is used inside the ticket', db_adapter: :postgresql do
- let(:object_manager_attribute_name) { 'autocompletion_ajax_external_data_source' }
- let(:object_manager_attribute_value) do
- {
- 'value' => 123,
- 'label' => 'Example',
- }
- end
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_autocompletion_ajax_external_data_source, name: object_manager_attribute_name)
- end
- include_examples 'check different usage'
- end
- end
- describe "when the placeholder contains object 'notification'" do
- let(:record) do
- {
- 'subject' => '#{notification.subject}',
- 'message' => '#{notification.message}',
- 'changes' => '#{notification.changes}',
- 'body' => '#{notification.body}',
- 'link' => '#{notification.link}',
- }.to_json
- end
- context "when the event is of the type 'create'" do
- let(:event) do
- {
- type: 'create',
- execution: 'trigger',
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Created by')
- expect(generate['changes']).to include('State: new')
- end
- end
- context "when the event is of the type 'update'" do
- let(:event) do
- {
- type: 'update',
- execution: 'trigger',
- changes: { 'state' => %w[open closed] },
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Updated by')
- expect(generate['changes']).to include('state: open -> closed')
- end
- context 'without changes' do
- let(:event) do
- {
- type: 'update',
- execution: 'trigger',
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Updated by')
- end
- end
- end
- context "when the event is of the type 'info'" do
- let(:event) do
- {
- type: 'info',
- execution: 'trigger',
- changes: { 'state' => %w[open closed] },
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Last updated at')
- end
- end
- context "when the event is of the type 'escalation'" do
- let(:event) do
- {
- type: 'escalation',
- execution: 'trigger',
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Escalated at')
- expect(generate['changes']).to include('has been escalated since')
- end
- end
- context "when the event is of the type 'escalation warning'" do
- let(:event) do
- {
- type: 'escalation_warning',
- execution: 'trigger',
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Will escalate at')
- expect(generate['changes']).to include('will escalate at')
- end
- end
- context "when the event is of the type 'reminder reached'" do
- let(:event) do
- {
- type: 'reminder_reached',
- execution: 'trigger',
- user_id: 1,
- }
- end
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to eq(article.body_as_text)
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Reminder reached!')
- expect(generate['changes']).to include('reminder reached for')
- end
- end
- context "when the event is triggered by a 'job'" do
- let(:event) do
- {
- type: '',
- execution: 'job',
- changes: { 'state' => %w[open closed] },
- user_id: 1,
- }
- end
- let(:article) { nil }
- it 'returns a valid json with a notification factory generated information"', :aggregate_failures do
- expect(generate['subject']).to eq(ticket.title)
- expect(generate['body']).to be_empty
- expect(generate['link']).to match(%r{http.*#ticket/zoom/#{ticket.id}$})
- expect(generate['message']).to include('Last updated at')
- end
- end
- end
- describe 'when the payload is a pre-defined webhook' do
- subject(:generate) { described_class.generate(record, { ticket:, article:, notification:, webhook: struct_webhook }) }
- let(:webhook) { create(:mattermost_webhook) }
- let(:struct_webhook) { TriggerWebhookJob::CustomPayload::Track::PreDefinedWebhook.generate({ ticket:, article: }, { event:, webhook: }) }
- let(:record) { TriggerWebhookJob::CustomPayload::Track::PreDefinedWebhook.payload('Mattermost') }
- it 'returns a valid json with webhook information"', :aggregate_failures do
- info = webhook.preferences[:pre_defined_webhook]
- expect(generate[:channel]).to eq(info[:channel])
- expect(generate[:icon_url]).to eq(info[:icon_url])
- end
- context 'when event has no changes' do
- let(:event) do
- {
- type: 'info',
- execution: 'trigger',
- changes: { 'state' => %w[open closed] },
- user_id: 1,
- }
- end
- it "returns a valid json with webhook information without 'attachments'", :aggregate_failures do
- info = webhook.preferences[:pre_defined_webhook]
- expect(generate[:channel]).to eq(info[:channel])
- expect(generate[:icon_url]).to eq(info[:icon_url])
- expect(generate).to not_include(:attachments)
- end
- end
- context 'when pre-defined webhook has no additional values' do
- let(:webhook) { create(:slack_webhook) }
- let(:record) { TriggerWebhookJob::CustomPayload::Track::PreDefinedWebhook.payload('Slack') }
- it 'returns a valid json with webhook information"', :aggregate_failures do
- expect(generate['text']).to eq("# #{ticket.title}")
- end
- end
- end
- end
- # rubocop:enable Lint/InterpolationCheck
- describe '.replacements' do
- subject(:replacements) { described_class.replacements(pre_defined_webhook_type: 'Mattermost') }
- it 'returns a hash with the replacement variables', :aggregate_failures do
- expect(replacements).to be_a(Hash)
- expect(replacements.keys).to include(:article, :ticket, :notification, :webhook)
- end
- end
- end
|