Browse Source

Initial support of recursive triggers for tickets - issue #2035.

Martin Edenhofer 6 years ago
parent
commit
b403432c76

+ 1 - 0
.rubocop.yml

@@ -202,6 +202,7 @@ Lint/InterpolationCheck:
     - "test/unit/notification_factory_renderer_test.rb"
     - "test/unit/notification_factory_template_test.rb"
     - "test/unit/ticket_trigger_test.rb"
+    - "test/unit/ticket_trigger_recursive_disabled_test.rb"
 
 # RSpec tests
 Style/NumericPredicate:

+ 4 - 0
app/models/channel/email_parser.rb

@@ -677,6 +677,10 @@ returns
       end
     end
 
+    ticket.reload
+    article.reload
+    session_user.reload
+
     # run postmaster post filter
     filters = {}
     Setting.where(area: 'Postmaster::PostFilter').order(:name).each do |setting|

+ 179 - 2
app/models/ticket.rb

@@ -1046,11 +1046,11 @@ perform changes on ticket
         tags = value['value'].split(/,/)
         if value['operator'] == 'add'
           tags.each do |tag|
-            tag_add(tag)
+            tag_add(tag, current_user_id || 1)
           end
         elsif value['operator'] == 'remove'
           tags.each do |tag|
-            tag_remove(tag)
+            tag_remove(tag, current_user_id || 1)
           end
         else
           logger.error "Unknown #{attribute} operator #{value['operator']}"
@@ -1085,6 +1085,183 @@ perform changes on ticket
     end
     return if !changed
     save
