Browse Source

Maintenance: Ported legacy test email_postmaster_to_sender.rb to rspec.

Martin Gruner 2 years ago
parent
commit
95d6734d91

+ 0 - 1
.gitlab/ci/test/integration/other.yml

@@ -7,6 +7,5 @@
     - echo "email_helper_deliver tests..."
     - bundle exec rails test test/integration/email_deliver_test.rb
     - bundle exec rails test test/integration/email_keep_on_server_test.rb
-    - bundle exec rails test test/integration/email_postmaster_to_sender.rb
     - echo "Clearbit test..."
     - bundle exec rails test test/integration/clearbit_test.rb

+ 161 - 1
spec/models/channel/driver/imap_spec.rb

@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe Channel::Driver::Imap, integration: true, required_envs: %w[MAIL_SERVER MAIL_ADDRESS_ASCII MAIL_PASS_ASCII] do
+RSpec.describe Channel::Driver::Imap, integration: true, required_envs: %w[MAIL_SERVER MAIL_ADDRESS MAIL_PASS MAIL_ADDRESS_ASCII MAIL_PASS_ASCII] do
   # https://github.com/zammad/zammad/issues/2964
   context 'when connecting with a ASCII 8-Bit password' do
     it 'succeeds' do
@@ -65,4 +65,164 @@ RSpec.describe Channel::Driver::Imap, integration: true, required_envs: %w[MAIL_
       expect { described_class.extract_rfc822_headers(nil) }.not_to raise_error
     end
   end
+
+  describe 'handling of oversized incoming emails' do
+    let(:folder) { "postmaster_to_sender_#{SecureRandom.uuid}" }
+
+    let(:server_address) { ENV['MAIL_SERVER'] }
+    let(:server_login)    { ENV['MAIL_ADDRESS'] }
+    let(:server_password) { ENV['MAIL_PASS'] }
+    let(:email_address)   { create(:email_address, realname: 'Zammad Helpdesk', email: "some-zammad-#{ENV['MAIL_ADDRESS']}") }
+    let(:group)           { create(:group, name: 'PostmasterToSenderTest', email_address: email_address) }
+    let(:inbound_options) do
+      {
+        adapter: 'imap',
+        options: {
+          host:           ENV['MAIL_SERVER'],
+          user:           ENV['MAIL_ADDRESS'],
+          password:       server_password,
+          ssl:            true,
+          folder:         folder,
+          keep_on_server: false,
+        }
+      }
+    end
+    let(:outbound_options) do
+      {
+        adapter: 'smtp',
+        options: {
+          host:      server_address,
+          port:      25,
+          start_tls: true,
+          user:      server_login,
+          password:  server_password,
+          email:     email_address.email
+        },
+      }
+    end
+    let(:channel) do
+      create(:email_channel, group: group, inbound: inbound_options, outbound: outbound_options).tap do |channel|
+        email_address.channel = channel
+        email_address.save!
+      end
+    end
+
+    let(:imap) { Net::IMAP.new(server_address, 993, true, nil, false).tap { |imap| imap.login(server_login, server_password) } }
+    let(:sender_email_address) { ENV['MAIL_ADDRESS'] }
+    let(:cid)                  { SecureRandom.uuid.tr('-', '.') }
+    let(:oversized_email) do
+      <<~OVERSIZED_EMAIL.gsub(%r{\n}, "\r\n")
+        Subject: Oversized Email Message
+        From: Max Mustermann <#{sender_email_address}>
+        To: shugo@example.com
+        Message-ID: <#{cid}@zammad.test.com>
+
+        Oversized Email Message Body #{'#' * 120_000}
+      OVERSIZED_EMAIL
+    end
+    let(:oversized_email_md5) { Digest::MD5.hexdigest(oversized_email) }
+    let(:oversized_email_size) { format('%<MB>.2f', MB: oversized_email.size.to_f / 1024 / 1024) }
+    let(:oversized_eml_folder) { Rails.root.join('tmp/oversized_mail') }
+    let(:oversized_eml_file) do
+      Dir.entries(oversized_eml_folder).grep(%r{^#{oversized_email_md5}\.eml$}).map { |path| oversized_eml_folder.join(path) }.last
+    end
+
+    let(:purge_inbox) do
+      imap.select('inbox')
+      imap.sort(['DATE'], ['ALL'], 'US-ASCII').each do |msg|
+        imap.store(msg, '+FLAGS', [:Deleted])
+      end
+      imap.expunge
+    end
+
+    let(:fetch_oversized_email) do
+      imap.create(folder)
+      imap.select(folder)
+      imap.append(folder, oversized_email, [], Time.zone.now)
+      channel.fetch(true)
+    end
+
+    context 'with email reply', :aggregate_failures do
+      before do
+        Setting.set('postmaster_max_size', 0.1)
+        purge_inbox
+        fetch_oversized_email
+      end
+
+      after do
+        imap.delete(folder)
+      end
+
+      let(:oversized_email_reply) do
+        imap.select('inbox')
+        5.times do |i|
+          sleep i
+          msg = imap.sort(['DATE'], ['ALL'], 'US-ASCII').first
+          if msg
+            return imap.fetch(msg, 'RFC822')[0].attr['RFC822']
+          end
+        end
+        nil
+      end
+
+      let(:parsed_oversized_email_reply) do
+        Channel::EmailParser.new.parse(oversized_email_reply)
+      end
+
+      it 'creates email reply correctly' do
+        # 1. verify that the oversized email has been saved locally to:
+        # /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
+        expect(oversized_eml_file).to be_present
+
+        # verify that the file is byte for byte identical to the sent message
+        expect(File.read(oversized_eml_file)).to eq(oversized_email)
+
+        # 2. verify that a postmaster response email has been sent to the sender
+        expect(oversized_email_reply).to be_present
+
+        # parse the reply mail and verify the various headers
+        expect(parsed_oversized_email_reply).to include({
+                                                          from_email: email_address.email,
+          subject: '[undeliverable] Message too large',
+          'references' => "<#{cid}@zammad.test.com>",
+          'in-reply-to' => "<#{cid}@zammad.test.com>",
+                                                        })
+
+        # verify the reply mail body content
+        expect(parsed_oversized_email_reply[:body]).to match(%r{^Dear Max Mustermann.*Oversized Email Message.*#{oversized_email_size} MB.*0.1 MB.*#{Setting.get('fqdn')}}sm)
+
+        # 3. check if original mail got removed
+        imap.select(folder)
+        expect(imap.sort(['DATE'], ['ALL'], 'US-ASCII')).to be_empty
+      end
+    end
+
+    context 'without email reply' do
+      before do
+        Setting.set('postmaster_max_size', 0.1)
+        Setting.set('postmaster_send_reject_if_mail_too_large', false)
+        purge_inbox
+        fetch_oversized_email
+      end
+
+      after do
+        imap.delete(folder)
+      end
+
+      it 'does not create email reply', :aggregate_failures do
+
+        # 1. verify that email was not locally processed
+        expect(oversized_eml_file).to be_nil
+
+        # 2. verify that no postmaster response email has been sent
+        imap.select('inbox')
+        sleep 1
+        expect(imap.sort(['DATE'], ['ALL'], 'US-ASCII').count).to be_zero
+
+        # 3. check that original mail is still there
+        imap.select(folder)
+        expect(imap.sort(['DATE'], ['ALL'], 'US-ASCII').count).to be(1)
+      end
+    end
+  end
 end

+ 0 - 223
test/integration/email_postmaster_to_sender.rb

@@ -1,223 +0,0 @@
-# Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
-
-require 'test_helper'
-require 'net/imap'
-
-class EmailPostmasterToSender < ActiveSupport::TestCase
-
-  setup do
-    Setting.set('postmaster_max_size', 0.1)
-
-    @test_id = SecureRandom.uuid
-
-    # setup the IMAP account info for Zammad
-    if ENV['MAIL_SERVER'].blank?
-      raise "Need MAIL_SERVER as ENV variable like export MAIL_SERVER='mx.example.com'"
-    end
-    if ENV['MAIL_ADDRESS'].blank?
-      raise 'Need MAIL_ADDRESS as ENV variable'
-    end
-    if ENV['MAIL_PASS'].blank?
-      raise 'Need MAIL_PASS as ENV variable'
-    end
-
-    @server_address = ENV['MAIL_SERVER']
-    @server_login = ENV['MAIL_ADDRESS']
-    @server_password = ENV['MAIL_PASS']
-
-    @folder = "postmaster_to_sender_#{@test_id}"
-
-    if ENV['MAIL_ADDRESS'].blank?
-      raise "Need MAIL_ADDRESS as ENV variable like export MAIL_ADDRESS='admin@example.com'"
-    end
-
-    @sender_email_address = ENV['MAIL_ADDRESS']
-
-    @email_address = EmailAddress.create!(
-      realname:      'me Helpdesk',
-      email:         "some-zammad-#{ENV['MAIL_ADDRESS']}",
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-
-    group = Group.create_or_update(
-      name:             'PostmasterToSenderTest',
-      email_address_id: @email_address.id,
-      updated_by_id:    1,
-      created_by_id:    1,
-    )
-
-    @channel = Channel.create!(
-      area:          'Email::Account',
-      group_id:      group.id,
-      options:       {
-        inbound:  {
-          adapter: 'imap',
-          options: {
-            host:           @server_address,
-            user:           @server_login,
-            password:       @server_password,
-            ssl:            true,
-            folder:         @folder,
-            keep_on_server: false,
-          }
-        },
-        outbound: {
-          adapter: 'smtp',
-          options: {
-            host:      @server_address,
-            port:      25,
-            start_tls: true,
-            user:      @server_login,
-            password:  @server_password,
-            email:     @email_address.email
-          },
-        },
-      },
-      active:        true,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-    @email_address.channel_id = @channel.id
-    @email_address.save!
-  end
-
-  test 'postmaster reply with email on oversized incoming emails' do
-    imap = Net::IMAP.new(@server_address, 993, true, nil, false)
-    imap.login(@server_login, @server_password)
-    imap.create(@folder)
-    imap.select(@folder)
-
-    # put a very large message in it
-    large_message = "Subject: Oversized Email Message
-From: Max Mustermann <#{@sender_email_address}>
-To: shugo@example.com
-Message-ID: <#{@test_id}@zammad.test.com>
-
-Oversized Email Message Body #{'#' * 120_000}
-".gsub(%r{\n}, "\r\n")
-
-    large_message_md5 = Digest::MD5.hexdigest(large_message)
-    large_message_size = format('%<MB>.2f', MB: large_message.size.to_f / 1024 / 1024)
-
-    imap.append(@folder, large_message, [], Time.zone.now)
-
-    @channel.fetch(true)
-
-    # 1. verify that the oversized email has been saved locally to:
-    # /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
-    path = Rails.root.join('tmp/oversized_mail')
-    target_files = Dir.entries(path).grep(%r{^#{large_message_md5}\.eml$})
-    assert(target_files.present?, 'Large message .eml log file must be present.')
-
-    # pick the latest file that matches the criteria
-    target_file = target_files.max
-
-    # verify that the file is byte for byte identical to the sent message
-    file_path = Rails.root.join('tmp/oversized_mail', target_file)
-    eml_data = File.read(file_path)
-    assert_equal(large_message, eml_data)
-
-    # 2. verify that a postmaster response email has been sent to the sender
-    message_ids = nil
-    5.times do |sleep_offset|
-      imap.select('inbox')
-      message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
-
-      break if message_ids.count.positive?
-
-      # send mail hasn't arrived yet in the inbox
-      sleep sleep_offset
-    end
-
-    assert(message_ids.count.positive?, 'Must have received a reply from the postmaster')
-    imap_message_id = message_ids.last
-    msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
-    assert(msg.present?, 'Must have received a reply from the postmaster')
-    imap.store(imap_message_id, '+FLAGS', [:Deleted])
-    imap.expunge
-
-    # parse the reply mail and verify the various headers
-    parser = Channel::EmailParser.new
-    mail = parser.parse(msg)
-    assert_equal(mail[:from_email], @email_address.email)
-    assert_equal(mail[:subject], '[undeliverable] Message too large')
-    assert_equal("<#{@test_id}@zammad.test.com>",
-                 mail['references'],
-                 'Reply\'s Referecnes header must match the send message ID')
-    assert_equal("<#{@test_id}@zammad.test.com>",
-                 mail['in-reply-to'],
-                 'Reply\'s In-Reply-To header must match the send message ID')
-
-    # verify the reply mail body content
-    body = mail[:body]
-    assert(body.start_with?('Dear Max Mustermann'), 'Body must contain sender name')
-    assert(body.include?('Oversized Email Message'), 'Body must contain original subject')
-    assert(body.include?('0.1 MB'), 'Body must contain max allowed message size')
-    assert(body.include?("#{large_message_size} MB"), 'Body must contain the original message size')
-    assert(body.include?(Setting.get('fqdn')), 'Body must contain the Zammad instance name')
-
-    # 3. check if original mail got removed
-    imap.select(@folder)
-    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
-    assert_equal(message_ids.count, 0, 'Original customer mail must be deleted.')
-
-    # final clean up
-    imap.delete(@folder)
-    @channel.destroy!
-  end
-
-  test 'postmaster reply with no email on oversized incoming emails' do
-    Setting.set('postmaster_send_reject_if_mail_too_large', false)
-    imap = Net::IMAP.new(@server_address, 993, true, nil, false)
-    imap.login(@server_login, @server_password)
-
-    imap.select('inbox')
-    message_count = imap.sort(['DATE'], ['ALL'], 'US-ASCII').count
-
-    imap.create(@folder)
-    imap.select(@folder)
-
-    # put a very large message in it
-    large_message = "Subject: Oversized Email Message
-From: Max Mustermann <#{@sender_email_address}>
-To: shugo@example.com
-Message-ID: <#{@test_id}@zammad.test.com>
-
-Oversized Email Message Body #{'#' * 120_000}
-".gsub(%r{\n}, "\r\n")
-
-    imap.append(@folder, large_message, [], Time.zone.now)
-
-    @channel.fetch(true)
-
-    # 1. verify that the oversized email has been saved locally to:
-    # /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
-    path = Rails.root.join('tmp/oversized_mail')
-    target_files = Dir.entries(path).grep(%r{^.+?\.eml$})
-    assert_not(target_files.blank?, 'Large message .eml log file must be blank.')
-
-    # 2. verify that a postmaster response email has been sent to the sender
-    imap.select('inbox')
-    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
-    assert_equal(message_ids.count, message_count, 'Must not have received a reply from the postmaster')
-
-    # 3. check if original mail got removed
-    imap.select(@folder)
-    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
-    imap_message_id = message_ids.last
-    msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
-    imap.store(imap_message_id, '+FLAGS', [:Deleted])
-    imap.expunge
-    assert(msg.present?, 'Oversized Email Message')
-    assert_equal(message_ids.count, 1, 'Original customer mail must be deleted.')
-
-    # final clean up
-    imap.delete(@folder)
-    @channel.destroy!
-  end
-
-  teardown do
-    Setting.set('postmaster_max_size', 10)
-  end
-end