123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- RSpec.describe NotificationFactory::Renderer do
- # rubocop:disable Lint/InterpolationCheck
- describe 'render' do
- before { @user = User.where(firstname: 'Nicole').first }
- it 'correctly renders a blank template' do
- renderer = build(:notification_factory_renderer)
- expect(renderer.render).to eq ''
- end
- context 'when rendering templates with ERB tags' do
- let(:template) { '<%% <%= "<%" %> %%>' }
- it 'ignores pre-existing ERB tags in an untrusted template' do
- renderer = build(:notification_factory_renderer, template: template)
- expect(renderer.render).to eq '<% <%= "<%" %> %%>'
- end
- it 'executes pre-existing ERB tags in a trusted template' do
- renderer = build(:notification_factory_renderer, template: template, trusted: true)
- expect(renderer.render).to eq '<% <% %%>'
- end
- end
- describe 'escaping' do
- let(:ticket) { create(:ticket, title: '< + some % special " characters') }
- let(:objects) { { ticket: ticket } }
- let(:renderer) { build(:notification_factory_renderer, objects: objects, template: template, escape: escape, url_encode: url_encode) }
- let(:escape) { false }
- let(:url_encode) { false }
- let(:template) { 'embedded #{ ticket.title } value' }
- context 'without escaping' do
- it 'renders correctly' do
- expect(renderer.render).to eq "embedded #{ticket.title} value"
- end
- end
- context 'with HTML escaping' do
- let(:escape) { true }
- it 'renders correctly' do
- expect(renderer.render).to eq 'embedded < + some % special " characters value'
- end
- end
- context 'with link encoding' do
- let(:url_encode) { true }
- it 'renders correctly' do
- expect(renderer.render).to eq 'embedded %3C%20%2B%20some%20%25%20special%20%22%20characters value'
- end
- end
- end
- describe 'interpolation error handling' do
- let(:renderer) { build(:notification_factory_renderer, objects: {}, template: template) }
- let(:template) { '#{ ticket.title }' }
- context 'with debug_errors' do
- it 'renders an debug message' do
- expect(renderer.render).to eq "\#{ticket / no such object}"
- end
- end
- context 'without debug_errors' do
- it 'renders a dash' do
- expect(renderer.render(debug_errors: false)).to eq '-'
- end
- end
- end
- it 'correctly renders chained object references' do
- user = User.where(firstname: 'Nicole').first
- ticket = create(:ticket, customer: user)
- renderer = build(:notification_factory_renderer,
- objects: { ticket: ticket },
- template: '#{ticket.customer.firstname.downcase}')
- expect(renderer.render).to eq 'nicole'
- end
- it 'correctly renders multiple value calls' do
- ticket = create(:ticket, customer: @user)
- renderer = build(:notification_factory_renderer,
- objects: { ticket: ticket },
- template: '#{ticket.created_at.value.value.value.value.to_s.first}')
- expect(renderer.render).to eq '2'
- end
- it 'raises a StandardError when rendering a template with a broken syntax' do
- renderer = build(:notification_factory_renderer, template: 'test <% if %>', objects: {}, trusted: true)
- expect { renderer.render }.to raise_error(StandardError)
- end
- it 'raises a StandardError when rendering a template calling a non existant method' do
- renderer = build(:notification_factory_renderer, template: 'test <% Ticket.non_existant_method %>', objects: {}, trusted: true)
- expect { renderer.render }.to raise_error(StandardError)
- end
- it 'raises a StandardError when rendering a template referencing a non existant object' do
- renderer = build(:notification_factory_renderer, template: 'test <% NonExistantObject.first %>', objects: {}, trusted: true)
- expect { renderer.render }.to raise_error(StandardError)
- end
- context 'with different article variables' do
- let(:customer) { create(:customer, firstname: 'Nicole') }
- let(:ticket) { create(:ticket, customer: customer) }
- let(:objects) do
- last_article = nil
- last_internal_article = nil
- last_external_article = nil
- all_articles = ticket.articles
- if article.nil?
- last_article = all_articles.last
- last_internal_article = all_articles.reverse.find(&:internal?)
- last_external_article = all_articles.reverse.find { |a| !a.internal? }
- else
- last_article = article
- last_internal_article = article.internal? ? article : all_articles.reverse.find(&:internal?)
- last_external_article = article.internal? ? all_articles.reverse.find { |a| !a.internal? } : article
- end
- {
- ticket: ticket,
- article: last_article,
- last_article: last_article,
- last_internal_article: last_internal_article,
- last_external_article: last_external_article,
- created_article: article,
- created_internal_article: article&.internal? ? article : nil,
- created_external_article: article&.internal? ? nil : article,
- }
- end
- let(:renderer) do
- build(:notification_factory_renderer,
- objects: objects,
- template: template)
- end
- let(:body) { 'test' }
- let(:article) { create(:ticket_article, ticket: ticket, body: body) }
- context 'with ticket.tags as template' do
- let(:template) { '#{ticket.tags}' }
- before do
- ticket.tag_add('Tag1', customer.id)
- end
- it 'correctly renders ticket tags references' do
- expect(renderer.render).to eq 'Tag1'
- end
- end
- %w[article last_article last_internal_article last_external_article
- created_article created_internal_article created_external_article].each do |tag|
- context "with #{tag}.body as template" do
- let(:template) { "\#{#{tag}.body}" }
- let(:article) do
- create(
- :ticket_article,
- ticket: ticket,
- body: body,
- internal: tag.match?('internal')
- )
- end
- it "renders an #{tag} body with quote" do
- expect(renderer.render).to eq "> #{body}<br>"
- end
- context 'with links' do
- context 'with &' do
- let(:body) { "This is an example\nhttps://example.com/?query=foo&query2=bar" }
- it "renders an #{tag} body with working links" do
- expect(renderer.render).to eq '> This is an example<br>> https://example.com/?query=foo&query2=bar<br>'
- end
- end
- context 'with &' do
- let(:body) { "This is an example\nhttps://example.com/?query=foo&query2=bar" }
- it "renders an #{tag} body with working links" do
- expect(renderer.render).to eq '> This is an example<br>> https://example.com/?query=foo&query2=bar<br>'
- end
- end
- end
- end
- end
- end
- context 'when handling ObjectManager::Attribute usage', db_strategy: :reset do
- before do
- create_object_manager_attribute
- ObjectManager::Attribute.migration_execute
- end
- let(:renderer) do
- build(:notification_factory_renderer,
- objects: { ticket: ticket },
- template: template)
- end
- shared_examples 'correctly rendering the attributes' do
- it 'correctly renders the attributes' do
- expect(renderer.render).to eq expected_render
- end
- end
- context 'with a simple select attribute' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_select, name: 'select')
- end
- let(:ticket) { create(:ticket, customer: @user, select: 'key_1') }
- let(:template) { '#{ticket.select} _SEPERATOR_ #{ticket.select.value}' }
- let(:expected_render) { 'key_1 _SEPERATOR_ value_1' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with select attribute on chained user object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_select,
- object_lookup_id: ObjectLookup.by_name('User'),
- name: 'select')
- end
- let(:user) do
- user = User.where(firstname: 'Nicole').first
- user.select = 'key_2'
- user.save
- user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.select} _SEPERATOR_ #{ticket.customer.select.value}' }
- let(:expected_render) { 'key_2 _SEPERATOR_ value_2' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with select attribute on chained group object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_select,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'select')
- end
- let(:template) { '#{ticket.group.select} _SEPERATOR_ #{ticket.group.select.value}' }
- let(:expected_render) { 'key_3 _SEPERATOR_ value_3' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.select = 'key_3'
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with select attribute on chained organization object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_select,
- object_lookup_id: ObjectLookup.by_name('Organization'),
- name: 'select')
- end
- let(:user) do
- @user.organization.select = 'key_2'
- @user.organization.save
- @user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.organization.select} _SEPERATOR_ #{ticket.customer.organization.select.value}' }
- let(:expected_render) { 'key_2 _SEPERATOR_ value_2' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with multiselect', mariadb: true do
- context 'with a simple multiselect attribute' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect, name: 'multiselect')
- end
- let(:ticket) { create(:ticket, customer: @user, multiselect: ['key_1']) }
- let(:template) { '#{ticket.multiselect} _SEPERATOR_ #{ticket.multiselect.value}' }
- let(:expected_render) { 'key_1 _SEPERATOR_ value_1' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with single multiselect attribute on chained user object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('User'),
- name: 'multiselect')
- end
- let(:user) do
- user = User.where(firstname: 'Nicole').first
- user.multiselect = ['key_2']
- user.save
- user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.multiselect} _SEPERATOR_ #{ticket.customer.multiselect.value}' }
- let(:expected_render) { 'key_2 _SEPERATOR_ value_2' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with single multiselect attribute on chained group object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'multiselect')
- end
- let(:template) { '#{ticket.group.multiselect} _SEPERATOR_ #{ticket.group.multiselect.value}' }
- let(:expected_render) { 'key_3 _SEPERATOR_ value_3' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.multiselect = ['key_3']
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with single multiselect attribute on chained organization object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('Organization'),
- name: 'multiselect')
- end
- let(:user) do
- @user.organization.multiselect = ['key_2']
- @user.organization.save
- @user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.organization.multiselect} _SEPERATOR_ #{ticket.customer.organization.multiselect.value}' }
- let(:expected_render) { 'key_2 _SEPERATOR_ value_2' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with a multiple multiselect attribute' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect, name: 'multiselect')
- end
- let(:ticket) { create(:ticket, customer: @user, multiselect: %w[key_1 key_2]) }
- let(:template) { '#{ticket.multiselect} _SEPERATOR_ #{ticket.multiselect.value}' }
- let(:expected_render) { 'key_1, key_2 _SEPERATOR_ value_1, value_2' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with multiple multiselect attribute on chained user object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('User'),
- name: 'multiselect')
- end
- let(:user) do
- user = User.where(firstname: 'Nicole').first
- user.multiselect = %w[key_2 key_3]
- user.save
- user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.multiselect} _SEPERATOR_ #{ticket.customer.multiselect.value}' }
- let(:expected_render) { 'key_2, key_3 _SEPERATOR_ value_2, value_3' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with multiple multiselect attribute on chained group object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'multiselect')
- end
- let(:template) { '#{ticket.group.multiselect} _SEPERATOR_ #{ticket.group.multiselect.value}' }
- let(:expected_render) { 'key_3, key_1 _SEPERATOR_ value_3, value_1' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.multiselect = %w[key_3 key_1]
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with select (custom sorted) attribute on chained group object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_select,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'select',
- data_option_options: [{ name: 'value_1', value: 'key_1' }, { name: 'value_2', value: 'key_2' }, { name: 'value_3', value: 'key_3' }])
- end
- let(:template) { '#{ticket.group.select} _SEPERATOR_ #{ticket.group.select.value}' }
- let(:expected_render) { 'key_3 _SEPERATOR_ value_3' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.select = 'key_3'
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with multiple multiselect (custom sorted) attribute on chained group object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'multiselect',
- data_option_options: [{ name: 'value_1', value: 'key_1' }, { name: 'value_2', value: 'key_2' }, { name: 'value_3', value: 'key_3' }])
- end
- let(:template) { '#{ticket.group.multiselect} _SEPERATOR_ #{ticket.group.multiselect.value}' }
- let(:expected_render) { 'key_3, key_1 _SEPERATOR_ value_3, value_1' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.multiselect = %w[key_3 key_1]
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with multiple multiselect attribute on chained organization object' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_multiselect,
- object_lookup_id: ObjectLookup.by_name('Organization'),
- name: 'multiselect')
- end
- let(:user) do
- @user.organization.multiselect = %w[key_2 key_1]
- @user.organization.save
- @user
- end
- let(:ticket) { create(:ticket, customer: user) }
- let(:template) { '#{ticket.customer.organization.multiselect} _SEPERATOR_ #{ticket.customer.organization.multiselect.value}' }
- let(:expected_render) { 'key_2, key_1 _SEPERATOR_ value_2, value_1' }
- it_behaves_like 'correctly rendering the attributes'
- end
- context 'with external data source attribute on chained group object', db_adapter: :postgresql do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_autocompletion_ajax_external_data_source,
- object_lookup_id: ObjectLookup.by_name('Group'),
- name: 'external_data_source')
- end
- let(:template) { '#{ticket.group.external_data_source} _SEPERATOR_ #{ticket.group.external_data_source.value}' }
- let(:expected_render) { '1234 _SEPERATOR_ Example' }
- let(:ticket) { create(:ticket, customer: @user) }
- before do
- group = ticket.group
- group.external_data_source = {
- value: 1234,
- label: 'Example'
- }
- group.save
- end
- it_behaves_like 'correctly rendering the attributes'
- end
- end
- context 'with a tree select attribute' do
- let(:create_object_manager_attribute) do
- create(:object_manager_attribute_tree_select, name: 'tree_select')
- end
- let(:ticket) { create(:ticket, customer: @user, tree_select: 'Incident::Hardware::Laptop') }
- let(:template) { '#{ticket.tree_select} _SEPERATOR_ #{ticket.tree_select.value}' }
- let(:expected_render) { 'Incident::Hardware::Laptop _SEPERATOR_ Incident::Hardware::Laptop' }
- it_behaves_like 'correctly rendering the attributes'
- end
- end
- end
- # rubocop:enable Lint/InterpolationCheck
- context 'with user avatar' do
- let(:base64_img) { 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' }
- let(:decoded_img) { Base64.decode64(base64_img) }
- let(:mime_type) { 'image/png' }
- let(:avatar) do
- Avatar.add(
- object: 'User',
- o_id: owner.id,
- full: {
- content: decoded_img,
- mime_type: mime_type,
- },
- resize: {
- content: decoded_img,
- mime_type: mime_type,
- },
- source: "upload #{Time.zone.now}",
- deletable: true,
- created_by_id: owner.id,
- updated_by_id: owner.id,
- )
- end
- let(:owner) { create(:user, group_ids: Group.pluck(:id)) }
- let(:ticket) { create(:ticket, owner: owner, group: Group.first) }
- context 'with an avatar' do
- before do
- owner.update!(image: avatar.store_hash)
- end
- it 'returns a <img> tag' do
- renderer = build(:notification_factory_renderer, template: 'Avatar test #{ticket.owner.avatar(150, 150)}', objects: { ticket: ticket }, trusted: true) # rubocop:disable Lint/InterpolationCheck
- expect(renderer.render).to eq "Avatar test <img src='data:#{mime_type};base64,#{base64_img}' width='150' height='150' />"
- end
- end
- context 'without an avatar' do
- it 'returns empty string' do
- renderer = build(:notification_factory_renderer, template: 'Avatar test #{ticket.owner.avatar(150, 150)}', objects: { ticket: ticket }, trusted: true) # rubocop:disable Lint/InterpolationCheck
- expect(renderer.render).to eq 'Avatar test '
- end
- end
- end
- end
|