require 'rails_helper' require 'models/application_model_examples' require 'models/concerns/can_lookup_examples' RSpec.describe Ticket, type: :model do it_behaves_like 'ApplicationModel' it_behaves_like 'CanLookup' describe '#merge_to' do it 'reassigns all links to the target ticket after merge' do source_ticket = create(:ticket) target_ticket = create(:ticket) important_ticket1 = create(:ticket) important_ticket2 = create(:ticket) important_ticket3 = create(:ticket) create(:link, link_object_source_value: source_ticket.id, link_object_target_value: important_ticket1.id) create(:link, link_object_source_value: source_ticket.id, link_object_target_value: important_ticket2.id) create(:link, link_object_source_value: source_ticket.id, link_object_target_value: important_ticket3.id) source_ticket.merge_to( ticket_id: target_ticket.id, user_id: 1, ) links = Link.list( link_object: 'Ticket', link_object_value: target_ticket.id, ) expected_ticket_ids = [source_ticket.id, important_ticket1.id, important_ticket2.id, important_ticket3.id ] check_ticket_ids = links.collect { |link| link['link_object_value'] } expect(check_ticket_ids).to match_array(expected_ticket_ids) end it 'prevents cross merging tickets' do source_ticket = create(:ticket) target_ticket = create(:ticket) result = source_ticket.merge_to( ticket_id: target_ticket.id, user_id: 1, ) expect(result).to be(true) expect do result = target_ticket.merge_to( ticket_id: source_ticket.id, user_id: 1, ) end.to raise_error('ticket already merged, no merge into merged ticket possible') end it 'prevents merging ticket in it self' do source_ticket = create(:ticket) expect do result = source_ticket.merge_to( ticket_id: source_ticket.id, user_id: 1, ) end.to raise_error('Can\'t merge ticket with it self!') end end describe '.create' do it 'handles NULL byte in title' do expect(create(:ticket, title: "some title \u0000 123")) .to be_persisted end end describe '#destroy' do it 'deletes all related objects before destroy' do ApplicationHandleInfo.current = 'application_server' source_ticket = create(:ticket) # create some links important_ticket1 = create(:ticket) important_ticket2 = create(:ticket) important_ticket3 = create(:ticket) # create some articles create(:ticket_article, ticket_id: source_ticket.id) create(:ticket_article, ticket_id: source_ticket.id) create(:ticket_article, ticket_id: source_ticket.id) create(:link, link_object_source_value: source_ticket.id, link_object_target_value: important_ticket1.id) create(:link, link_object_source_value: important_ticket2.id, link_object_target_value: source_ticket.id) create(:link, link_object_source_value: source_ticket.id, link_object_target_value: important_ticket3.id) create(:online_notification, o_id: source_ticket.id) create(:tag, o_id: source_ticket.id) Observer::Transaction.commit Scheduler.worker(true) # get before destroy activities = ActivityStream.where( activity_stream_object_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) links = Link.list( link_object: 'Ticket', link_object_value: source_ticket.id ) articles = Ticket::Article.where(ticket_id: source_ticket.id) history = History.list('Ticket', source_ticket.id, nil, true) karma_log = Karma::ActivityLog.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) online_notifications = OnlineNotification.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) recent_views = OnlineNotification.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) tags = Tag.tag_list( object: 'Ticket', o_id: source_ticket.id, ) # check before destroy expect(activities.count).to be >= 0 expect(links.count).to be >= 0 expect(articles.count).to be >= 0 expect(history[:list].count).to be >= 0 expect(karma_log.count).to be >= 0 expect(online_notifications.count).to be >= 0 expect(recent_views.count).to be >= 0 expect(tags.count).to be >= 0 # destroy ticket source_ticket.destroy # get after destroy activities = ActivityStream.where( activity_stream_object_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) links = Link.list( link_object: 'Ticket', link_object_value: source_ticket.id ) articles = Ticket::Article.where(ticket_id: source_ticket.id) history = History.list('Ticket', source_ticket.id, nil, true) karma_log = Karma::ActivityLog.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) online_notifications = OnlineNotification.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) recent_views = OnlineNotification.where( object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: source_ticket.id, ) tags = Tag.tag_list( object: 'Ticket', o_id: source_ticket.id, ) # check after destroy expect(activities.count).to be == 0 expect(links.count).to be == 0 expect(articles.count).to be == 0 expect(history[:list].count).to be == 0 expect(karma_log.count).to be == 0 expect(online_notifications.count).to be == 0 expect(recent_views.count).to be == 0 expect(tags.count).to be == 0 end end describe '#perform_changes' do it 'performs a ticket state change on a ticket' do source_ticket = create(:ticket) changes = { 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s }, } source_ticket.perform_changes(changes, 'trigger', source_ticket, User.find(1)) source_ticket.reload expect(source_ticket.state.name).to eq('closed') end it 'performs a ticket deletion on a ticket' do source_ticket = create(:ticket) changes = { 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s }, 'ticket.action' => { 'value' => 'delete' }, } source_ticket.perform_changes(changes, 'trigger', source_ticket, User.find(1)) ticket_with_source_ids = Ticket.where(id: source_ticket.id) expect(ticket_with_source_ids).to match_array([]) end # Regression test for https://github.com/zammad/zammad/issues/2001 it 'does not modify its arguments' do trigger = Trigger.new( perform: { 'notification.email' => { body: "Hello \#{ticket.customer.firstname} \#{ticket.customer.lastname},", recipient: %w[article_last_sender ticket_owner ticket_customer ticket_agents], subject: "Autoclose (\#{ticket.title})" } } ) expect { Ticket.first.perform_changes(trigger.perform, 'trigger', {}, 1) } .to not_change { trigger.perform['notification.email'][:body] } .and not_change { trigger.perform['notification.email'][:subject] } end # Regression test for https://github.com/zammad/zammad/issues/1543 # # If a new article fires an email notification trigger, # and then another article is added to the same ticket # before that trigger is performed, # the email template's 'article' var should refer to the originating article, # not the newest one. # # (This occurs whenever one action fires multiple email notification triggers.) it 'passes the correct article to NotificationFactory::Mailer' do # required by Ticket#perform_changes for email notifications Group.first.update(email_address: create(:email_address)) ticket = Ticket.first orig_article = Ticket::Article.where(ticket_id: ticket.id).first newer_article = create(:ticket_article, ticket_id: ticket.id) trigger = Trigger.new( perform: { 'notification.email' => { body: '', recipient: 'ticket_customer', subject: '' } } ) allow(NotificationFactory::Mailer).to receive(:template).and_return('') ticket.perform_changes(trigger.perform, 'trigger', { article_id: orig_article.id }, 1) expect(NotificationFactory::Mailer) .to have_received(:template) .with(hash_including(objects: { ticket: ticket, article: orig_article })) .at_least(:once) expect(NotificationFactory::Mailer) .not_to have_received(:template) .with(hash_including(objects: { ticket: ticket, article: newer_article })) end end describe '#selectors' do # https://github.com/zammad/zammad/issues/1769 it 'does not return multiple results for a single ticket' do source_ticket = create(:ticket) source_ticket2 = create(:ticket) # create some articles create(:ticket_article, ticket_id: source_ticket.id, from: 'asdf1@blubselector.de') create(:ticket_article, ticket_id: source_ticket.id, from: 'asdf2@blubselector.de') create(:ticket_article, ticket_id: source_ticket.id, from: 'asdf3@blubselector.de') create(:ticket_article, ticket_id: source_ticket2.id, from: 'asdf4@blubselector.de') create(:ticket_article, ticket_id: source_ticket2.id, from: 'asdf5@blubselector.de') create(:ticket_article, ticket_id: source_ticket2.id, from: 'asdf6@blubselector.de') condition = { 'article.from' => { operator: 'contains', value: 'blubselector.de', }, } ticket_count, tickets = Ticket.selectors(condition, 100, nil, 'full') expect(ticket_count).to be == 2 expect(tickets.count).to be == 2 end end context 'callbacks' do describe '#reset_pending_time' do it 'resets the pending time on state change' do ticket = create(:ticket, state: Ticket::State.lookup(name: 'pending reminder'), pending_time: Time.zone.now + 2.days) expect(ticket.pending_time).not_to be nil ticket.update!(state: Ticket::State.lookup(name: 'open')) expect(ticket.pending_time).to be nil end it 'lets handle ActiveRecord nil as new value' do ticket = create(:ticket) expect do ticket.update!(state: nil) end.to raise_error(ActiveRecord::StatementInvalid) end end end describe '#access?' do context 'agent' do it 'allows owner access' do owner = create(:agent_user) ticket = create(:ticket, owner: owner) expect( ticket.access?(owner, 'full') ).to be(true) end it 'allows group access' do agent = create(:agent_user) group = create(:group) ticket = create(:ticket, group: group) agent.group_names_access_map = { group.name => 'full', } expect( ticket.access?(agent, 'full') ).to be(true) end it 'prevents unauthorized access' do agent = create(:agent_user) ticket = create(:ticket) expect( ticket.access?(agent, 'read') ).to be(false) end end context 'customer' do it 'allows assigned access' do customer = create(:customer_user) ticket = create(:ticket, customer: customer) expect( ticket.access?(customer, 'full') ).to be(true) end context 'organization' do it 'allows access for shared' do organization = create(:organization) assigned = create(:customer_user, organization: organization) collegue = create(:customer_user, organization: organization) ticket = create(:ticket, customer: assigned) expect( ticket.access?(collegue, 'full') ).to be(true) end it 'prevents unshared access' do organization = create(:organization, shared: false) assigned = create(:customer_user, organization: organization) collegue = create(:customer_user, organization: organization) ticket = create(:ticket, customer: assigned) expect( ticket.access?(collegue, 'full') ).to be(false) end end it 'prevents unauthorized access' do customer = create(:customer_user) ticket = create(:ticket) expect( ticket.access?(customer, 'read') ).to be(false) end end end end