Просмотр исходного кода

Improved email loop protection. Remember permanent delivery failed on user to prevent email loops.

Martin Edenhofer 7 лет назад
Родитель
Сommit
48e3da57ee

+ 73 - 0
app/models/channel/filter/bounce_delivery_permanent_failed.rb

@@ -0,0 +1,73 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+module Channel::Filter::BounceDeliveryPermanentFailed
+
+  def self.run(_channel, mail)
+
+    return if !mail[:mail_instance]
+    return if !mail[:mail_instance].bounced?
+    return if !mail[:attachments]
+
+    # remember, do not send notifications to certain recipients again if failed permanent
+    mail[:attachments].each { |attachment|
+      next if !attachment[:preferences]
+      next if attachment[:preferences]['Mime-Type'] != 'message/rfc822'
+      next if !attachment[:data]
+
+      result = Channel::EmailParser.new.parse(attachment[:data])
+      next if !result[:message_id]
+      message_id_md5 = Digest::MD5.hexdigest(result[:message_id])
+      article = Ticket::Article.where(message_id_md5: message_id_md5).order('created_at DESC, id DESC').limit(1).first
+      next if !article
+
+      # check user preferences
+      next if mail[:mail_instance].action != 'failed'
+      next if mail[:mail_instance].retryable? != false
+      next if mail[:mail_instance].error_status != '5.1.1'
+
+      # get recipient of origin article, if only one - mark this user to not sent notifications anymore
+      recipients = []
+      if article.sender.name == 'System' || article.sender.name == 'Agent'
+        %w(to cc).each { |line|
+          next if article[line].blank?
+          recipients = []
+          begin
+            list = Mail::AddressList.new(article[line])
+            list.addresses.each { |address|
+              next if address.address.blank?
+              recipients.push address.address.downcase
+            }
+          rescue
+            Rails.logger.info "Unable to parse email address in '#{article[line]}'"
+          end
+        }
+        if recipients.count > 1
+          recipients = []
+        end
+      end
+
+      # get recipient bounce mail, mark this user to not sent notifications anymore
+      final_recipient = mail[:mail_instance].final_recipient
+      if final_recipient.present?
+        final_recipient.sub!(/rfc822;\s{0,10}/, '')
+        if final_recipient.present?
+          recipients.push final_recipient.downcase
+        end
+      end
+
+      # set user preferences
+      recipients.each { |recipient|
+        users = User.where(email: recipient)
+        users.each { |user|
+          next if !user
+          user.preferences[:mail_delivery_failed] = true
+          user.preferences[:mail_delivery_failed_data] = Time.zone.now
+          user.save!
+        }
+      }
+    }
+
+    true
+
+  end
+end

+ 5 - 1
app/models/channel/filter/bounce_check.rb → app/models/channel/filter/bounce_follow_up_check.rb

@@ -1,6 +1,6 @@
 # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
 
-module Channel::Filter::BounceCheck
+module Channel::Filter::BounceFollowUpCheck
 
   def self.run(_channel, mail)
 
@@ -13,13 +13,17 @@ module Channel::Filter::BounceCheck
       next if !attachment[:preferences]
       next if attachment[:preferences]['Mime-Type'] != 'message/rfc822'
       next if !attachment[:data]
+
       result = Channel::EmailParser.new.parse(attachment[:data])
       next if !result[:message_id]
       message_id_md5 = Digest::MD5.hexdigest(result[:message_id])
       article = Ticket::Article.where(message_id_md5: message_id_md5).order('created_at DESC, id DESC').limit(1).first
       next if !article
+
       Rails.logger.debug "Follow up for '##{article.ticket.number}' in bounce email."
       mail[ 'x-zammad-ticket-id'.to_sym ] = article.ticket_id
+      mail[ 'x-zammad-is-auto-response'.to_sym ] = true
+
       return true
     }
 

+ 13 - 0
app/models/ticket.rb