+
+    true
+  end
+
+=begin
+
+perform active triggers on ticket
+
+  Ticket.perform_triggers(ticket, article, item, options)
+
+=end
+
+  def self.perform_triggers(ticket, article, item, options = {})
+    recursive = Setting.get('ticket_trigger_recursive')
+    type = options[:type] || item[:type]
+    local_options = options.clone
+    local_options[:type] = type
+    local_options[:reset_user_id] = true
+    local_options[:disable] = ['Transaction::Notification']
+    local_options[:trigger_ids] ||= {}
+    local_options[:trigger_ids][ticket.id] ||= []
+    local_options[:loop_count] ||= 0
+    local_options[:loop_count] += 1
+
+    ticket_trigger_recursive_max_loop = Setting.get('ticket_trigger_recursive_max_loop')&.to_i || 10
+    if local_options[:loop_count] > ticket_trigger_recursive_max_loop
+      message = "Stopped perform_triggers for this object (Ticket/#{ticket.id}), because loop count was #{local_options[:loop_count]}!"
+      logger.info { message }
+      return [false, message]
+    end
+
+    triggers = if Rails.configuration.db_case_sensitive
+                 ::Trigger.where(active: true).order('LOWER(name)')
+               else
+                 ::Trigger.where(active: true).order(:name)
+               end
+    return [true, 'No triggers active'] if triggers.blank?
+
+    # check if notification should be send because of customer emails
+    send_notification = true
+    if local_options[:send_notification] == false
+      send_notification = false
+    elsif item[:article_id]
+      article = Ticket::Article.lookup(id: item[:article_id])
+      if article&.preferences && article.preferences['send-auto-response'] == false
+        send_notification = false
+      end
+    end
+
+    Transaction.execute(local_options) do
+      triggers.each do |trigger|
+        condition = trigger.condition
+
+        # check if one article attribute is used
+        one_has_changed_done = false
+        article_selector = false
+        trigger.condition.each_key do |key|
+          (object_name, attribute) = key.split('.', 2)
+          next if object_name != 'article'
+          next if attribute == 'id'
+          article_selector = true
+        end
+        if article && article_selector
+          one_has_changed_done = true
+        end
+        if article && type == 'update'
+          one_has_changed_done = true
+        end
+
+        # check ticket "has changed" options
+        has_changed_done = true
+        condition.each do |key, value|
+          next if value.blank?
+          next if value['operator'].blank?
+          next if !value['operator']['has changed']
+
+          # remove condition item, because it has changed
+          (object_name, attribute) = key.split('.', 2)
+          next if object_name != 'ticket'
+          next if item[:changes].blank?
+          next if !item[:changes].key?(attribute)
+          condition.delete(key)
+          one_has_changed_done = true
+        end
+
+        # check if we have not matching "has changed" attributes
+        condition.each_value do |value|
+          next if value.blank?
+          next if value['operator'].blank?
+          next if !value['operator']['has changed']
+          has_changed_done = false
+          break
+        end
+
+        # check ticket action
+        if condition['ticket.action']
+          next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != type
+          next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == type
+          condition.delete('ticket.action')
+        end
+        next if !has_changed_done
+
+        # check in min one attribute of condition has changed on update
+        one_has_changed_condition = false
+        if type == 'update'
+
+          # verify if ticket condition exists
+          condition.each_key do |key|
+            (object_name, attribute) = key.split('.', 2)
+            next if object_name != 'ticket'
+            one_has_changed_condition = true
+            next if item[:changes].blank?
+            next if !item[:changes].key?(attribute)
+            one_has_changed_done = true
+            break
+          end
+          next if one_has_changed_condition && !one_has_changed_done
+        end
+
+        # check if ticket selector is matching
+        condition['ticket.id'] = {
+          operator: 'is',
+          value: ticket.id,
+        }
+        next if article_selector && !article
+
+        # check if article selector is matching
+        if article_selector
+          condition['article.id'] = {
+            operator: 'is',
+            value: article.id,
+          }
+        end
+
+        # verify is condition is matching
+        ticket_count, tickets = Ticket.selectors(condition, 1)
+
+        next if ticket_count.blank?
+        next if ticket_count.zero?
+        next if tickets.first.id != ticket.id
+        user_id = ticket.updated_by_id
+        if article
+          user_id = article.updated_by_id
+        end
+
+        if recursive == false && local_options[:loop_count] > 1
+          message = "Do not execute recursive triggers per default until Zammad 3.0. With Zammad 3.0 and higher the following trigger is executed '#{trigger.name}' on Ticket:#{ticket.id}. Please review your current triggers and change them if needed."
+          logger.info { message }
+          return [true, message]
+        end
+
+        local_send_notification = true
+        if article && send_notification == false && trigger.perform['notification.email'] && trigger.perform['notification.email']['recipient']
+          recipient = trigger.perform['notification.email']['recipient']
+          local_send_notification = false
+          local_options[:send_notification] = false
+          if recipient.include?('ticket_customer') || recipient.include?('article_last_sender')
+            logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because sender do not want to get auto responder for object (Ticket/#{ticket.id}/Article/#{article.id})" }
+            next
+          end
+        end
+
+        if local_options[:trigger_ids][ticket.id].include?(trigger.id)
+          logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because was already executed for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
+          next
+        end
+        local_options[:trigger_ids][ticket.id].push trigger.id
+        logger.info { "Execute trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
+
+        ticket.perform_changes(trigger.perform, 'trigger', item, user_id)
+
+        if recursive == true
+          Observer::Transaction.commit(local_options)
+        end
+      end
+    end
+    [true, ticket, local_options]
   end
 
 =begin

+ 1 - 4
app/models/transaction.rb

@@ -13,10 +13,7 @@ class Transaction
       if options[:interface_handle]
         ApplicationHandleInfo.current = original_interface_handle
       end
-      Observer::Transaction.commit(
-        disable_notification: options[:disable_notification],
-        disable: options[:disable],
-      )
+      Observer::Transaction.commit(options)
       PushMessages.finish
     end
   end

+ 1 - 103
app/models/transaction/trigger.rb

@@ -29,13 +29,6 @@ class Transaction::Trigger
 
     return if @item[:object] != 'Ticket'
 
-    triggers = if Rails.configuration.db_case_sensitive
-                 ::Trigger.where(active: true).order('LOWER(name)')
-               else
-                 ::Trigger.where(active: true).order(:name)
-               end
-    return if triggers.blank?
-
     ticket = Ticket.lookup(id: @item[:object_id])
     return if !ticket
     if @item[:article_id]
@@ -44,103 +37,8 @@ class Transaction::Trigger
 
     original_user_id = UserInfo.current_user_id
 
-    Transaction.execute(reset_user_id: true, disable: ['Transaction::Trigger', 'Transaction::Notification']) do
-      triggers.each do |trigger|
-        condition = trigger.condition
-
-        # check if one article attribute is used
-        one_has_changed_done = false
-        article_selector = false
-        trigger.condition.each_key do |key|
-          (object_name, attribute) = key.split('.', 2)
-          next if object_name != 'article'
-          next if attribute == 'id'
-          article_selector = true
-        end
-        if article && article_selector
-          one_has_changed_done = true
-        end
-        if article && @item[:type] == 'update'
-          one_has_changed_done = true
-        end
-
-        # check ticket "has changed" options
-        has_changed_done = true
-        condition.each do |key, value|
-          next if value.blank?
-          next if value['operator'].blank?
-          next if !value['operator']['has changed']
-
-          # remove condition item, because it has changed
-          (object_name, attribute) = key.split('.', 2)
-          next if object_name != 'ticket'
-          next if @item[:changes].blank?
-          next if !@item[:changes].key?(attribute)
-          condition.delete(key)
-          one_has_changed_done = true
-        end
-
-        # check if we have not matching "has changed" attributes
-        condition.each_value do |value|
-          next if value.blank?
-          next if value['operator'].blank?
-          next if !value['operator']['has changed']
-          has_changed_done = false
-          break
-        end
-
-        # check ticket action
-        if condition['ticket.action']
-          next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != @item[:type]
-          next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == @item[:type]
-          condition.delete('ticket.action')
-        end
-        next if !has_changed_done
+    Ticket.perform_triggers(ticket, article, @item, @params)
 
-        # check in min one attribute of condition has changed on update
-        one_has_changed_condition = false
-        if @item[:type] == 'update'
-
-          # verify if ticket condition exists
-          condition.each_key do |key|
-            (object_name, attribute) = key.split('.', 2)
-            next if object_name != 'ticket'
-            one_has_changed_condition = true
-            next if @item[:changes].blank?
-            next if !@item[:changes].key?(attribute)
-            one_has_changed_done = true
-            break
-          end
-          next if one_has_changed_condition && !one_has_changed_done
-        end
-
-        # check if ticket selector is matching
-        condition['ticket.id'] = {
-          operator: 'is',
-          value: ticket.id,
-        }
-        next if article_selector && !article
-
-        # check if article selector is matching
-        if article_selector
-          condition['article.id'] = {
-            operator: 'is',
-            value: article.id,
-          }
-        end
-
-        # verify is condition is matching
-        ticket_count, tickets = Ticket.selectors(condition, 1)
-        next if ticket_count.blank?
-        next if ticket_count.zero?
-        next if tickets.first.id != ticket.id
-        user_id = ticket.updated_by_id
-        if article
-          user_id = article.updated_by_id
-        end
-        ticket.perform_changes(trigger.perform, 'trigger', @item, user_id)
-      end
-    end
     UserInfo.current_user_id = original_user_id
   end
 

+ 79 - 0
db/migrate/20180529000001_issue_2035_recursive_ticket_trigger.rb

@@ -0,0 +1,79 @@
+class Issue2035RecursiveTicketTrigger < ActiveRecord::Migration[5.1]
+  def change
+
+    # return if it's a new setup
+    return if !Setting.find_by(name: 'system_init_done')
+
+    Setting.create_if_not_exists(
+      title: 'Recursive Ticket Triggers',
+      name: 'ticket_trigger_recursive',
+      area: 'Ticket::Core',
+      description: 'Activate the recursive processing of ticket triggers.',
+      options: {
+        form: [
+          {
+            display: 'Recursive Ticket Triggers',
+            null: true,
+            name: 'ticket_trigger_recursive',
+            tag: 'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: false,
+      preferences: {
+        permission: ['admin.ticket'],
+        hidden: true,
+      },
+      frontend: false
+    )
+    Setting.create_if_not_exists(
+      title: 'Recursive Ticket Triggers Loop Max.',
+      name: 'ticket_trigger_recursive_max_loop',
+      area: 'Ticket::Core',
+      description: 'Maximum number of recursively executed triggers.',
+      options: {
+        form: [
+          {
+            display: 'Recursive Ticket Triggers',
+            null: true,
+            name: 'ticket_trigger_recursive_max_loop',
+            tag: 'select',
+            options: {
+              1 => ' 1',
+              2 => ' 2',
+              3 => ' 3',
+              4 => ' 4',
+              5 => ' 5',
+              6 => ' 6',
+              7 => ' 7',
+              8 => ' 8',
+              9 => ' 9',
+              10 => '10',
+              11 => '11',
+              12 => '12',
+              13 => '13',
+              14 => '14',
+              15 => '15',
+              16 => '16',
+              17 => '17',
+              18 => '18',
+              19 => '19',
+              20 => '20',
+            },
+          },
+        ],
+      },
+      state: 10,
+      preferences: {
+        permission: ['admin.ticket'],
+        hidden: true,
+      },
+      frontend: false
+    )
+
+  end
+end

+ 71 - 0
db/seeds/settings.rb

@@ -1978,6 +1978,77 @@ Setting.create_if_not_exists(
   frontend: false
 )
 
+Setting.create_if_not_exists(
+  title: 'Recursive Ticket Triggers',
+  name: 'ticket_trigger_recursive',
+  area: 'Ticket::Core',
+  description: 'Activate the recursive processing of ticket triggers.',
+  options: {
+    form: [
+      {
+        display: 'Recursive Ticket Triggers',
+        null: true,
+        name: 'ticket_trigger_recursive',
+        tag: 'boolean',
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  state: false,
+  preferences: {
+    permission: ['admin.ticket'],
+    hidden: true,
+  },
+  frontend: false
+)
+Setting.create_if_not_exists(
+  title: 'Recursive Ticket Triggers Loop Max.',
+  name: 'ticket_trigger_recursive_max_loop',
+  area: 'Ticket::Core',
+  description: 'Maximum number of recursively executed triggers.',
+  options: {
+    form: [
+      {
+        display: 'Recursive Ticket Triggers',
+        null: true,
+        name: 'ticket_trigger_recursive_max_loop',
+        tag: 'select',
+        options: {
+          1 => ' 1',
+          2 => ' 2',
+          3 => ' 3',
+          4 => ' 4',
+          5 => ' 5',
+          6 => ' 6',
+          7 => ' 7',
+          8 => ' 8',
+          9 => ' 9',
+          10 => '10',
+          11 => '11',
+          12 => '12',
+          13 => '13',
+          14 => '14',
+          15 => '15',
+          16 => '16',
+          17 => '17',
+          18 => '18',
+          19 => '19',
+          20 => '20',
+        },
+      },
+    ],
+  },
+  state: 10,
+  preferences: {
+    permission: ['admin.ticket'],
+    hidden: true,
+  },
+  frontend: false
+)
+
 Setting.create_if_not_exists(
   title: 'Enable Ticket creation',
   name: 'customer_ticket_create',

+ 657 - 10
test/unit/email_process_auto_response_test.rb

@@ -2,10 +2,10 @@ require 'test_helper'
 
 class EmailProcessAutoResponseTest < ActiveSupport::TestCase
 
-  test 'process auto reply check' do
+  test 'process auto reply check - 1' do
 
     roles  = Role.where(name: 'Agent')
-    agent1 = User.create_or_update(
+    agent1 = User.create!(
       login: 'ticket-auto-responder-agent1@example.com',
       firstname: 'AutoReponder',
       lastname: 'Agent1',
@@ -18,9 +18,13 @@ class EmailProcessAutoResponseTest < ActiveSupport::TestCase
       created_by_id: 1,
     )
 
-    Trigger.create_or_update(
-      name: 'auto reply',
+    Trigger.create!(
+      name: '002 auto reply',
       condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
         'ticket.state_id' => {
           'operator' => 'is',
           'value' => Ticket::State.lookup(name: 'new').id.to_s,
@@ -37,7 +41,7 @@ class EmailProcessAutoResponseTest < ActiveSupport::TestCase
         },
         'ticket.tags' => {
           'operator' => 'add',
-          'value' => 'aa, kk',
+          'value' => 'aa, kk, auto-reply',
         },
       },
       disable_notification: true,
@@ -188,8 +192,8 @@ test"
     assert_equal(1, article_p.ticket.articles.count)
 
     # add an agent notification
-    Trigger.create_or_update(
-      name: 'additional agent notification',
+    Trigger.create!(
+      name: '001 additional agent notification',
       condition: {
         'ticket.state_id' => {
           'operator' => 'is',
@@ -207,7 +211,7 @@ test"
         },
         'ticket.tags' => {
           'operator' => 'add',
-          'value' => 'aa, kk',
+          'value' => 'aa, kk, agent-notification',
         },
       },
       disable_notification: true,
@@ -226,20 +230,663 @@ Some Text"
     ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
     assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
     Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
     assert_equal(2, article_p.ticket.articles.count)
     article_customer = article_p.ticket.articles.first
     assert_equal('me@example.com', article_customer.from)
     assert_equal('customer@example.com', article_customer.to)
     assert_equal('Customer', article_customer.sender.name)
     assert_equal('email', article_customer.type.name)
-    article_notification = article_p.ticket.articles.last
+    article_notification = article_p.ticket.articles[1]
     assert_match(/New Ticket add. info/, article_notification.subject)
     assert_no_match(/me@example.com/, article_notification.to)
     assert_match(/#{agent1.email}/, article_notification.to)
     assert_equal('System', article_notification.sender.name)
     assert_equal('email', article_notification.type.name)
 
-    Trigger.destroy_all
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles.first
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', false)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert(tags.include?('auto-reply'))
+    assert_equal(3, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+    article_auto_reply = article_p.ticket.articles[2]
+    assert_match(/Thanks for your inquiry/, article_auto_reply.subject)
+    assert_match(/me@example.com/, article_auto_reply.to)
+    assert_equal('System', article_auto_reply.sender.name)
+    assert_equal('email', article_auto_reply.type.name)
+
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert(tags.include?('auto-reply'))
+    assert_equal(3, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+    article_auto_reply = article_p.ticket.articles[2]
+    assert_match(/Thanks for your inquiry/, article_auto_reply.subject)
+    assert_match(/me@example.com/, article_auto_reply.to)
+    assert_equal('System', article_auto_reply.sender.name)
+    assert_equal('email', article_auto_reply.type.name)
+
+  end
+
+  test 'process auto reply check - 2' do
+
+    roles  = Role.where(name: 'Agent')
+    agent1 = User.create!(
+      login: 'ticket-auto-responder-agent1@example.com',
+      firstname: 'AutoReponder',
+      lastname: 'Agent1',
+      email: 'ticket-auto-responder-agent1@example.com',
+      password: 'agentpw',
+      active: true,
+      roles: roles,
+      groups: Group.all,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create!(
+      name: '001 auto reply',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        }
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.tags' => {
+          'operator' => 'add',
+          'value' => 'aa, kk, auto-reply',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(2, article_p.ticket.articles.count)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+X-Loop: yes
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(1, article_p.ticket.articles.count)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+Precedence: Bulk
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+Auto-Submitted: auto-generated
+
+Some Text"
+    Scheduler.worker(true)
+    assert_equal(1, article_p.ticket.articles.count)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+X-Auto-Response-Suppress: All
+
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(1, article_p.ticket.articles.count)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+List-Unsubscribe: <mailto:somebody@example.com>
+
+Some Text"
+
+    fqdn = Setting.get('fqdn')
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+Message-ID: <1234@#{fqdn}>
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(1, article_p.ticket.articles.count)
+
+    fqdn = Setting.get('fqdn')
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+Message-ID: <1234@not_matching.#{fqdn}>
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(2, article_p.ticket.articles.count)
+
+    email_raw_string = "Return-Path: <XX@XX.XX>
+X-Original-To: sales@znuny.com
+Received: from mail-qk0-f170.example.com (mail-qk0-f170.example.com [209.1.1.1])
+    by arber.znuny.com (Postfix) with ESMTPS id C3AED5FE2E
+    for <sales@znuny.com>; Mon, 22 Aug 2016 19:03:15 +0200 (CEST)
+Received: by mail-qk0-f170.example.com with SMTP id t7so87721720qkh.1
+        for <sales@znuny.com>; Mon, 22 Aug 2016 10:03:15 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=XX.XX; s=example;
+        h=to:from:date:message-id:subject:mime-version:precedence
+         :auto-submitted:content-transfer-encoding:content-disposition;
+        bh=SL5tTVvGdxsKjLic38irxzlP439P3jixJH0QTG1HJ5I=;
+        b=CIk3PLELgjOCagyiFFbd6rlb8ZRDGYRUrg5Dntxa7e5X+PT4cgL+IE13N9TFkK8ZUJ
+         GohlaPLGiBymIYLTtYMKUpcf22oiX8ZgGiSu1aEMC1Gsa1ZDf+vpy4kd4+7EecRT3IWF
+         4RafQxeaqe67budhQpO1Z6UAel6BdJj0xguKM=
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=1e100.net; s=20130820;
+        h=x-gm-message-state:to:from:date:message-id:subject:mime-version
+         :precedence:auto-submitted:content-transfer-encoding
+         :content-disposition;
+        bh=SL5tTVvGdxsKjLic38irxzlP439P3jixJH0QTG1HJ5I=;
+        b=PYULo3xigc4O/cuNZ79OathQ5HDMFWWIwUxz6CHbpXDQR5k3EPy/skJU1992hVz9Rl
+         xiGwScBCkMqOjlxHjQSWhFJIxNtdvMk4m0bixBZ79IEvRuQa9cEbqjf6efnV58br5ftQ
+         2osHrtQczoSqLE/d61/o102RfQ0avVyX8XNJik0iepg8MiCY7LTOE9hrbnuDDLxgQecH
+         rMEfkR7bafcUj1YEto5Vd7uV11cVZYx8UIQqVAVbfygv8dTSFeOzz3NyM0M41rRexfYH
+         79Yi5i7z/Wk6q2427wkJ3FIR1B7VQVQEmcq/Texbch+gAXPGBNPUHdg2WHt7NXGktrHL
+         d3DA==
+X-Gm-Message-State: AE9vXwMCTnihGiG/tc7xNNlhFLcEK6DPp7otypJg5e4alD3xGK2R707BP29druIi/mcdNyaHg1vP5lSZ8EvrwvOF8iA0HNFhECGjBTJ40YrSJAR8E89xVwxFv/er+U3vEpqmPmt+hL4QhxK/+D2gKOcHSxku
+X-Received: by 10.1.1.1 with SMTP id 17mr25015996qkf.279.1471885393931;
+        Mon, 22 Aug 2016 10:03:13 -0700 (PDT)
+To: sales@znuny.com
+From: \"XXX\" <XX@XX.XX>
+Date: Mon, 22 Aug 2016 10:03:13 -0700
+Message-ID: <CA+kqV8PH1DU+zcSx3M00Hrm_oJedRLjbgAUdoi9p0+sMwYsyUg@mail.gmail.com>
+Subject: XX PieroXXway - vacation response RE: Callback Request: XX XX [Ticket#1118974]
+MIME-Version: 1.0
+Precedence: bulk
+X-Autoreply: yes
+Auto-Submitted: auto-replied
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    assert_equal(1, article_p.ticket.articles.count)
+
+    # add an agent notification
+    Trigger.create!(
+      name: '002 additional agent notification',
+      condition: {
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        }
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}',
+          'recipient' => 'ticket_agents',
+          'subject' => 'New Ticket add. info (#{ticket.title})!',
+        },
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.tags' => {
+          'operator' => 'add',
+          'value' => 'aa, kk, agent-notification',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+X-Loop: yes
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles.first
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles.first
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', false)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert(tags.include?('auto-reply'))
+    assert_equal(3, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_auto_reply = article_p.ticket.articles[1]
+    assert_match(/Thanks for your inquiry/, article_auto_reply.subject)
+    assert_match(/me@example.com/, article_auto_reply.to)
+    assert_equal('System', article_auto_reply.sender.name)
+    assert_equal('email', article_auto_reply.type.name)
+    article_notification = article_p.ticket.articles[2]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    tags = ticket_p.tag_list
+    assert_equal('new', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert(tags.include?('auto-reply'))
+    assert_equal(3, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_auto_reply = article_p.ticket.articles[1]
+    assert_match(/Thanks for your inquiry/, article_auto_reply.subject)
+    assert_match(/me@example.com/, article_auto_reply.to)
+    assert_equal('System', article_auto_reply.sender.name)
+    assert_equal('email', article_auto_reply.type.name)
+    article_notification = article_p.ticket.articles[2]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+  end
+
+  test 'process auto reply check - recursive' do
+
+    roles  = Role.where(name: 'Agent')
+    agent1 = User.create!(
+      login: 'ticket-auto-responder-agent1@example.com',
+      firstname: 'AutoReponder',
+      lastname: 'Agent1',
+      email: 'ticket-auto-responder-agent1@example.com',
+      password: 'agentpw',
+      active: true,
+      roles: roles,
+      groups: Group.all,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create!(
+      name: '001 auto reply',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        }
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.tags' => {
+          'operator' => 'add',
+          'value' => 'aa, kk, auto-reply',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    # add an agent notification
+    Trigger.create!(
+      name: '002 additional agent notification',
+      condition: {
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        }
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}',
+          'recipient' => 'ticket_agents',
+          'subject' => 'New Ticket add. info (#{ticket.title})!',
+        },
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+        'ticket.tags' => {
+          'operator' => 'add',
+          'value' => 'aa, kk, agent-notification',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+X-Loop: yes
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('open', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles.first
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('open', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(3, tags.count)
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles.first
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', false)
+
+    email_raw_string = "From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text"
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+
+    tags = ticket_p.tag_list
+    assert_equal('open', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert_equal(2, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+
+    Setting.set('ticket_trigger_recursive', true)
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
+    Scheduler.worker(true)
+    tags = ticket_p.tag_list
+    assert_equal('open', ticket_p.state.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert(tags.include?('aa'))
+    assert(tags.include?('kk'))
+    assert(tags.include?('agent-notification'))
+    assert(tags.include?('auto-reply'))
+    assert_equal(3, article_p.ticket.articles.count)
+    article_customer = article_p.ticket.articles[0]
+    assert_equal('me@example.com', article_customer.from)
+    assert_equal('customer@example.com', article_customer.to)
+    assert_equal('Customer', article_customer.sender.name)
+    assert_equal('email', article_customer.type.name)
+    article_notification = article_p.ticket.articles[1]
+    assert_match(/New Ticket add. info/, article_notification.subject)
+    assert_no_match(/me@example.com/, article_notification.to)
+    assert_match(/#{agent1.email}/, article_notification.to)
+    assert_equal('System', article_notification.sender.name)
+    assert_equal('email', article_notification.type.name)
+    article_auto_reply = article_p.ticket.articles[2]
+    assert_match(/Thanks for your inquiry/, article_auto_reply.subject)
+    assert_match(/me@example.com/, article_auto_reply.to)
+    assert_equal('System', article_auto_reply.sender.name)
+    assert_equal('email', article_auto_reply.type.name)
 
   end
 

+ 677 - 0
test/unit/ticket_trigger_extended_recursive_disabled_test.rb

@@ -0,0 +1,677 @@
+require 'test_helper'
+
+class TicketTriggerExtendedRecursiveDisabledTest < ActiveSupport::TestCase
+
+  setup do
+    Setting.set('ticket_trigger_recursive', false)
+  end
+
+  test 'recursive trigger' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('closed', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+  end
+
+  test 'recursive trigger - loop test' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set prio to 1 low',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set prio to 3 high',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('2 normal', ticket_p.priority.name)
+    assert_equal('open', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+  end
+
+  test 'recursive trigger - 2 trigger will not trigger next trigger' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('new', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'recursive trigger - 2 trigger will trigger next trigger - case 1' do
+    trigger1 = Trigger.create!(
+      name: '1) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('open', ticket_p.state.name)
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'recursive trigger - 2 trigger will trigger next trigger - case 2' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('2 normal', ticket_p.priority.name)
+    assert_equal('open', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'trigger based move and verify correct agent notifications' do
+
+    group1 = Group.create!(
+      name: 'Group 1',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group2 = Group.create!(
+      name: 'Group 2',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group3 = Group.create!(
+      name: 'Group 3',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    roles = Role.where(name: 'Agent')
+    user1 = User.create!(
+      login: 'trigger1@example.org',
+      firstname: 'trigger1',
+      lastname: 'trigger1',
+      email: 'trigger1@example.org',
+      password: 'some_pass',
+      active: true,
+      groups: [group1],
+      roles: roles,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    user2 = User.create!(
+      login: 'trigger2@example.org',
+      firstname: 'trigger2',
+      lastname: 'trigger2',
+      email: 'trigger2@example.org',
+      password: 'some_pass',
+      active: true,
+      groups: [group2],
+      roles: roles,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    # trigger, move ticket created in group1 into group3 and then into group2
+    trigger1 = Trigger.create_or_update(
+      name: '1 dispatch',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.group_id' => {
+          'operator' => 'is',
+          'value' => group3.id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.group_id' => {
+          'value' => group2.id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger2 = Trigger.create_or_update(
+      name: '2 dispatch',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.group_id' => {
+          'value' => group3.id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket1 = Ticket.create!(
+      title: '123',
+      group: group1,
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert(ticket1)
+
+    assert_equal(ticket1.title, '123')
+    assert_equal(ticket1.group.name, group1.name)
+    assert_equal(ticket1.state.name, 'new')
+
+    article_inbound1 = Ticket::Article.create!(
+      ticket_id: ticket1.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'some subject',
+      message_id: 'some@id',
+      body: 'some message',
+      internal: false,
+      sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+      type: Ticket::Article::Type.find_by(name: 'email'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    # verfiy if agent1 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user1, 'email'), ticket1.id)
+
+    # verfiy if agent2 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user2, 'email'), ticket1.id)
+
+    Observer::Transaction.commit
+    Scheduler.worker(true)
+
+    ticket1.reload
+    assert_equal('123', ticket1.title)
+    assert_equal(group3.name, ticket1.group.name)
+    assert_equal('new', ticket1.state.name)
+    assert_equal('2 normal', ticket1.priority.name)
+    assert_equal(1, ticket1.articles.count)
+
+    # verfiy if agent1 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user1, 'email'), ticket1.id)
+
+    # verfiy if agent2 got notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user2, 'email'), ticket1.id)
+
+  end
+
+  test 'recursive trigger loop check' do
+    trigger0 = Trigger.create!(
+      name: '000',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger1 = Trigger.create!(
+      name: '001',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger2 = Trigger.create!(
+      name: '002',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group1 = Group.find_by(name: 'Users')
+    ticket1 = Ticket.create!(
+      title: '123',
+      group: group1,
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert(ticket1)
+
+    assert_equal(ticket1.title, '123')
+    assert_equal(ticket1.group.name, group1.name)
+    assert_equal(ticket1.state.name, 'new')
+
+    article_inbound1 = Ticket::Article.create!(
+      ticket_id: ticket1.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'some subject',
+      message_id: 'some@id',
+      body: 'some message',
+      internal: false,
+      sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+      type: Ticket::Article::Type.find_by(name: 'email'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Observer::Transaction.commit
+    Scheduler.worker(true)
+
+    ticket1.reload
+    assert_equal('123', ticket1.title)
+    assert_equal('new', ticket1.state.name)
+    assert_equal('3 high', ticket1.priority.name)
+    assert_equal(1, ticket1.articles.count)
+
+  end
+
+end

+ 718 - 0
test/unit/ticket_trigger_extended_test.rb

@@ -0,0 +1,718 @@
+require 'test_helper'
+
+class TicketTriggerExtendedTest < ActiveSupport::TestCase
+
+  setup do
+    Setting.set('ticket_trigger_recursive', true)
+  end
+
+  test 'recursive trigger' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('closed', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+  end
+
+  test 'recursive trigger - loop test' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set prio to 1 low',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set prio to 3 high',
+      condition: {
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('2 normal', ticket_p.priority.name)
+    assert_equal('open', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+  end
+
+  test 'recursive trigger - 2 trigger will not trigger next trigger' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('new', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'recursive trigger - 2 trigger will trigger next trigger - case 1' do
+    trigger1 = Trigger.create!(
+      name: '1) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('closed', ticket_p.state.name)
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'recursive trigger - 2 trigger will trigger next trigger - case 2' do
+    trigger1 = Trigger.create!(
+      name: '1) set prio to 3 high',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger2 = Trigger.create!(
+      name: '2) set state to closed',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    trigger3 = Trigger.create!(
+      name: '3) set state to open',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'open').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    email_raw_string = 'From: me@example.com
+To: customer@example.com
+Subject: some new subject
+
+Some Text'
+
+    ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+
+    assert_equal('some new subject', ticket_p.title)
+    assert_equal('Users', ticket_p.group.name)
+    assert_equal('3 high', ticket_p.priority.name)
+    assert_equal('closed', ticket_p.state.name)
+
+    assert_equal(1, ticket_p.articles.count, 'ticket1.articles verify')
+
+  end
+
+  test 'trigger based move and verify correct agent notifications' do
+
+    group1 = Group.create!(
+      name: 'Group 1',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group2 = Group.create!(
+      name: 'Group 2',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group3 = Group.create!(
+      name: 'Group 3',
+      active: true,
+      email_address: EmailAddress.first,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    roles = Role.where(name: 'Agent')
+    user1 = User.create!(
+      login: 'trigger1@example.org',
+      firstname: 'trigger1',
+      lastname: 'trigger1',
+      email: 'trigger1@example.org',
+      password: 'some_pass',
+      active: true,
+      groups: [group1],
+      roles: roles,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    user2 = User.create!(
+      login: 'trigger2@example.org',
+      firstname: 'trigger2',
+      lastname: 'trigger2',
+      email: 'trigger2@example.org',
+      password: 'some_pass',
+      active: true,
+      groups: [group2],
+      roles: roles,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    # trigger, move ticket created in group1 into group3 and then into group2
+    trigger1 = Trigger.create_or_update(
+      name: '1 dispatch',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.group_id' => {
+          'operator' => 'is',
+          'value' => group3.id.to_s,
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.group_id' => {
+          'value' => group2.id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger2 = Trigger.create_or_update(
+      name: '2 dispatch',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.state_id' => {
+          'operator' => 'is',
+          'value' => Ticket::State.lookup(name: 'new').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.group_id' => {
+          'value' => group3.id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket1 = Ticket.create!(
+      title: '123',
+      group: group1,
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert(ticket1)
+
+    assert_equal(ticket1.title, '123')
+    assert_equal(ticket1.group.name, group1.name)
+    assert_equal(ticket1.state.name, 'new')
+
+    article_inbound1 = Ticket::Article.create!(
+      ticket_id: ticket1.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'some subject',
+      message_id: 'some@id',
+      body: 'some message',
+      internal: false,
+      sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+      type: Ticket::Article::Type.find_by(name: 'email'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    # verfiy if agent1 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user1, 'email'), ticket1.id)
+
+    # verfiy if agent2 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user2, 'email'), ticket1.id)
+
+    Observer::Transaction.commit
+    Scheduler.worker(true)
+
+    ticket1.reload
+    assert_equal('123', ticket1.title)
+    assert_equal(group2.name, ticket1.group.name)
+    assert_equal('new', ticket1.state.name)
+    assert_equal('2 normal', ticket1.priority.name)
+    assert_equal(1, ticket1.articles.count)
+
+    # verfiy if agent1 got no notifcation
+    assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, user1, 'email'), ticket1.id)
+
+    # verfiy if agent2 got notifcation
+    assert_equal(1, NotificationFactory::Mailer.already_sent?(ticket1, user2, 'email'), ticket1.id)
+
+  end
+
+  test 'recursive trigger loop check' do
+    Setting.set('ticket_trigger_recursive_max_loop', 2)
+    trigger0 = Trigger.create!(
+      name: '000',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.state_id' => {
+          'value' => Ticket::State.lookup(name: 'closed').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger1 = Trigger.create!(
+      name: '001',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    trigger2 = Trigger.create!(
+      name: '002',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+        'ticket.priority_id' => {
+          'operator' => 'is',
+          'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
+        },
+      },
+      perform: {
+        'ticket.priority_id' => {
+          'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    group1 = Group.find_by(name: 'Users')
+    ticket1 = Ticket.create!(
+      title: '123',
+      group: group1,
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert(ticket1)
+
+    assert_equal(ticket1.title, '123')
+    assert_equal(ticket1.group.name, group1.name)
+    assert_equal(ticket1.state.name, 'new')
+
+    article_inbound1 = Ticket::Article.create!(
+      ticket_id: ticket1.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'some subject',
+      message_id: 'some@id',
+      body: 'some message',
+      internal: false,
+      sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+      type: Ticket::Article::Type.find_by(name: 'email'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Observer::Transaction.commit
+    Scheduler.worker(true)
+
+    ticket1.reload
+    assert_equal('123', ticket1.title)
+    assert_equal('new', ticket1.state.name)
+    assert_equal('1 low', ticket1.priority.name)
+    assert_equal(1, ticket1.articles.count)
+
+    Setting.set('ticket_trigger_recursive_max_loop', 3)
+
+    ticket1 = Ticket.create!(
+      title: '123',
+      group: group1,
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert(ticket1)
+
+    assert_equal(ticket1.title, '123')
+    assert_equal(ticket1.group.name, group1.name)
+    assert_equal(ticket1.state.name, 'new')
+
+    article_inbound1 = Ticket::Article.create!(
+      ticket_id: ticket1.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'some subject',
+      message_id: 'some@id',
+      body: 'some message',
+      internal: false,
+      sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+      type: Ticket::Article::Type.find_by(name: 'email'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Observer::Transaction.commit
+    Scheduler.worker(true)
+
+    ticket1.reload
+    assert_equal('123', ticket1.title)
+    assert_equal('closed', ticket1.state.name)
+    assert_equal('1 low', ticket1.priority.name)
+    assert_equal(1, ticket1.articles.count)
+
+  end
+
+end

Some files were not shown because too many files changed in this diff