require 'rails_helper' RSpec.describe 'Ticket zoom', type: :system do describe 'owner auto-assignment', authenticated_as: :authenticate do let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), state: Ticket::State.find_by(name: 'new')) } let!(:session_user) { User.find_by(login: 'master@example.com') } context 'for agent disabled' do def authenticate Setting.set('ticket_auto_assignment', false) Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } }) Setting.set('ticket_auto_assignment_user_ids_ignore', []) true end it 'do not assign ticket to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: '-', options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'for agent enabled' do def authenticate Setting.set('ticket_auto_assignment', true) Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } }) Setting.set('ticket_auto_assignment_user_ids_ignore', setting_user_ids_ignore) if defined?(setting_user_ids_ignore) true end context 'with empty "ticket_auto_assignment_user_ids_ignore"' do it 'assigns ticket to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('.content.active select[name=owner_id]') expect(page).to have_select('owner_id', selected: session_user.fullname, options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'with "ticket_auto_assignment_user_ids_ignore" (as integer)' do let(:setting_user_ids_ignore) { session_user.id } it 'assigns ticket not to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: '-', options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'with "ticket_auto_assignment_user_ids_ignore" (as string)' do let(:setting_user_ids_ignore) { session_user.id.to_s } it 'assigns ticket not to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: '-', options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'with "ticket_auto_assignment_user_ids_ignore" (as [integer])' do let(:setting_user_ids_ignore) { [session_user.id] } it 'assigns ticket not to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: '-', options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'with "ticket_auto_assignment_user_ids_ignore" (as [string])' do let(:setting_user_ids_ignore) { [session_user.id.to_s] } it 'assigns ticket not to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: '-', options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end context 'with "ticket_auto_assignment_user_ids_ignore" and other user ids' do let(:setting_user_ids_ignore) { [99_999, 999_999] } it 'assigns ticket to current session user' do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do expect(page).to have_css('select[name=owner_id]') expect(page).to have_select('owner_id', selected: session_user.fullname, options: ['-', 'Agent 1 Test', 'Test Master Agent']) end end end end end context 'when ticket has an attachment' do let(:group) { Group.find_by(name: 'Users') } let(:ticket) { create(:ticket, group: group) } let(:article) { create(:ticket_article, ticket: ticket) } let(:attachment_name) { 'some_file.txt' } before do Store.add( object: 'Ticket::Article', o_id: article.id, data: 'some content', filename: attachment_name, preferences: { 'Content-Type' => 'text/plain', }, created_by_id: 1, ) end context 'article was already forwarded once' do before do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do find('a[data-type=emailForward]').click click('.js-reset') have_no_css('.js-reset') end end it 'adds attachments when forwarding multiple times' do within(:active_content) do find('a[data-type=emailForward]').click end within('.js-writeArea') do expect(page).to have_text attachment_name end end end end context 'replying' do context 'Group without signature' do let(:ticket) { create(:ticket) } let(:current_user) { create(:agent, password: 'test', groups: [ticket.group]) } before do # initial article to reply to create(:ticket_article, ticket: ticket) end it 'ensures that text input opens on multiple replies', authenticated_as: :current_user do visit "ticket/zoom/#{ticket.id}" 2.times do |article_offset| articles_existing = 1 articles_expected = articles_existing + (article_offset + 1) all('a[data-type=emailReply]').last.click # wait till input box expands completely find('.attachmentPlaceholder-label').in_fixed_position expect(page).to have_no_css('.attachmentPlaceholder-hint') find('.articleNewEdit-body').send_keys('Some reply') click '.js-submit' expect(page).to have_css('.ticket-article-item', count: articles_expected) end end end context 'to inbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do let(:agent) { create(:agent, groups: [Group.first]) } let(:customer) { create(:agent) } let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) } let!(:article) { create(:ticket_article, :inbound_phone, ticket: ticket) } it 'goes to customer email' do visit "ticket/zoom/#{ticket.id}" within :active_ticket_article, article do click '.js-ArticleAction[data-type=emailReply]' end within :active_content do within '.article-new' do expect(find('[name=to]', visible: :all).value).to eq customer.email end end end end context 'to outbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do let(:agent) { create(:agent, groups: [Group.first]) } let(:customer) { create(:agent) } let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) } let!(:article) { create(:ticket_article, :outbound_phone, ticket: ticket) } it 'goes to customer email' do visit "ticket/zoom/#{ticket.id}" within :active_ticket_article, article do click '.js-ArticleAction[data-type=emailReply]' end within :active_content do within '.article-new' do expect(find('[name=to]', visible: :all).value).to eq customer.email end end end end end describe 'delete article', authenticated_as: :authenticate do let(:group) { Group.first } let(:admin) { create :admin, groups: [group] } let(:agent) { create :agent, groups: [group] } let(:other_agent) { create :agent, groups: [group] } let(:customer) { create :customer } let(:article) { send(item) } def authenticate Setting.set('ui_ticket_zoom_article_delete_timeframe', setting_delete_timeframe) if defined?(setting_delete_timeframe) article user end def article_communication create_ticket_article(sender_name: 'Agent', internal: false, type_name: 'email', updated_by: customer) end def article_note_self create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: user) end def article_note_other create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: other_agent) end def article_note_customer create_ticket_article(sender_name: 'Customer', internal: false, type_name: 'note', updated_by: customer) end def article_note_communication_self create(:ticket_article_type, name: 'note_communication', communication: true) create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: user) end def article_note_communication_other create(:ticket_article_type, name: 'note_communication', communication: true) create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: other_agent) end def create_ticket_article(sender_name:, internal:, type_name:, updated_by:) UserInfo.current_user_id = updated_by.id ticket = create :ticket, group: group, customer: customer create(:ticket_article, sender_name: sender_name, internal: internal, type_name: type_name, ticket: ticket, body: "to be deleted #{offset} #{item}", created_at: offset.ago, updated_at: offset.ago) end context 'going through full stack' do context 'as admin' do let(:user) { admin } let(:item) { 'article_note_self' } let(:offset) { 0.minutes } it 'succeeds' do ensure_websocket do visit "ticket/zoom/#{article.ticket.id}" end within :active_ticket_article, article do click '.js-ArticleAction[data-type=delete]' end in_modal do click '.js-submit' end wait.until_disappears { find :active_ticket_article, article, wait: false } end end end context 'verifying permissions matrix' do shared_examples 'according to permission matrix' do |item:, expects_visible:, offset:, description:| context "looking at #{description} #{item}" do let(:item) { item } let(:offset) { offset } let(:matcher) { expects_visible ? :have_css : :have_no_css } it expects_visible ? 'delete button is visible' : 'delete button is not visible' do visit "ticket/zoom/#{article.ticket.id}" within :active_ticket_article, article do expect(page).to send(matcher, '.js-ArticleAction[data-type=delete]', wait: 0) end end end end shared_examples 'deleting ticket article' do |item:, now:, later:, much_later:| include_examples 'according to permission matrix', item: item, expects_visible: now, offset: 0.minutes, description: 'just created' include_examples 'according to permission matrix', item: item, expects_visible: later, offset: 6.minutes, description: 'few minutes old' include_examples 'according to permission matrix', item: item, expects_visible: much_later, offset: 11.minutes, description: 'very old' end context 'as admin' do let(:user) { admin } include_examples 'deleting ticket article', item: 'article_communication', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_self', now: true, later: true, much_later: false include_examples 'deleting ticket article', item: 'article_note_other', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_customer', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_communication_self', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_communication_other', now: false, later: false, much_later: false end context 'as agent' do let(:user) { agent } include_examples 'deleting ticket article', item: 'article_communication', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_self', now: true, later: true, much_later: false include_examples 'deleting ticket article', item: 'article_note_other', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_customer', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_communication_self', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_communication_other', now: false, later: false, much_later: false end context 'as customer' do let(:user) { customer } include_examples 'deleting ticket article', item: 'article_communication', now: false, later: false, much_later: false include_examples 'deleting ticket article', item: 'article_note_customer', now: false, later: false, much_later: false end context 'with custom offset' do let(:setting_delete_timeframe) { 6_000 } context 'as admin' do let(:user) { admin } include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe' include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe' end context 'as agent' do let(:user) { agent } include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe' include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe' end end context 'with timeframe as 0' do let(:setting_delete_timeframe) { 0 } context 'as agent' do let(:user) { agent } include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 99.days, description: 'long after' end end end context 'button is hidden on the go' do let(:setting_delete_timeframe) { 5 } let(:user) { agent } let(:item) { 'article_note_self' } let!(:article) { send(item) } let(:offset) { 0.seconds } it 'successfully' do visit "ticket/zoom/#{article.ticket.id}" within :active_ticket_article, article do find '.js-ArticleAction[data-type=delete]' # make sure delete button did show up expect(page).to have_no_css('.js-ArticleAction[data-type=delete]') end end end end context 'S/MIME active', authenticated_as: :authenticate do let(:system_email_address) { 'smime1@example.com' } let(:email_address) { create(:email_address, email: system_email_address) } let(:group) { create(:group, email_address: email_address) } let(:agent_groups) { [group] } let(:agent) { create(:agent, groups: agent_groups) } let(:sender_email_address) { 'smime2@example.com' } let(:customer) { create(:customer, email: sender_email_address) } let!(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) } def authenticate Setting.set('smime_integration', true) agent end context 'received mail' do context 'article meta information' do context 'success' do it 'shows encryption/sign information' do create(:ticket_article, preferences: { security: { type: 'S/MIME', encryption: { success: true, comment: 'COMMENT_ENCRYPT_SUCCESS', }, sign: { success: true, comment: 'COMMENT_SIGN_SUCCESS', }, } }, ticket: ticket) visit "#ticket/zoom/#{ticket.id}" expect(page).to have_css('svg.icon-lock') expect(page).to have_css('svg.icon-signed') open_article_meta expect(page).to have_css('span', text: 'Encrypted') expect(page).to have_css('span', text: 'Signed') expect(page).to have_css('span[title=COMMENT_ENCRYPT_SUCCESS]') expect(page).to have_css('span[title=COMMENT_SIGN_SUCCESS]') end end context 'error' do it 'shows create information about encryption/sign failed' do create(:ticket_article, preferences: { security: { type: 'S/MIME', encryption: { success: false, comment: 'Encryption failed because XXX', }, sign: { success: false, comment: 'Sign failed because XXX', }, } }, ticket: ticket) visit "#ticket/zoom/#{ticket.id}" expect(page).to have_css('svg.icon-not-signed') open_article_meta expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX') expect(page).to have_css('div.alert.alert--warning', text: 'Sign failed because XXX') end end end context 'certificate not present at time of arrival' do it 'retry' do smime1 = create(:smime_certificate, :with_private, fixture: system_email_address) smime2 = create(:smime_certificate, :with_private, fixture: sender_email_address) mail = Channel::EmailBuild.build( from: sender_email_address, to: system_email_address, body: 'somebody with some text', content_type: 'text/plain', security: { type: 'S/MIME', sign: { success: true, }, encryption: { success: true, }, }, ) smime1.destroy smime2.destroy parsed_mail = Channel::EmailParser.new.parse(mail.to_s) ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw']) expect(Ticket::Article.find(article.id).body).to eq('no visible content') create(:smime_certificate, fixture: sender_email_address) create(:smime_certificate, :with_private, fixture: system_email_address) visit "#ticket/zoom/#{ticket.id}" expect(page).to have_no_css('.article-content', text: 'somebody with some text') click '.js-securityRetryProcess' expect(page).to have_css('.article-content', text: 'somebody with some text') end end end context 'replying', authenticated_as: :setup_and_authenticate do def setup_and_authenticate create(:ticket_article, ticket: ticket, from: customer.email) create(:smime_certificate, :with_private, fixture: system_email_address) create(:smime_certificate, fixture: sender_email_address) authenticate end it 'plain' do visit "#ticket/zoom/#{ticket.id}" all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) expect(page).to have_css('.js-securitySign.btn--active', wait: 5) click '.js-securityEncrypt' click '.js-securitySign' click '.js-submit' expect(page).to have_css('.ticket-article-item', count: 2) expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil end it 'signed' do visit "#ticket/zoom/#{ticket.id}" all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) expect(page).to have_css('.js-securitySign.btn--active', wait: 5) click '.js-securityEncrypt' click '.js-submit' expect(page).to have_css('.ticket-article-item', count: 2) expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true end it 'encrypted' do visit "#ticket/zoom/#{ticket.id}" all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) expect(page).to have_css('.js-securitySign.btn--active', wait: 5) click '.js-securitySign' click '.js-submit' expect(page).to have_css('.ticket-article-item', count: 2) expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil end it 'signed and encrypted' do visit "#ticket/zoom/#{ticket.id}" all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) expect(page).to have_css('.js-securitySign.btn--active', wait: 5) click '.js-submit' expect(page).to have_css('.ticket-article-item', count: 2) expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true end end context 'Group default behavior' do let(:smime_config) { {} } def authenticate Setting.set('smime_integration', true) Setting.set('smime_config', smime_config) create(:ticket_article, ticket: ticket, from: customer.email) create(:smime_certificate, :with_private, fixture: system_email_address) create(:smime_certificate, fixture: sender_email_address) agent end shared_examples 'security defaults example' do |sign:, encrypt:| it "security defaults sign: #{sign}, encrypt: #{encrypt}" do within(:active_content) do encrypt_button = find('.js-securityEncrypt', wait: 5) sign_button = find('.js-securitySign', wait: 5) await_empty_ajax_queue active_button_class = '.btn--active' expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt) expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign) end end end shared_examples 'security defaults' do |sign:, encrypt:| before do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') await_empty_ajax_queue end end include_examples 'security defaults example', sign: sign, encrypt: encrypt end shared_examples 'security defaults group change' do |sign:, encrypt:| before do visit "#ticket/zoom/#{ticket.id}" within(:active_content) do all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') await_empty_ajax_queue select new_group.name, from: 'group_id' end end include_examples 'security defaults example', sign: sign, encrypt: encrypt end context 'not configured' do it_behaves_like 'security defaults', sign: true, encrypt: true end context 'configuration present' do let(:smime_config) do { 'group_id' => group_defaults } end let(:group_defaults) do { 'default_encryption' => { group.id.to_s => default_encryption, }, 'default_sign' => { group.id.to_s => default_sign, } } end let(:default_sign) { true } let(:default_encryption) { true } shared_examples 'sign and encrypt variations' do |check_examples_name| it_behaves_like check_examples_name, sign: true, encrypt: true context 'no value' do let(:group_defaults) { {} } it_behaves_like check_examples_name, sign: true, encrypt: true end context 'signing disabled' do let(:default_sign) { false } it_behaves_like check_examples_name, sign: false, encrypt: true end context 'encryption disabled' do let(:default_encryption) { false } it_behaves_like check_examples_name, sign: true, encrypt: false end end context 'same Group' do it_behaves_like 'sign and encrypt variations', 'security defaults' end context 'Group change' do let(:new_group) { create(:group, email_address: email_address) } let(:agent_groups) { [group, new_group] } let(:group_defaults) do { 'default_encryption' => { new_group.id.to_s => default_encryption, }, 'default_sign' => { new_group.id.to_s => default_sign, } } end it_behaves_like 'sign and encrypt variations', 'security defaults group change' end end end end describe 'linking Knowledge Base answer' do include_context 'basic Knowledge Base' let(:ticket) { create :ticket, group: Group.find_by(name: 'Users') } let(:answer) { published_answer } let(:translation) { answer.translations.first } shared_examples 'verify linking' do it 'allows to look up an answer' do visit "#ticket/zoom/#{ticket.id}" within :active_content do within '.link_kb_answers' do find('.js-add').click find('.js-input').send_keys translation.title find(%(li[data-value="#{translation.id}"])).click expect(find('.link_kb_answers ol')).to have_text translation.title end end end end context 'with ES', searchindex: true, authenticated_as: :authenticate do def authenticate configure_elasticsearch(required: true, rebuild: true) do answer end true end include_examples 'verify linking' end context 'without ES', authenticated_as: :authenticate do def authenticate answer true end include_examples 'verify linking' end end describe 'forwarding article with an image' do let(:ticket_article_body) do filename = 'squares.png' file = File.binread(Rails.root.join("spec/fixtures/image/#{filename}")) ext = File.extname(filename)[1...] base64 = Base64.encode64(file).delete("\n") "
" end def current_ticket Ticket.find current_url.split('/').last end def create_ticket visit '#ticket/create' within :active_content do find('[data-type=email-out]').click find('[name=title]').fill_in with: 'Title' find('[name=customer_id_completion]').fill_in with: 'customer@example.com' find('[name=group_id]').select 'Users' find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\"" find('.js-submit').click end await_empty_ajax_queue end def forward within :active_content do click '.js-ArticleAction[data-type=emailForward]' fill_in 'To', with: 'customer@example.com' find('.js-submit').click end await_empty_ajax_queue end def images_identical?(image_a, image_b) return false if image_a.height != image_b.height return false if image_a.width != image_b.width image_a.height.times do |y| image_a.row(y).each_with_index do |pixel, x| return false if pixel != image_b[x, y] end end true end it 'keeps image intact' do create_ticket forward images = current_ticket.articles.map do |article| ChunkyPNG::Image.from_string article.attachments.first.content end expect(images_identical?(images.first, images.second)).to be(true) end end context 'object manager attribute permission view' do let!(:group_users) { Group.find_by(name: 'Users') } shared_examples 'shows attributes and values for agent view and editable' do it 'shows attributes and values for agent view and editable', authenticated_as: :current_user do visit "ticket/zoom/#{ticket.id}" refresh # refresh to have assets generated for ticket expect(page).to have_select('state_id', options: ['new', 'open', 'pending reminder', 'pending close', 'closed']) expect(page).to have_select('priority_id') expect(page).to have_select('owner_id') expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]') end end shared_examples 'shows attributes and values for agent view but disabled' do it 'shows attributes and values for agent view but disabled', authenticated_as: :current_user do visit "ticket/zoom/#{ticket.id}" refresh # refresh to have assets generated for ticket expect(page).to have_css('select[name=state_id][disabled]') expect(page).to have_css('select[name=priority_id][disabled]') expect(page).to have_css('select[name=owner_id][disabled]') expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]') end end shared_examples 'shows attributes and values for customer view' do it 'shows attributes and values for customer view', authenticated_as: :current_user do visit "ticket/zoom/#{ticket.id}" refresh # refresh to have assets generated for ticket expect(page).to have_select('state_id', options: %w[new open closed]) expect(page).to have_no_select('priority_id') expect(page).to have_no_select('owner_id') expect(page).to have_no_css('div.tabsSidebar-tab[data-tab=customer]') end end context 'as customer' do let!(:current_user) { create(:customer) } let(:ticket) { create(:ticket, customer: current_user) } include_examples 'shows attributes and values for customer view' end context 'as agent with full permissions' do let(:current_user) { create(:agent, groups: [ group_users ] ) } let(:ticket) { create(:ticket, group: group_users ) } include_examples 'shows attributes and values for agent view and editable' end context 'as agent with change permissions' do let!(:current_user) { create(:agent) } let(:ticket) { create(:ticket, group: group_users) } before do current_user.group_names_access_map = { group_users.name => %w[read change], } end include_examples 'shows attributes and values for agent view and editable' end context 'as agent with read permissions' do let!(:current_user) { create(:agent) } let(:ticket) { create(:ticket, group: group_users) } before do current_user.group_names_access_map = { group_users.name => 'read', } end include_examples 'shows attributes and values for agent view but disabled' end context 'as agent+customer with full permissions' do let!(:current_user) { create(:agent_and_customer, groups: [ group_users ] ) } context 'normal ticket' do let(:ticket) { create(:ticket, group: group_users ) } include_examples 'shows attributes and values for agent view and editable' end context 'ticket where current_user is also customer' do let(:ticket) { create(:ticket, customer: current_user, group: group_users ) } include_examples 'shows attributes and values for agent view and editable' end end context 'as agent+customer with change permissions' do let!(:current_user) { create(:agent_and_customer) } before do current_user.group_names_access_map = { group_users.name => %w[read change], } end context 'normal ticket' do let(:ticket) { create(:ticket, group: group_users) } include_examples 'shows attributes and values for agent view and editable' end context 'ticket where current_user is also customer' do let(:ticket) { create(:ticket, customer: current_user, group: group_users) } include_examples 'shows attributes and values for agent view and editable' end end context 'as agent+customer with read permissions' do let!(:current_user) { create(:agent_and_customer) } before do current_user.group_names_access_map = { group_users.name => 'read', } end context 'normal ticket' do let(:ticket) { create(:ticket, group: group_users) } include_examples 'shows attributes and values for agent view but disabled' end context 'ticket where current_user is also customer' do let(:ticket) { create(:ticket, customer: current_user, group: group_users) } include_examples 'shows attributes and values for agent view but disabled' end end context 'as agent+customer but only customer for the ticket (no agent access)' do let!(:current_user) { create(:agent_and_customer) } let(:ticket) { create(:ticket, customer: current_user) } include_examples 'shows attributes and values for customer view' end end describe 'note visibility', authenticated_as: :customer do context 'when logged in as a customer' do let(:customer) { create(:customer) } let(:ticket) { create(:ticket, customer: customer) } let!(:ticket_article) { create(:ticket_article, ticket: ticket) } let!(:ticket_note) { create(:ticket_article, ticket: ticket, internal: true, type_name: 'note') } it 'previously created private note is not visible' do visit "ticket/zoom/#{ticket_article.ticket.id}" expect(page).to have_no_selector(:active_ticket_article, ticket_note) end it 'previously created private note shows up via WS push' do visit "ticket/zoom/#{ticket_article.ticket.id}" # make sure ticket is done loading and change will be pushed via WS find(:active_ticket_article, ticket_article) await_empty_ajax_queue ticket_note.update!(internal: false) expect(page).to have_selector(:active_ticket_article, ticket_note) end end end # https://github.com/zammad/zammad/issues/3260 describe 'next in overview macro changes URL', authenticated_as: :authenticate do let(:next_ticket) { create(:ticket, title: 'next Ticket', group: Group.first) } let(:macro) { create(:macro, name: 'next macro', ux_flow_next_up: 'next_from_overview') } def authenticate next_ticket && macro true end it 'to next Ticket ID' do visit 'ticket/view/all_unassigned' click_on 'Welcome to Zammad!' click '.js-openDropdownMacro' find(:macro, macro.id).click wait(5, interval: 1).until_constant { current_url } expect(current_url).to include("ticket/zoom/#{next_ticket.id}") end end # https://github.com/zammad/zammad/issues/3279 describe 'previous/next clickability when at last or first ticket' do let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) } let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) } before do ticket_a && ticket_b visit 'ticket/view/all_unassigned' end it 'previous is not clickable for the first item' do open_nth_item(0) expect { click '.pagination .previous' }.not_to change { current_url } end it 'next is clickable for the first item' do open_nth_item(0) expect { click '.pagination .next' }.to change { current_url } end it 'previous is clickable for the middle item' do open_nth_item(1) expect { click '.pagination .previous' }.to change { current_url } end it 'next is clickable for the middle item' do open_nth_item(1) expect { click '.pagination .next' }.to change { current_url } end it 'previous is clickable for the last item' do open_nth_item(2) expect { click '.pagination .previous' }.to change { current_url } end it 'next is not clickable for the last item' do open_nth_item(2) expect { click '.pagination .next' }.not_to change { current_url } end def open_nth_item(nth) within :active_content do find_all('.table tr.item .user-popover')[nth].click end end end # https://github.com/zammad/zammad/issues/3267 describe 'previous/next buttons are added when open ticket is opened from overview' do let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) } let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) } # prepare an opened ticket and go to overview before do ticket_a && ticket_b visit "ticket/zoom/#{ticket_a.id}" await_empty_ajax_queue visit 'ticket/view/all_unassigned' end it 'adds previous/next buttons to existing ticket' do within :active_content do click_on ticket_a.title expect(page).to have_css('.pagination-counter') end end it 'keeps previous/next buttons when navigating to overview ticket from elsewhere' do within :active_content do click_on ticket_a.title visit 'dashboard' visit "ticket/zoom/#{ticket_a.id}" expect(page).to have_css('.pagination-counter') end end end # https://github.com/zammad/zammad/issues/2942 describe 'attachemts are lost in specific conditions' do let(:ticket) { create(:ticket, group: Group.first) } it 'attachment is retained when forwarding a fresh article' do ensure_websocket do visit "ticket/zoom/#{ticket.id}" end # add an article, forcing reset of form_id find('.articleNewEdit-body').send_keys('Note here') click '.js-submit' # create a on-the-fly article with attachment that will get pushed to open browser article1 = create(:ticket_article, ticket: ticket) Store.add( object: 'Ticket::Article', o_id: article1.id, data: 'some content', filename: 'some_file.txt', preferences: { 'Content-Type' => 'text/plain', }, created_by_id: 1, ) within :active_ticket_article, article1 do find('a[data-type=emailForward]').click end fill_in 'To', with: 'forward@example.org' find('.articleNewEdit-body').send_keys('Forwarding with the attachment') click '.js-submit' await_empty_ajax_queue # check if attachment was forwarded successfully within :active_ticket_article, ticket.reload.articles.last do within '.attachments--list' do expect(page).to have_text('some_file.txt') end end end end end