@@ -813,6 +813,19 @@ perform changes on ticket
         recipients_checked = []
         recipients_raw.each { |recipient_email|
 
+          skip_user = false
+          users = User.where(email: recipient_email)
+          users.each { |user|
+            next if user.preferences[:mail_delivery_failed] != true
+            next if !user.preferences[:mail_delivery_failed_data]
+            till_blocked = ((user.preferences[:mail_delivery_failed_data] - Time.zone.now - 60.days) / 60 / 60 / 24).round
+            next if till_blocked.positive?
+            logger.info "Send no trigger based notification to #{recipient_email} because email is marked as mail_delivery_failed for #{till_blocked} days"
+            skip_user = true
+            break
+          }
+          next if skip_user
+
           # send notifications only to email adresses
           next if !recipient_email
           next if recipient_email !~ /@/

+ 35 - 0
db/migrate/20170529000002_setting_delivery_permanent_failed.rb

@@ -0,0 +1,35 @@
+class SettingDeliveryPermanentFailed < ActiveRecord::Migration
+  def up
+
+    # return if it's a new setup
+    return if !Setting.find_by(name: 'system_init_done')
+
+    setting = Setting.find_by(name: '0900_postmaster_filter_bounce_check')
+    if setting
+      setting.name = '0900_postmaster_filter_bounce_follow_up_check'
+      setting.state = 'Channel::Filter::BounceFollowUpCheck'
+      setting.save!
+    else
+      Setting.create_if_not_exists(
+        title: 'Defines postmaster filter.',
+        name: '0900_postmaster_filter_bounce_follow_up_check',
+        area: 'Postmaster::PreFilter',
+        description: 'Defines postmaster filter to identify postmaster bounced - to handle it as follow-up of the original ticket.',
+        options: {},
+        state: 'Channel::Filter::BounceFollowUpCheck',
+        frontend: false
+      )
+    end
+    Setting.create_if_not_exists(
+      title: 'Defines postmaster filter.',
+      name: '0950_postmaster_filter_bounce_delivery_permanent_failed',
+      area: 'Postmaster::PreFilter',
+      description: 'Defines postmaster filter to identify postmaster bounced - disable sending notification on permanent deleivery failed.',
+      options: {},
+      state: 'Channel::Filter::BounceDeliveryPermanentFailed',
+      frontend: false
+    )
+
+  end
+
+end

+ 11 - 2
db/seeds/settings.rb

@@ -2327,11 +2327,20 @@ Setting.create_if_not_exists(
 )
 Setting.create_if_not_exists(
   title: 'Defines postmaster filter.',
-  name: '0900_postmaster_filter_bounce_check',
+  name: '0900_postmaster_filter_bounce_follow_up_check',
   area: 'Postmaster::PreFilter',
   description: 'Defines postmaster filter to identify postmaster bounced - to handle it as follow-up of the original ticket.',
   options: {},
-  state: 'Channel::Filter::BounceCheck',
+  state: 'Channel::Filter::BounceFollowUpCheck',
+  frontend: false
+)
+Setting.create_if_not_exists(
+  title: 'Defines postmaster filter.',
+  name: '0950_postmaster_filter_bounce_delivery_permanent_failed',
+  area: 'Postmaster::PreFilter',
+  description: 'Defines postmaster filter to identify postmaster bounced - disable sending notification on permanent deleivery failed.',
+  options: {},
+  state: 'Channel::Filter::BounceDeliveryPermanentFailed',
   frontend: false
 )
 Setting.create_if_not_exists(

+ 230 - 0
test/fixtures/mail55.box

@@ -0,0 +1,230 @@
+Return-Path: <MAILER-DAEMON>
+Delivered-To: example@zammad.com
+Received: by mx1.zammad.loc (Postfix)
+  id 738F920A13B2; Fri, 26 May 2017 17:01:45 +0200 (CEST)
+Date: Fri, 26 May 2017 17:01:45 +0200 (CEST)
+From: MAILER-DAEMON@mx1.zammad.loc (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+To: example@zammad.com
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+  boundary="207FF20398ED.1495810905/mx1.zammad.loc"
+Message-Id: <20170526150145.738F920A13B2@mx1.zammad.loc>
+
+This is a MIME-encapsulated message.
+
+--207FF20398ED.1495810905/mx1.zammad.loc
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+This is the mail system at host mx1.zammad.loc.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+                   The mail system
+
+                   <ticket-bounce-trigger2@example.com>: host aspmx.l.example.com[108.177.96.26] said:
+    550-5.1.1 The email account that you tried to reach does not exist. Please
+    try 550-5.1.1 double-checking the recipient's email address for typos or
+    550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1
+    https://support.example.com/mail/?p=NoSuchUser l59si1635011edl.281 - gsmtp
+    (in reply to RCPT TO command)
+
+--207FF20398ED.1495810905/mx1.zammad.loc
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mx1.zammad.loc
+X-Postfix-Queue-ID: 207FF20398ED
+X-Postfix-Sender: rfc822; example@zammad.com
+Arrival-Date: Fri, 26 May 2017 17:01:45 +0200 (CEST)
+
+Final-Recipient: rfc822; ticket-bounce-trigger2@example.com
+Original-Recipient: rfc822;ticket-bounce-trigger2@example.com
+Action: failed
+Status: 5.1.1
+Remote-MTA: dns; aspmx.l.example.com
+Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does
+    not exist. Please try 550-5.1.1 double-checking the recipient's email
+    address for typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1
+    https://support.example.com/mail/?p=NoSuchUser l59si1635011edl.281 - gsmtp
+
+--207FF20398ED.1495810905/mx1.zammad.loc
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <example@zammad.com>
+Received: from apn0000.dc.zammad.com (apn0000.dc.zammad.com [88.0.0.0])
+  by mx1.zammad.loc (Postfix) with ESMTP id 207FF20398ED
+  for <ticket-bounce-trigger2@example.com>; Fri, 26 May 2017 17:01:45 +0200 (CEST)
+Received: by apn0000.dc.zammad.com (Postfix, from userid 1050)
+  id 08443420973; Fri, 26 May 2017 17:01:45 +0200 (CEST)
+Date: Fri, 26 May 2017 17:01:45 +0200
+From: Twelve SaaS GmbH Helpdesk <example@zammad.com>
+To: ticket-bounce-trigger2@example.com
+Message-ID: <20170526150141.232.13312@example.zammad.loc>
+In-Reply-To: 
+References: <20170526150142.232.819805@example.zammad.loc>
+ <20170526150119.6C5E520A13B2@mx1.zammad.loc>
+ <20170526150141.232.799457@example.zammad.loc>
+ <20170526150117.0560820A13B3@mx1.zammad.loc>
+ <20170526150115.232.175460@example.zammad.loc>
+ <20170526150108.232.482766@example.zammad.loc>
+ <20170526150041.F3D2C20A13B3@mx1.zammad.loc>
+ <20170526150036.232.513248@example.zammad.loc>
+ <20170526150008.6AE8A20A13B8@mx1.zammad.loc>
+ <20170526150004.232.103372@example.zammad.loc>
+ <20170526145940.D799220A13B3@mx1.zammad.loc>
+ <20170526145932.232.91897@example.zammad.loc>
+ <20170526145906.8FCA520A13B2@mx1.zammad.loc>
+ <20170526145901.232.269971@example.zammad.loc>
+ <lyHYjBDWwaU5KGbDrCyOfA@notifications.example.com>
+ Subject: =?UTF-8?Q?[Ticket#1705265400361]_RE:_Thanks_for_your_follow_up?=
+ =?UTF-8?Q?_=28G_Suite:_Benachrichtigung_=C3=BCber_Verl=C3=A4ngerung_in_30?=
+ =?UTF-8?Q?_Tagen=29?=
+ Mime-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="--==_mimepart_5928435950b1_22d42086504355bc";
+ charset=UTF-8
+ Content-Transfer-Encoding: 7bit
+Organization: Twelve SaaS GmbH
+X-Loop: yes
+Precedence: bulk
+Auto-Submitted: auto-generated
+X-Auto-Response-Suppress: All
+X-Powered-By: Zammad - Helpdesk/Support (https://zammad.org/)
+X-Mailer: Zammad Mail Service
+
+
+----==_mimepart_5928435950b1_22d42086504355bc
+Content-Type: multipart/alternative;
+ boundary="--==_mimepart_592843594d35_22d4208650435394";
+ charset=UTF-8
+ Content-Transfer-Encoding: 7bit
+
+
+----==_mimepart_592843594d35_22d4208650435394
+Content-Type: text/plain;
+ charset=UTF-8
+ Content-Transfer-Encoding: 7bit
+
+Your follow up for (Ticket#1705265400361) has been received and will be reviewed by our support staff.
+
+To provide additional information, please reply to this email or click on the following link:[1] https://example.zammad.loc/#ticket/zoom/232
+
+Your Twelve SaaS Helpdesk Team
+
+[2] Zammad, your customer support system
+
+[1] https://example.zammad.loc/#ticket/zoom/232
+[2] https://zammad.com
+----==_mimepart_592843594d35_22d4208650435394
+Content-Type: multipart/related;
+ boundary="--==_mimepart_592843594e47_22d4208650435484";
+ charset=UTF-8
+ Content-Transfer-Encoding: 7bit
+
+
+----==_mimepart_592843594e47_22d4208650435484
+Content-Type: text/html;
+ charset=UTF-8
+ Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <style type="text/css">
+      body {
+        font-family:'Helvetica Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;;
+      }
+      img {
+        outline: none;
+        text-decoration: none;
+        -ms-interpolation-mode: bicubic;
+      }
+      a img {
+        border: none;
+      }
+      table td {
+        border-collapse: collapse;
+      }
+      table {
+        border-collapse: collapse;
+        mso-table-lspace: 0pt;
+        mso-table-rspace: 0pt;
+        border: none;
+        table-layout: auto;
+        display: block;
+        width: 100%;
+        overflow: auto;
+        word-break: keep-all;
+      }
+      table,
+      pre,
+      blockquote {
+        margin: 0 0 16px;
+      }
+      td, th {
+        padding: 7px 12px;
+        border: 1px solid hsl(0,0%,87%);
+      }
+      th {
+        font-weight: bold;
+        text-align: center;
+      }
+      tbody tr:nth-child(even) {
+        background: hsl(0,0%,97%);
+      }
+      col {
+        width: auto;
+      }
+      p {
+        margin: 0;
+      }
+      code {
+        border: none;
+        background: hsl(0,0%,97%);
+        white-space: pre-wrap;
+      }
+      blockquote {
+        padding: 8px 12px;
+        border-left: 5px solid #eee;
+      }
+      pre {
+        padding: 12px 15px;
+        font-size: 13px;
+        line-height: 1.45;
+        background: hsl(0,0%,97%);
+        white-space: pre-wrap;
+        border-radius: 3px;
+        border: none;
+        overflow: auto;
+      }
+    </style>
+  </head>
+  <body style="font-family:'Helvetica Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;"><div>Your follow up for <b>(Ticket#1705265400361)</b> has been received and will be reviewed by our support staff.</div>
+  <br>
+<div>To provide additional information, please reply to this email or click on the following link:
+<a href="https://example.zammad.loc/#ticket/zoom/232" rel="nofollow noreferrer noopener" target="_blank">https://example.zammad.loc/#ticket/zoom/232</a>
+</div>
+<br>
+<div>Your Twelve SaaS Helpdesk Team</div>
+<br>
+<div><i><a href="https://zammad.com" rel="nofollow noreferrer noopener" target="_blank">Zammad</a>, your customer support system</i></div></body>
+</html>
+
+----==_mimepart_592843594e47_22d4208650435484--
+
+----==_mimepart_592843594d35_22d4208650435394--
+
+----==_mimepart_5928435950b1_22d42086504355bc--
+
+--207FF20398ED.1495810905/mx1.zammad.loc--

+ 220 - 0
test/unit/email_process_bounce_delivery_permanent_failed_test.rb

@@ -0,0 +1,220 @@
+# encoding: utf-8
+require 'test_helper'
+
+class EmailProcessBounceDeliveryPermanentFailedTest < ActiveSupport::TestCase
+
+  test 'process with bounce trigger email loop check - article based blocker' do
+    roles = Role.where(name: %w(Customer))
+    customer1 = User.create_or_update(
+      login: 'ticket-bounce-trigger1@example.com',
+      firstname: 'Notification',
+      lastname: 'Customer1',
+      email: 'ticket-bounce-trigger1@example.com',
+      active: true,
+      roles: roles,
+      preferences: {},
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create_or_update(
+      name: 'auto reply new ticket',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    Trigger.create_or_update(
+      name: 'auto reply followup',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'update',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your follow up (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket = Ticket.create(
+      title: 'bounce check',
+      group: Group.lookup(name: 'Users'),
+      customer: customer1,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check',
+      message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>',
+      body: 'some message bounce check',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal('new', ticket.state.name)
+    assert_equal(2, ticket.articles.count)
+
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: customer1.email,
+      subject: 'bounce check 2',
+      message_id: '<20150830145601.30.608881@edenhofer.zammad.com>',
+      body: 'some message bounce check 2',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal(4, ticket.articles.count)
+
+    travel 1.second
+    email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box')
+    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(ticket.id, ticket_p.id)
+    assert_equal('open', ticket_p.state.name)
+    assert_equal(5, ticket_p.articles.count)
+    travel_back
+    ticket.destroy
+  end
+
+  test 'process with bounce trigger email loop check - bounce based blocker' do
+    roles = Role.where(name: %w(Customer))
+    customer2 = User.create_or_update(
+      login: 'ticket-bounce-trigger2@example.com',
+      firstname: 'Notification',
+      lastname: 'Customer2',
+      email: 'ticket-bounce-trigger2@example.com',
+      active: true,
+      roles: roles,
+      preferences: {},
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create_or_update(
+      name: 'auto reply new ticket',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    Trigger.create_or_update(
+      name: 'auto reply followup',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'update',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your follow up (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket = Ticket.create(
+      title: 'bounce check',
+      group: Group.lookup(name: 'Users'),
+      customer: customer2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check',
+      message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>',
+      body: 'some message bounce check',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal('new', ticket.state.name)
+    assert_equal(2, ticket.articles.count)
+
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check 2',
+      message_id: '<20170526150141.232.13312@example.zammad.loc>',
+      body: 'some message bounce check 2',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal(4, ticket.articles.count)
+
+    travel 1.second
+    email_raw_string = IO.binread('test/fixtures/mail55.box')
+    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(ticket.id, ticket_p.id)
+    assert_equal('open', ticket_p.state.name)
+    assert_equal(5, ticket_p.articles.count)
+    travel_back
+    ticket.destroy
+  end
+
+end

+ 254 - 0
test/unit/email_process_bounce_follow_test.rb

@@ -0,0 +1,254 @@
+# encoding: utf-8
+require 'test_helper'
+
+class EmailProcessBounceFollowUpTest < ActiveSupport::TestCase
+
+  test 'process with bounce follow up check' do
+
+    ticket = Ticket.create(
+      title: 'bounce check',
+      group: Group.lookup(name: 'Users'),
+      customer_id: 2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check',
+      message_id: '<20150830145601.30.608881@edenhofer.zammad.com>',
+      body: 'some message bounce check',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Customer').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    travel 1.second
+    email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box')
+    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(ticket.id, ticket_p.id)
+    assert_equal('new', ticket_p.state.name)
+    travel_back
+    ticket.destroy
+
+  end
+
+  test 'process with bounce trigger email loop check - article based blocker' do
+    roles = Role.where(name: %w(Customer))
+    customer1 = User.create_or_update(
+      login: 'ticket-bounce-trigger1@example.com',
+      firstname: 'Notification',
+      lastname: 'Customer1',
+      email: 'ticket-bounce-trigger1@example.com',
+      active: true,
+      roles: roles,
+      preferences: {},
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create_or_update(
+      name: 'auto reply new ticket',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    Trigger.create_or_update(
+      name: 'auto reply followup',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'update',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your follow up (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket = Ticket.create(
+      title: 'bounce check',
+      group: Group.lookup(name: 'Users'),
+      customer: customer1,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check',
+      message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>',
+      body: 'some message bounce check',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal('new', ticket.state.name)
+    assert_equal(2, ticket.articles.count)
+
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: customer1.email,
+      subject: 'bounce check 2',
+      message_id: '<20150830145601.30.608881@edenhofer.zammad.com>',
+      body: 'some message bounce check 2',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal(4, ticket.articles.count)
+
+    travel 1.second
+    email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box')
+    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(ticket.id, ticket_p.id)
+    assert_equal('open', ticket_p.state.name)
+    assert_equal(5, ticket_p.articles.count)
+    travel_back
+    ticket.destroy
+  end
+
+  test 'process with bounce trigger email loop check - bounce based blocker' do
+    roles = Role.where(name: %w(Customer))
+    customer2 = User.create_or_update(
+      login: 'ticket-bounce-trigger2@example.com',
+      firstname: 'Notification',
+      lastname: 'Customer2',
+      email: 'ticket-bounce-trigger2@example.com',
+      active: true,
+      roles: roles,
+      preferences: {},
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+
+    Trigger.create_or_update(
+      name: 'auto reply new ticket',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'create',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your inquiry (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+    Trigger.create_or_update(
+      name: 'auto reply followup',
+      condition: {
+        'ticket.action' => {
+          'operator' => 'is',
+          'value' => 'update',
+        },
+      },
+      perform: {
+        'notification.email' => {
+          'body' => 'some text<br>#{ticket.customer.lastname}<br>#{ticket.title}<br>#{article.body}',
+          'recipient' => 'ticket_customer',
+          'subject' => 'Thanks for your follow up (#{ticket.title})!',
+        },
+      },
+      disable_notification: true,
+      active: true,
+      created_by_id: 1,
+      updated_by_id: 1,
+    )
+
+    ticket = Ticket.create(
+      title: 'bounce check',
+      group: Group.lookup(name: 'Users'),
+      customer: customer2,
+      state: Ticket::State.lookup(name: 'new'),
+      priority: Ticket::Priority.lookup(name: '2 normal'),
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check',
+      message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>',
+      body: 'some message bounce check',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal('new', ticket.state.name)
+    assert_equal(2, ticket.articles.count)
+
+    article = Ticket::Article.create(
+      ticket_id: ticket.id,
+      from: 'some_sender@example.com',
+      to: 'some_recipient@example.com',
+      subject: 'bounce check 2',
+      message_id: '<20170526150141.232.13312@example.zammad.loc>',
+      body: 'some message bounce check 2',
+      internal: false,
+      sender: Ticket::Article::Sender.where(name: 'Agent').first,
+      type: Ticket::Article::Type.where(name: 'email').first,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    Observer::Transaction.commit
+    assert_equal(4, ticket.articles.count)
+
+    travel 1.second
+    email_raw_string = IO.binread('test/fixtures/mail55.box')
+    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal(ticket.id, ticket_p.id)
+    assert_equal('open', ticket_p.state.name)
+    assert_equal(5, ticket_p.articles.count)
+    travel_back
+    ticket.destroy
+  end
+
+end

+ 0 - 39
test/unit/email_process_bounce_test.rb

@@ -1,39 +0,0 @@
-# encoding: utf-8
-require 'test_helper'
-
-class EmailProcessBounceTest < ActiveSupport::TestCase
-
-  test 'process with bounce check' do
-
-    ticket = Ticket.create(
-      title: 'bounce check',
-      group: Group.lookup( name: 'Users'),
-      customer_id: 2,
-      state: Ticket::State.lookup( name: 'new' ),
-      priority: Ticket::Priority.lookup( name: '2 normal' ),
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-    article = Ticket::Article.create(
-      ticket_id: ticket.id,
-      from: 'some_sender@example.com',
-      to: 'some_recipient@example.com',
-      subject: 'bounce check',
-      message_id: '<20150830145601.30.608881@edenhofer.zammad.com>',
-      body: 'some message bounce check',
-      internal: false,
-      sender: Ticket::Article::Sender.where(name: 'Customer').first,
-      type: Ticket::Article::Type.where(name: 'email').first,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-    travel 1.second
-    email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box')
-    ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string)
-    assert_equal(ticket.id, ticket_p.id)
-    assert_equal('new', ticket_p.state.name)
-    travel_back
-    ticket.destroy
-  end
-
-end