diff --git a/.rubocop_todo.rspec.yml b/.rubocop_todo.rspec.yml index a7bf4d619..eab54855d 100644 --- a/.rubocop_todo.rspec.yml +++ b/.rubocop_todo.rspec.yml @@ -272,7 +272,7 @@ RSpec/NamedSubject: # Offense count: 545 RSpec/NestedGroups: - Max: 7 + Max: 8 # Offense count: 28 # Cop supports --auto-correct. diff --git a/spec/models/channel/email_parser_spec.rb b/spec/models/channel/email_parser_spec.rb index c9a67ecbf..17019be4a 100644 --- a/spec/models/channel/email_parser_spec.rb +++ b/spec/models/channel/email_parser_spec.rb @@ -35,6 +35,8 @@ RSpec.describe Channel::EmailParser, type: :model do describe '#process' do let(:raw_mail) { File.read(mail_file) } + before { Trigger.destroy_all } # triggers may cause additional articles to be created + describe 'auto-creating new users' do context 'with one unrecognized email address' do it 'creates one new user' do @@ -98,7 +100,7 @@ RSpec.describe Channel::EmailParser, type: :model do it 'creates a ticket and article' do expect { Channel::EmailParser.new.process({}, raw_mail) } .to change { Ticket.count }.by(1) - .and change { Ticket::Article.count }.by_at_least(1) # triggers may cause additional articles to be created + .and change { Ticket::Article.count }.by_at_least(1) end it 'sets #title to email subject' do @@ -156,99 +158,485 @@ RSpec.describe Channel::EmailParser, type: :model do end describe 'associating emails to existing tickets' do - let(:mail_file) { Rails.root.join('test', 'data', 'mail', 'mail001.box') } + let!(:ticket) { create(:ticket) } let(:ticket_ref) { Setting.get('ticket_hook') + Setting.get('ticket_hook_divider') + ticket.number } - let(:ticket) { create(:ticket) } - context 'when email subject contains ticket reference' do - let(:raw_mail) { File.read(mail_file).sub(/(?<=^Subject: ).*$/, ticket_ref) } + describe 'based on where a ticket reference appears in the message' do + shared_context 'ticket reference in subject' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: #{ticket_ref} - it 'adds message to ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { ticket.articles.length } + Lorem ipsum dolor + RAW end - context 'and ticket is closed' do - before { ticket.update(state: Ticket::State.find_by(name: 'closed')) } + shared_context 'ticket reference in body' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + Lorem ipsum dolor #{ticket_ref} + RAW + end + + shared_context 'ticket reference in body (text/html)' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + Content-Transfer-Encoding: 7bit + Content-Type: text/html; + + Lorem ipsum dolor #{ticket_ref} + RAW + end + + shared_context 'ticket reference in attachment' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + Content-Type: multipart/mixed; boundary="Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2" + Subject: no reference + Date: Sun, 30 Aug 2015 23:20:54 +0200 + To: Martin Edenhofer + Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\)) + X-Mailer: Apple Mail (2.2104) + + + --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 + Content-Transfer-Encoding: 7bit + Content-Type: text/plain; + charset=us-ascii + + no reference + --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 + Content-Disposition: attachment; + filename=test1.txt + Content-Type: text/plain; + name="test.txt" + Content-Transfer-Encoding: 7bit + + Some Text #{ticket_ref} + + --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2-- + RAW + end + + shared_context 'ticket reference in In-Reply-To header' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + In-Reply-To: #{article.message_id} + + Lorem ipsum dolor + RAW + + let!(:article) { create(:ticket_article, ticket: ticket, message_id: '<20150830145601.30.608882@edenhofer.zammad.com>') } + end + + shared_context 'ticket reference in References header' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + + Lorem ipsum dolor + RAW + + let!(:article) { create(:ticket_article, ticket: ticket, message_id: '<20150830145601.30.608882@edenhofer.zammad.com>') } + end + + shared_examples 'adds message to ticket' do it 'adds message to ticket' do expect { described_class.new.process({}, raw_mail) } - .to change { ticket.articles.length } + .to change { ticket.articles.length }.by(1) end end - context 'but ticket group’s #follow_up_possible attribute is "new_ticket"' do - before { ticket.group.update(follow_up_possible: 'new_ticket') } + shared_examples 'creates a new ticket' do + it 'creates a new ticket' do + expect { described_class.new.process({}, raw_mail) } + .to change { Ticket.count }.by(1) + .and not_change { ticket.articles.length } + end + end - context 'and ticket is open' do - it 'still adds message to ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { ticket.articles.length } + context 'when not explicitly configured to search anywhere' do + before { Setting.set('postmaster_follow_up_search_in', nil) } + + context 'when subject contains ticket reference' do + include_context 'ticket reference in subject' + include_examples 'adds message to ticket' + + context 'alongside other, invalid ticket references' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: [#{Setting.get('ticket_hook') + Setting.get('ticket_hook_divider') + Ticket::Number.generate}] #{ticket_ref} + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + + context 'and ticket is closed' do + before { ticket.update(state: Ticket::State.find_by(name: 'closed')) } + + include_examples 'adds message to ticket' + end + + context 'but ticket group’s #follow_up_possible attribute is "new_ticket"' do + before { ticket.group.update(follow_up_possible: 'new_ticket') } + + context 'and ticket is open' do + include_examples 'adds message to ticket' + end + + context 'and ticket is closed' do + before { ticket.update(state: Ticket::State.find_by(name: 'closed')) } + + include_examples 'creates a new ticket' + end + + context 'and ticket is merged' do + before { ticket.update(state: Ticket::State.find_by(name: 'merged')) } + + include_examples 'creates a new ticket' + end + + context 'and ticket is removed' do + before { ticket.update(state: Ticket::State.find_by(name: 'removed')) } + + include_examples 'creates a new ticket' + end + end + + context 'and "ticket_hook" setting is non-default value' do + before { Setting.set('ticket_hook', 'VD-Ticket#') } + + include_examples 'adds message to ticket' end end - context 'and ticket is closed' do - before { ticket.update(state: Ticket::State.find_by(name: 'closed')) } + context 'when body contains ticket reference' do + include_context 'ticket reference in body' + include_examples 'creates a new ticket' + end - it 'creates a new ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { Ticket.count }.by(1) - .and not_change { ticket.articles.length } + context 'when attachment contains ticket reference' do + include_context 'ticket reference in attachment' + include_examples 'creates a new ticket' + end + + context 'when In-Reply-To header contains article message-id' do + include_context 'ticket reference in In-Reply-To header' + include_examples 'creates a new ticket' + + context 'and subject matches article subject' do + let(:raw_mail) { <<~RAW.chomp } + From: customer@example.com + To: me@example.com + Subject: AW: RE: #{article.subject} + In-Reply-To: #{article.message_id} + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + + context 'and "ticket_hook_position" setting is "none"' do + before { Setting.set('ticket_hook_position', 'none') } + + let(:raw_mail) { <<~RAW.chomp } + From: customer@example.com + To: me@example.com + Subject: RE: Foo bar + In-Reply-To: #{article.message_id} + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' end end - context 'and ticket is merged' do - before { ticket.update(state: Ticket::State.find_by(name: 'merged')) } + context 'when References header contains article message-id' do + include_context 'ticket reference in References header' + include_examples 'creates a new ticket' - it 'creates a new ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { Ticket.count }.by(1) - .and not_change { ticket.articles.length } + context 'and Auto-Submitted header reads "auto-replied"' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + Auto-Submitted: auto-replied + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + + context 'and subject matches article subject' do + let(:raw_mail) { <<~RAW.chomp } + From: customer@example.com + To: me@example.com + Subject: AW: RE: #{article.subject} + References: #{article.message_id} + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + + context 'and "ticket_hook_position" setting is "none"' do + before { Setting.set('ticket_hook_position', 'none') } + + let(:raw_mail) { <<~RAW.chomp } + From: customer@example.com + To: me@example.com + Subject: RE: Foo bar + References: #{article.message_id} + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + end + end + + context 'when configured to search body' do + before { Setting.set('postmaster_follow_up_search_in', 'body') } + + context 'when subject contains ticket reference' do + include_context 'ticket reference in subject' + include_examples 'adds message to ticket' + end + + context 'when body contains ticket reference' do + context 'in visible text' do + include_context 'ticket reference in body' + include_examples 'adds message to ticket' + end + + context 'as part of a larger word' do + let(:ticket_ref) { "Foo#{Setting.get('ticket_hook')}#{Setting.get('ticket_hook_divider')}#{ticket.number}bar" } + + include_context 'ticket reference in body' + include_examples 'creates a new ticket' + end + + context 'between html tags' do + include_context 'ticket reference in body (text/html)' + include_examples 'adds message to ticket' + end + + context 'in html attributes' do + let(:ticket_ref) { %(
) } + + include_context 'ticket reference in body (text/html)' + include_examples 'creates a new ticket' end end - context 'and ticket is removed' do - before { ticket.update(state: Ticket::State.find_by(name: 'removed')) } + context 'when attachment contains ticket reference' do + include_context 'ticket reference in attachment' + include_examples 'creates a new ticket' + end - it 'creates a new ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { Ticket.count }.by(1) - .and not_change { ticket.articles.length } + context 'when In-Reply-To header contains article message-id' do + include_context 'ticket reference in In-Reply-To header' + include_examples 'creates a new ticket' + + context 'and Auto-Submitted header reads "auto-replied"' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + Auto-Submitted: auto-replied + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + end + + context 'when References header contains article message-id' do + include_context 'ticket reference in References header' + include_examples 'creates a new ticket' + end + end + + context 'when configured to search attachments' do + before { Setting.set('postmaster_follow_up_search_in', 'attachment') } + + context 'when subject contains ticket reference' do + include_context 'ticket reference in subject' + include_examples 'adds message to ticket' + end + + context 'when body contains ticket reference' do + include_context 'ticket reference in body' + include_examples 'creates a new ticket' + end + + context 'when attachment contains ticket reference' do + include_context 'ticket reference in attachment' + include_examples 'adds message to ticket' + end + + context 'when In-Reply-To header contains article message-id' do + include_context 'ticket reference in In-Reply-To header' + include_examples 'creates a new ticket' + end + + context 'when References header contains article message-id' do + include_context 'ticket reference in References header' + include_examples 'creates a new ticket' + + context 'and Auto-Submitted header reads "auto-replied"' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + Auto-Submitted: auto-replied + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + end + end + + context 'when configured to search headers' do + before { Setting.set('postmaster_follow_up_search_in', 'references') } + + context 'when subject contains ticket reference' do + include_context 'ticket reference in subject' + include_examples 'adds message to ticket' + end + + context 'when body contains ticket reference' do + include_context 'ticket reference in body' + include_examples 'creates a new ticket' + end + + context 'when attachment contains ticket reference' do + include_context 'ticket reference in attachment' + include_examples 'creates a new ticket' + end + + context 'when In-Reply-To header contains article message-id' do + include_context 'ticket reference in In-Reply-To header' + include_examples 'adds message to ticket' + end + + context 'when References header contains article message-id' do + include_context 'ticket reference in References header' + include_examples 'adds message to ticket' + + context 'that matches two separate tickets' do + let!(:newer_ticket) { create(:ticket) } + let!(:newer_article) { create(:ticket_article, ticket: newer_ticket, message_id: article.message_id) } + + it 'returns more recently created ticket' do + expect(described_class.new.process({}, raw_mail).first).to eq(newer_ticket) + end + + it 'adds message to more recently created ticket' do + expect { described_class.new.process({}, raw_mail) } + .to change { newer_ticket.articles.count }.by(1) + .and not_change { ticket.articles.count } + end + end + + context 'and Auto-Submitted header reads "auto-replied"' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + Auto-Submitted: auto-replied + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' + end + end + end + + context 'when configured to search everything' do + before { Setting.set('postmaster_follow_up_search_in', %w[body attachment references]) } + + context 'when subject contains ticket reference' do + include_context 'ticket reference in subject' + include_examples 'adds message to ticket' + end + + context 'when body contains ticket reference' do + include_context 'ticket reference in body' + include_examples 'adds message to ticket' + end + + context 'when attachment contains ticket reference' do + include_context 'ticket reference in attachment' + include_examples 'adds message to ticket' + end + + context 'when In-Reply-To header contains article message-id' do + include_context 'ticket reference in In-Reply-To header' + include_examples 'adds message to ticket' + end + + context 'when References header contains article message-id' do + include_context 'ticket reference in References header' + include_examples 'adds message to ticket' + + context 'and Auto-Submitted header reads "auto-replied"' do + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: no reference + References: #{article.message_id} + Auto-Submitted: auto-replied + + Lorem ipsum dolor + RAW + + include_examples 'adds message to ticket' end end end end - context 'when configured to search body' do - before { Setting.set('postmaster_follow_up_search_in', 'body') } + context 'for a closed ticket' do + let(:ticket) { create(:ticket, state_name: 'closed') } - context 'when body contains ticket reference' do - context 'in visible text' do - let(:raw_mail) { File.read(mail_file).sub(/Hallo =\nMartin,(?=)/, ticket_ref) } + let(:raw_mail) { <<~RAW.chomp } + From: me@example.com + To: customer@example.com + Subject: #{ticket_ref} - it 'adds message to ticket' do - expect { described_class.new.process({}, raw_mail) } - .to change { ticket.articles.length } - end - end + Lorem ipsum dolor + RAW - context 'as part of a larger word' do - let(:raw_mail) { File.read(mail_file).sub(/(?<=Hallo) =\n(?=Martin,)/, ticket_ref) } - - it 'creates a separate ticket' do - expect { described_class.new.process({}, raw_mail) } - .not_to change { ticket.articles.length } - end - end - - context 'in html attributes' do - let(:raw_mail) { File.read(mail_file).sub(%r{}m, %(
)) } - - it 'creates a separate ticket' do - expect { described_class.new.process({}, raw_mail) } - .not_to change { ticket.articles.length } - end - end + it 'reopens it' do + expect { described_class.new.process({}, raw_mail) } + .to change { ticket.reload.state.name }.to('open') end end end diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index 01c5aec57..a009a53ae 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -255,11 +255,94 @@ RSpec.describe Ticket, type: :model do end end end + + describe '#subject_build' do + context 'with default "ticket_hook_position" setting ("right")' do + it 'returns the given string followed by a ticket reference (of the form "[Ticket#123]")' do + expect(ticket.subject_build('foo')) + .to eq("foo [Ticket##{ticket.number}]") + end + + context 'and a non-default value for the "ticket_hook" setting' do + before { Setting.set('ticket_hook', 'bar baz') } + + it 'replaces "Ticket#" with the new ticket hook' do + expect(ticket.subject_build('foo')) + .to eq("foo [bar baz#{ticket.number}]") + end + end + + context 'and a non-default value for the "ticket_hook_divider" setting' do + before { Setting.set('ticket_hook_divider', ': ') } + + it 'inserts the new ticket hook divider between "Ticket#" and the ticket number' do + expect(ticket.subject_build('foo')) + .to eq("foo [Ticket#: #{ticket.number}]") + end + end + + context 'when the given string already contains a ticket reference, but in the wrong place' do + it 'moves the ticket reference to the end' do + expect(ticket.subject_build("[Ticket##{ticket.number}] foo")) + .to eq("foo [Ticket##{ticket.number}]") + end + end + + context 'when the given string already contains an alternately formatted ticket reference' do + it 'reformats the ticket reference' do + expect(ticket.subject_build("foo [Ticket#: #{ticket.number}]")) + .to eq("foo [Ticket##{ticket.number}]") + end + end + end + + context 'with alternate "ticket_hook_position" setting ("left")' do + before { Setting.set('ticket_hook_position', 'left') } + + it 'returns a ticket reference (of the form "[Ticket#123]") followed by the given string' do + expect(ticket.subject_build('foo')) + .to eq("[Ticket##{ticket.number}] foo") + end + + context 'and a non-default value for the "ticket_hook" setting' do + before { Setting.set('ticket_hook', 'bar baz') } + + it 'replaces "Ticket#" with the new ticket hook' do + expect(ticket.subject_build('foo')) + .to eq("[bar baz#{ticket.number}] foo") + end + end + + context 'and a non-default value for the "ticket_hook_divider" setting' do + before { Setting.set('ticket_hook_divider', ': ') } + + it 'inserts the new ticket hook divider between "Ticket#" and the ticket number' do + expect(ticket.subject_build('foo')) + .to eq("[Ticket#: #{ticket.number}] foo") + end + end + + context 'when the given string already contains a ticket reference, but in the wrong place' do + it 'moves the ticket reference to the start' do + expect(ticket.subject_build("foo [Ticket##{ticket.number}]")) + .to eq("[Ticket##{ticket.number}] foo") + end + end + + context 'when the given string already contains an alternately formatted ticket reference' do + it 'reformats the ticket reference' do + expect(ticket.subject_build("[Ticket#: #{ticket.number}] foo")) + .to eq("[Ticket##{ticket.number}] foo") + end + end + end + end end describe 'Attributes:' do describe '#owner' do let(:original_owner) { create(:agent_user, groups: [ticket.group]) } + before { ticket.update(owner: original_owner) } context 'when assigned directly' do diff --git a/test/unit/email_process_follow_up_test.rb b/test/unit/email_process_follow_up_test.rb deleted file mode 100644 index 03ab989dd..000000000 --- a/test/unit/email_process_follow_up_test.rb +++ /dev/null @@ -1,546 +0,0 @@ -require 'test_helper' - -class EmailProcessFollowUpTest < ActiveSupport::TestCase - - test 'process with follow up check' do - - ticket = Ticket.create( - title: 'follow up 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: 'follow up check', - message_id: '<20150830145601.30.608882@edenhofer.zammad.com>', - body: 'some message article', - internal: false, - sender: Ticket::Article::Sender.lookup(name: 'Agent'), - type: Ticket::Article::Type.lookup(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - ) - - email_raw_string_subject = "From: me@example.com -To: customer@example.com -Subject: #{ticket.subject_build('some new subject')} - -Some Text" - - email_raw_string_other_subject = "From: me@example.com -To: customer@example.com -Subject: other subject #{Setting.get('ticket_hook')}#{ticket.number} - -Some Text" - - email_raw_string_body = "From: me@example.com -To: customer@example.com -Subject: no reference - -Some Text #{ticket.subject_build('some new subject')} " - - email_raw_string_attachment = "From: me@example.com -Content-Type: multipart/mixed; boundary=\"Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2\" -Subject: no reference -Date: Sun, 30 Aug 2015 23:20:54 +0200 -To: Martin Edenhofer -Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\)) -X-Mailer: Apple Mail (2.2104) - - ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset=us-ascii - -no reference ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 -Content-Disposition: attachment; - filename=test1.txt -Content-Type: text/plain; - name=\"test.txt\" -Content-Transfer-Encoding: 7bit - -Some Text #{ticket.subject_build('some new subject')} - ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--" - - email_raw_string_references1 = "From: me@example.com -To: customer@example.com -Subject: no reference -In-Reply-To: <20150830145601.30.608882@edenhofer.zammad.com> -References: - -no reference " - - email_raw_string_references2 = "From: me@example.com -To: customer@example.com -Subject: no reference -References: <20150830145601.30.608882@edenhofer.zammad.com> - -no reference " - - setting_orig = Setting.get('postmaster_follow_up_search_in') - Setting.set('postmaster_follow_up_search_in', %w[body attachment references]) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_other_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2) - assert_equal(ticket.id, ticket_p.id) - - Setting.set('postmaster_follow_up_search_in', nil) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_other_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2) - assert_not_equal(ticket.id, ticket_p.id) - - Setting.set('postmaster_follow_up_search_in', 'references') - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_other_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2) - assert_equal(ticket.id, ticket_p.id) - - Setting.set('postmaster_follow_up_search_in', setting_orig) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_other_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1) - assert_not_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2) - assert_not_equal(ticket.id, ticket_p.id) - travel_back - end - - test 'process with follow up check with different ticket hook' do - - Setting.set('ticket_hook', 'VD-Ticket#') - - ticket = Ticket.create( - title: 'follow up check ticket hook', - 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: 'follow up check', - message_id: '<20150830145601.30.608882.123123@edenhofer.zammad.com>', - body: 'some message article', - internal: false, - sender: Ticket::Article::Sender.lookup(name: 'Agent'), - type: Ticket::Article::Type.lookup(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - ) - - email_raw_string_subject = "From: me@example.com -To: customer@example.com -Subject: #{ticket.subject_build('some new subject')} - -Some Text" - - email_raw_string_other_subject = "From: me@example.com -To: customer@example.com -Subject: Aw: RE: other subject [#{Setting.get('ticket_hook')}#{ticket.number}] - -Some Text" - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - travel 1.second - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_other_subject) - assert_equal(ticket.id, ticket_p.id) - - travel_back - end - - test 'process with follow up check with two external reference headers' do - - Setting.set('postmaster_follow_up_search_in', %w[body attachment references]) - - data1 = "From: me@example.com -To: z@example.com -Subject: test 123 -Message-ID: <9d16181c-2db2-c6c1-ff7f-41f2da4e289a@linuxhotel.de> - -test 123 -" - ticket_p1, article_p1, user_p1 = Channel::EmailParser.new.process({}, data1) - - travel 1.second - - data1 = "From: me@example.com -To: z@example.com -Subject: test 123 -Message-ID: <9d16181c-2db2-c6c1-ff7f-41f2da4e289a@linuxhotel.de> - -test 123 -" - ticket_p2, article_p2, user_p2 = Channel::EmailParser.new.process({}, data1) - assert_not_equal(ticket_p1.id, ticket_p2.id) - - data2 = "From: you@example.com -To: y@example.com -Subject: RE: test 123 -Message-ID: -References: <9d16181c-2db2-c6c1-ff7f-41f2da4e289a@linuxhotel.de> - -test 123 -" - ticket_p3, article_p3, user_p3 = Channel::EmailParser.new.process({}, data2) - - assert_equal(ticket_p2.id, ticket_p3.id) - - travel_back - end - - test 'process with follow up check - with auto responses and no T# in subject_build' do - - ticket = Ticket.create( - title: 'follow up - with references follow up check', - group: Group.lookup(name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - 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: 'follow up with references follow up check', - message_id: '<20151222145601.30.608881@edenhofer.zammad.com>', - body: 'some message with references follow up check', - internal: false, - sender: Ticket::Article::Sender.lookup(name: 'Agent'), - type: Ticket::Article::Type.lookup(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - ) - travel 1.second - - # auto response without T# in subject, find follow up by references header - email_raw_string = "From: bob@example.com -To: customer@example.com -Subject: =?ISO-8859-1?Q?AUTO=3A_Bob_Smith_ist_au=DFer_Haus=2E_=2F_is_out_of?= - =?ISO-8859-1?Q?_office=2E_=28R=FCckkehr_am_28=2E12=2E2015=29?= -In-Reply-To: <20251222081758.116249.983698@portal.znuny.com> -References: <20151222145601.30.608881@edenhofer.zammad.com> <20251222081758.116249.983698@portal.znuny.com> -Message-ID: -Auto-Submitted: auto-replied - -Some Text" - - ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket = Ticket.find(ticket.id) - assert_equal(ticket.id, ticket_p.id) - assert_equal('open', ticket.state.name) - travel_back - end - - test 'process with follow up check - email with more forgein T#\'s in subject' do - - ticket = Ticket.create( - title: 'email with more forgein T#\'s in subject', - group: Group.lookup(name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - 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: 'follow up with references follow up check', - message_id: '<20151222145601.30.608881@edenhofer.zammad.com>', - body: 'some message with references follow up check', - internal: false, - sender: Ticket::Article::Sender.lookup(name: 'Agent'), - type: Ticket::Article::Type.lookup(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - ) - travel 1.second - - system_id = Setting.get('system_id') - ticket_hook = Setting.get('ticket_hook') - ticket_hook_divider = Setting.get('ticket_hook_divider') - - tn = "[#{ticket_hook}#{ticket_hook_divider}#{system_id}#{Ticket::Number.generate}99]" - - email_raw_string_subject = "From: me@example.com -To: customer@example.com -Subject: First foreign Tn #{tn} #{tn} #{tn} - #{ticket.subject_build('some new subject')} - -Some Text" - - ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string_subject) - ticket = Ticket.find(ticket.id) - assert_equal(ticket.id, ticket_p.id) - assert_equal('open', ticket.state.name) - travel_back - end - - test 'process with follow up check - ticket initiated by customer without T# in subject and other people in Cc reply to all' do - - # check if follow up based on inital system sender address - Setting.set('postmaster_follow_up_search_in', []) - - subject = "ticket initiated by customer without T# in subject and other people in Cc reply to all #{rand(9999)}" - - email_raw_string = "From: me@example.com -To: my@system.test, bob@example.com -Subject: #{subject} -Message-ID: <123456789-$follow-up-test§-1@linuxhotel.de> - -Some Text" - - ticket_p1, article_1, user_1, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket1 = Ticket.find(ticket_p1.id) - assert_equal(subject, ticket1.title) - - # follow up possible because same subject - email_raw_string = "From: bob@example.com -To: my@system.test, me@example.com -Subject: AW: #{subject} -Message-ID: <123456789-$follow-up-test§-2@linuxhotel.de> -References: <123456789-$follow-up-test§-1@linuxhotel.de> - -Some Text" - - ticket_p2, article_p2, user_p2, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket2 = Ticket.find(ticket_p2.id) - assert_equal(ticket1.id, ticket2.id) - assert_equal(subject, ticket2.title) - - # follow up possible because same subject - email_raw_string = "From: bob@example.com -To: my@system.test, me@example.com -Subject: AW: RE: #{subject} -Message-ID: <123456789-$follow-up-test§-2@linuxhotel.de> -References: <123456789-$follow-up-test§-1@linuxhotel.de> - -Some Text" - - ticket_p3, article_p3, user_p3, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket3 = Ticket.find(ticket_p3.id) - assert_equal(ticket1.id, ticket3.id) - assert_equal(subject, ticket3.title) - - # follow up not possible because subject has changed - subject = "new subject without ticket ref #{rand(9_999_999)}" - email_raw_string = "From: bob@example.com -To: my@system.test -Subject: #{subject} -Message-ID: <123456789-$follow-up-test§-3@linuxhotel.de> -References: <123456789-$follow-up-test§-1@linuxhotel.de> - -Some Text" - - ticket_p4, article_p4, user_p4, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket4 = Ticket.find(ticket_p4.id) - assert_not_equal(ticket1.id, ticket4.id) - assert_equal(subject, ticket4.title) - - # usecase with same subject but no Ticket# (reference headers check because of same subject) - subject = 'Embedded Linux 20.03 - 23.03.17' - - email_raw_string = "From: iw@example.com -To: customer@example.com -Subject: #{subject} -Message-ID: - -Some Text" - - ticket_p5, article_5, user_5, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket5 = Ticket.find(ticket_p5.id) - assert_not_equal(ticket1.id, ticket5.id) - assert_equal(subject, ticket5.title) - - email_raw_string = "From: customer@example.com -To: iw@example.com -Subject: Re: #{subject} -Message-ID: -In-Reply-To: - -Some other Text" - - ticket_p6, article_6, user_6, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket6 = Ticket.find(ticket_p6.id) - assert_equal(ticket5.id, ticket6.id) - assert_equal(subject, ticket6.title) - - end - - test 'process with follow up check - with none ticket# in subject' do - - Setting.set('postmaster_follow_up_search_in', []) - Setting.set('ticket_hook_position', 'none') - - subject = 'some title' - email_raw_string = "From: me@example.com -To: bob@example.com -Subject: #{subject} -Message-ID: <123456789-follow-up-test-ticket_hook_position-none@linuxhotel.de> - -Some Text" - - ticket_p1, article_1, user_1, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket1 = Ticket.find(ticket_p1.id) - assert_equal(subject, ticket1.title) - - # follow up possible because same subject - subject = 'new subject lalala' - email_raw_string = "From: bob@example.com -To: me@example.com -Subject: AW: #{subject} -Message-ID: <123456789-follow-up-test-ticket_hook_position-none-2@linuxhotel.de> -References: <123456789-follow-up-test-ticket_hook_position-none@linuxhotel.de> - -Some Text" - - ticket_p2, article_p2, user_p2, mail = Channel::EmailParser.new.process({}, email_raw_string) - ticket2 = Ticket.find(ticket_p2.id) - assert_equal(ticket1.id, ticket2.id) - end - - test 'process with follow up check - in body' do - - Setting.set('postmaster_follow_up_search_in', %w[body attachment references]) - Setting.set('ticket_hook', '#') - - email_raw_string = "From: me@example.com -To: bob@example.com -Subject: some subject - -Some Text" - - ticket_p1, article_1, user_1, mail = Channel::EmailParser.new.process({}, email_raw_string) - - email_raw_string = "From: me@example.com -To: bob@example.com -Subject: some subject - -Some Text #{Setting.get('ticket_hook')}#{ticket_p1.number} asdasd" - - ticket_p2, article_2, user_2, mail = Channel::EmailParser.new.process({}, email_raw_string) - assert_equal(ticket_p1.id, ticket_p2.id) - - email_raw_string = "From: me@example.com -To: bob@example.com -Subject: some subject -Content-Transfer-Encoding: 7bit -Content-Type: text/html; - -Some Text #{Setting.get('ticket_hook')}#{ticket_p1.number} -" - - ticket_p3, article_3, user_3, mail = Channel::EmailParser.new.process({}, email_raw_string) - assert_equal(ticket_p1.id, ticket_p3.id) - - email_raw_string = "From: me@example.com -To: bob@example.com -Subject: some subject -Content-Transfer-Encoding: 8bit -Content-Type: text/html; - -Some Text test -" - - ticket_p4, article_4, user_4, mail = Channel::EmailParser.new.process({}, email_raw_string) - assert_not_equal(ticket_p1.id, ticket_p4.id) - - end -end