diff options
author | George Claghorn <george@basecamp.com> | 2018-12-24 15:16:22 -0500 |
---|---|---|
committer | George Claghorn <george@basecamp.com> | 2018-12-25 21:32:35 -0500 |
commit | a5b2fff64ca0c1fa7be5124f40a251d991c10a85 (patch) | |
tree | 33a79841402b7151e52d9ad3949ce54f320c10aa /actionmailbox/test/unit | |
parent | 4298df00ae6219b9b5b7c40f281d4fa4d66f4383 (diff) | |
parent | dcddff1d2d0c695318670686a27429a76f20ae03 (diff) | |
download | rails-a5b2fff64ca0c1fa7be5124f40a251d991c10a85.tar.gz rails-a5b2fff64ca0c1fa7be5124f40a251d991c10a85.tar.bz2 rails-a5b2fff64ca0c1fa7be5124f40a251d991c10a85.zip |
Import Action Mailbox
Diffstat (limited to 'actionmailbox/test/unit')
-rw-r--r-- | actionmailbox/test/unit/inbound_email/incineration_test.rb | 47 | ||||
-rw-r--r-- | actionmailbox/test/unit/inbound_email/message_id_test.rb | 15 | ||||
-rw-r--r-- | actionmailbox/test/unit/inbound_email_test.rb | 15 | ||||
-rw-r--r-- | actionmailbox/test/unit/mail_ext/address_equality_test.rb | 11 | ||||
-rw-r--r-- | actionmailbox/test/unit/mail_ext/address_wrapping_test.rb | 13 | ||||
-rw-r--r-- | actionmailbox/test/unit/mail_ext/recipients_test.rb | 35 | ||||
-rw-r--r-- | actionmailbox/test/unit/mailbox/bouncing_test.rb | 31 | ||||
-rw-r--r-- | actionmailbox/test/unit/mailbox/callbacks_test.rb | 77 | ||||
-rw-r--r-- | actionmailbox/test/unit/mailbox/routing_test.rb | 32 | ||||
-rw-r--r-- | actionmailbox/test/unit/mailbox/state_test.rb | 51 | ||||
-rw-r--r-- | actionmailbox/test/unit/postfix_relayer_test.rb | 92 | ||||
-rw-r--r-- | actionmailbox/test/unit/router_test.rb | 139 |
12 files changed, 558 insertions, 0 deletions
diff --git a/actionmailbox/test/unit/inbound_email/incineration_test.rb b/actionmailbox/test/unit/inbound_email/incineration_test.rb new file mode 100644 index 0000000000..21c01a9cea --- /dev/null +++ b/actionmailbox/test/unit/inbound_email/incineration_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class ActionMailbox::InboundEmail::IncinerationTest < ActiveSupport::TestCase + test "incinerating 30 days after delivery" do + freeze_time + + assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do + create_inbound_email_from_fixture("welcome.eml").delivered! + end + + travel 30.days + + assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do + perform_enqueued_jobs only: ActionMailbox::IncinerationJob + end + end + + test "incinerating 30 days after bounce" do + freeze_time + + assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do + create_inbound_email_from_fixture("welcome.eml").bounced! + end + + travel 30.days + + assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do + perform_enqueued_jobs only: ActionMailbox::IncinerationJob + end + end + + test "incinerating 30 days after failure" do + freeze_time + + assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do + create_inbound_email_from_fixture("welcome.eml").failed! + end + + travel 30.days + + assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do + perform_enqueued_jobs only: ActionMailbox::IncinerationJob + end + end +end diff --git a/actionmailbox/test/unit/inbound_email/message_id_test.rb b/actionmailbox/test/unit/inbound_email/message_id_test.rb new file mode 100644 index 0000000000..af467a8d45 --- /dev/null +++ b/actionmailbox/test/unit/inbound_email/message_id_test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class ActionMailbox::InboundEmail::MessageIdTest < ActiveSupport::TestCase + test "message id is extracted from raw email" do + inbound_email = create_inbound_email_from_fixture("welcome.eml") + assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id + end + + test "message id is generated if its missing" do + inbound_email = create_inbound_email_from_source "Date: Fri, 28 Sep 2018 11:08:55 -0700\r\nTo: a@example.com\r\nMime-Version: 1.0\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHello!" + assert_not_nil inbound_email.message_id + end +end diff --git a/actionmailbox/test/unit/inbound_email_test.rb b/actionmailbox/test/unit/inbound_email_test.rb new file mode 100644 index 0000000000..993423406f --- /dev/null +++ b/actionmailbox/test/unit/inbound_email_test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module ActionMailbox + class InboundEmailTest < ActiveSupport::TestCase + test "mail provides the parsed source" do + assert_equal "Discussion: Let's debate these attachments", create_inbound_email_from_fixture("welcome.eml").mail.subject + end + + test "source returns the contents of the raw email" do + assert_equal file_fixture("welcome.eml").read, create_inbound_email_from_fixture("welcome.eml").source + end + end +end diff --git a/actionmailbox/test/unit/mail_ext/address_equality_test.rb b/actionmailbox/test/unit/mail_ext/address_equality_test.rb new file mode 100644 index 0000000000..e4426aeae9 --- /dev/null +++ b/actionmailbox/test/unit/mail_ext/address_equality_test.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module MailExt + class AddressEqualityTest < ActiveSupport::TestCase + test "two addresses with the same address are equal" do + assert_equal Mail::Address.new("david@basecamp.com"), Mail::Address.new("david@basecamp.com") + end + end +end diff --git a/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb b/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb new file mode 100644 index 0000000000..c4eb1328ef --- /dev/null +++ b/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module MailExt + class AddressWrappingTest < ActiveSupport::TestCase + test "wrap" do + needing_wrapping = Mail::Address.wrap("david@basecamp.com") + wrapping_not_needed = Mail::Address.wrap(Mail::Address.new("david@basecamp.com")) + assert_equal needing_wrapping.address, wrapping_not_needed.address + end + end +end diff --git a/actionmailbox/test/unit/mail_ext/recipients_test.rb b/actionmailbox/test/unit/mail_ext/recipients_test.rb new file mode 100644 index 0000000000..fdcad440e2 --- /dev/null +++ b/actionmailbox/test/unit/mail_ext/recipients_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module MailExt + class RecipientsTest < ActiveSupport::TestCase + setup do + @mail = Mail.new \ + to: "david@basecamp.com", + cc: "jason@basecamp.com", + bcc: "andrea@basecamp.com", + x_original_to: "ryan@basecamp.com" + end + + test "recipients include everyone from to, cc, bcc, and x-original-to" do + assert_equal %w[ david@basecamp.com jason@basecamp.com andrea@basecamp.com ryan@basecamp.com ], @mail.recipients + end + + test "recipients addresses use address objects" do + assert_equal "basecamp.com", @mail.recipients_addresses.first.domain + end + + test "to addresses use address objects" do + assert_equal "basecamp.com", @mail.to_addresses.first.domain + end + + test "cc addresses use address objects" do + assert_equal "basecamp.com", @mail.cc_addresses.first.domain + end + + test "bcc addresses use address objects" do + assert_equal "basecamp.com", @mail.bcc_addresses.first.domain + end + end +end diff --git a/actionmailbox/test/unit/mailbox/bouncing_test.rb b/actionmailbox/test/unit/mailbox/bouncing_test.rb new file mode 100644 index 0000000000..d4bd6ea6db --- /dev/null +++ b/actionmailbox/test/unit/mailbox/bouncing_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class BouncingWithReplyMailbox < ActionMailbox::Base + def process + bounce_with BounceMailer.bounce(to: mail.from) + end +end + +class ActionMailbox::Base::BouncingTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + + setup do + @inbound_email = create_inbound_email_from_mail \ + from: "sender@example.com", to: "replies@example.com", subject: "Bounce me" + end + + test "bouncing with a reply" do + perform_enqueued_jobs only: ActionMailer::MailDeliveryJob do + BouncingWithReplyMailbox.receive @inbound_email + end + + assert @inbound_email.bounced? + assert_emails 1 + + mail = ActionMailer::Base.deliveries.last + assert_equal %w[ sender@example.com ], mail.to + assert_equal "Your email was not delivered", mail.subject + end +end diff --git a/actionmailbox/test/unit/mailbox/callbacks_test.rb b/actionmailbox/test/unit/mailbox/callbacks_test.rb new file mode 100644 index 0000000000..8d98a3f3ac --- /dev/null +++ b/actionmailbox/test/unit/mailbox/callbacks_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class CallbackMailbox < ActionMailbox::Base + before_processing { $before_processing = "Ran that!" } + after_processing { $after_processing = "Ran that too!" } + around_processing ->(r, block) { block.call; $around_processing = "Ran that as well!" } + + def process + $processed = mail.subject + end +end + +class BouncingCallbackMailbox < ActionMailbox::Base + before_processing { $before_processing = [ "Pre-bounce" ] } + + before_processing do + bounce_with BounceMailer.bounce(to: mail.from) + $before_processing << "Bounce" + end + + before_processing { $before_processing << "Post-bounce" } + + after_processing { $after_processing = true } + + def process + $processed = true + end +end + +class DiscardingCallbackMailbox < ActionMailbox::Base + before_processing { $before_processing = [ "Pre-discard" ] } + + before_processing do + delivered! + $before_processing << "Discard" + end + + before_processing { $before_processing << "Post-discard" } + + after_processing { $after_processing = true } + + def process + $processed = true + end +end + +class ActionMailbox::Base::CallbacksTest < ActiveSupport::TestCase + setup do + $before_processing = $after_processing = $around_processing = $processed = false + @inbound_email = create_inbound_email_from_fixture("welcome.eml") + end + + test "all callback types" do + CallbackMailbox.receive @inbound_email + assert_equal "Ran that!", $before_processing + assert_equal "Ran that too!", $after_processing + assert_equal "Ran that as well!", $around_processing + end + + test "bouncing in a callback terminates processing" do + BouncingCallbackMailbox.receive @inbound_email + assert @inbound_email.bounced? + assert_equal [ "Pre-bounce", "Bounce" ], $before_processing + assert_not $processed + assert_not $after_processing + end + + test "marking the inbound email as delivered in a callback terminates processing" do + DiscardingCallbackMailbox.receive @inbound_email + assert @inbound_email.delivered? + assert_equal [ "Pre-discard", "Discard" ], $before_processing + assert_not $processed + assert_not $after_processing + end +end diff --git a/actionmailbox/test/unit/mailbox/routing_test.rb b/actionmailbox/test/unit/mailbox/routing_test.rb new file mode 100644 index 0000000000..d4dad7eafb --- /dev/null +++ b/actionmailbox/test/unit/mailbox/routing_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class ApplicationMailbox < ActionMailbox::Base + routing "replies@example.com" => :replies +end + +class RepliesMailbox < ActionMailbox::Base + def process + $processed = mail.subject + end +end + +class ActionMailbox::Base::RoutingTest < ActiveSupport::TestCase + setup do + $processed = false + @inbound_email = create_inbound_email_from_fixture("welcome.eml") + end + + test "string routing" do + ApplicationMailbox.route @inbound_email + assert_equal "Discussion: Let's debate these attachments", $processed + end + + test "delayed routing" do + perform_enqueued_jobs only: ActionMailbox::RoutingJob do + create_inbound_email_from_fixture "welcome.eml", status: :pending + assert_equal "Discussion: Let's debate these attachments", $processed + end + end +end diff --git a/actionmailbox/test/unit/mailbox/state_test.rb b/actionmailbox/test/unit/mailbox/state_test.rb new file mode 100644 index 0000000000..b3a58ad667 --- /dev/null +++ b/actionmailbox/test/unit/mailbox/state_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +class SuccessfulMailbox < ActionMailbox::Base + def process + $processed = mail.subject + end +end + +class UnsuccessfulMailbox < ActionMailbox::Base + rescue_from(RuntimeError) { $processed = :failure } + + def process + raise "No way!" + end +end + +class BouncingMailbox < ActionMailbox::Base + def process + $processed = :bounced + bounced! + end +end + + +class ActionMailbox::Base::StateTest < ActiveSupport::TestCase + setup do + $processed = false + @inbound_email = create_inbound_email_from_mail \ + to: "replies@example.com", subject: "I was processed" + end + + test "successful mailbox processing leaves inbound email in delivered state" do + SuccessfulMailbox.receive @inbound_email + assert @inbound_email.delivered? + assert_equal "I was processed", $processed + end + + test "unsuccessful mailbox processing leaves inbound email in failed state" do + UnsuccessfulMailbox.receive @inbound_email + assert @inbound_email.failed? + assert_equal :failure, $processed + end + + test "bounced inbound emails are not delivered" do + BouncingMailbox.receive @inbound_email + assert @inbound_email.bounced? + assert_equal :bounced, $processed + end +end diff --git a/actionmailbox/test/unit/postfix_relayer_test.rb b/actionmailbox/test/unit/postfix_relayer_test.rb new file mode 100644 index 0000000000..5f7496ec3f --- /dev/null +++ b/actionmailbox/test/unit/postfix_relayer_test.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +require "action_mailbox/postfix_relayer" + +module ActionMailbox + class PostfixRelayerTest < ActiveSupport::TestCase + URL = "https://example.com/rails/action_mailbox/postfix/inbound_emails" + INGRESS_PASSWORD = "secret" + + setup do + @relayer = ActionMailbox::PostfixRelayer.new(url: URL, password: INGRESS_PASSWORD) + end + + test "successfully relaying an email" do + stub_request(:post, URL).to_return status: 204 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "2.0.0 Successfully relayed message to Postfix ingress", result.output + assert result.success? + assert_not result.failure? + + assert_requested :post, URL, body: file_fixture("welcome.eml").read, + basic_auth: [ "actionmailbox", INGRESS_PASSWORD ], + headers: { "Content-Type" => "message/rfc822", "User-Agent" => /\AAction Mailbox Postfix relayer v\d+\./ } + end + + test "unsuccessfully relaying with invalid credentials" do + stub_request(:post, URL).to_return status: 401 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.7.0 Invalid credentials for Postfix ingress", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to an unspecified server error" do + stub_request(:post, URL).to_return status: 500 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0 HTTP 500", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to a gateway timeout" do + stub_request(:post, URL).to_return status: 504 + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0 HTTP 504", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to ECONNRESET" do + stub_request(:post, URL).to_raise Errno::ECONNRESET.new + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2 Network error relaying to Postfix ingress: Connection reset by peer", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to connection failure" do + stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443") + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2 Network error relaying to Postfix ingress: Failed to open TCP connection to example.com:443", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to client-side timeout" do + stub_request(:post, URL).to_timeout + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.4.2 Timed out relaying to Postfix ingress", result.output + assert_not result.success? + assert result.failure? + end + + test "unsuccessfully relaying due to an unhandled exception" do + stub_request(:post, URL).to_raise StandardError.new("Something went wrong") + + result = @relayer.relay(file_fixture("welcome.eml").read) + assert_equal "4.0.0 Error relaying to Postfix ingress: Something went wrong", result.output + assert_not result.success? + assert result.failure? + end + end +end diff --git a/actionmailbox/test/unit/router_test.rb b/actionmailbox/test/unit/router_test.rb new file mode 100644 index 0000000000..c5dce60856 --- /dev/null +++ b/actionmailbox/test/unit/router_test.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +class RootMailbox < ActionMailbox::Base + def process + $processed_by = self.class.to_s + $processed_mail = mail + end +end + +class FirstMailbox < RootMailbox +end + +class SecondMailbox < RootMailbox +end + +module Nested + class FirstMailbox < RootMailbox + end +end + +class FirstMailboxAddress + def match?(inbound_email) + inbound_email.mail.to.include?("replies-class@example.com") + end +end + +module ActionMailbox + class RouterTest < ActiveSupport::TestCase + setup do + @router = ActionMailbox::Router.new + $processed_by = $processed_mail = nil + end + + test "single string route" do + @router.add_routes("first@example.com" => :first) + + inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "FirstMailbox", $processed_by + assert_equal inbound_email.mail, $processed_mail + end + + test "single string routing on cc" do + @router.add_routes("first@example.com" => :first) + + inbound_email = create_inbound_email_from_mail(to: "someone@example.com", cc: "first@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "FirstMailbox", $processed_by + assert_equal inbound_email.mail, $processed_mail + end + + test "single string routing case-insensitively" do + @router.add_routes("first@example.com" => :first) + + inbound_email = create_inbound_email_from_mail(to: "FIRST@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "FirstMailbox", $processed_by + assert_equal inbound_email.mail, $processed_mail + end + + test "multiple string routes" do + @router.add_routes("first@example.com" => :first, "second@example.com" => :second) + + inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "FirstMailbox", $processed_by + assert_equal inbound_email.mail, $processed_mail + + inbound_email = create_inbound_email_from_mail(to: "second@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "SecondMailbox", $processed_by + assert_equal inbound_email.mail, $processed_mail + end + + test "single regexp route" do + @router.add_routes(/replies-\w+@example.com/ => :first, "replies-nowhere@example.com" => :second) + + inbound_email = create_inbound_email_from_mail(to: "replies-okay@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "FirstMailbox", $processed_by + end + + test "single proc route" do + @router.add_route \ + ->(inbound_email) { inbound_email.mail.to.include?("replies-proc@example.com") }, + to: :second + + @router.route create_inbound_email_from_mail(to: "replies-proc@example.com", subject: "This is a reply") + assert_equal "SecondMailbox", $processed_by + end + + test "address class route" do + @router.add_route FirstMailboxAddress.new, to: :first + @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply") + assert_equal "FirstMailbox", $processed_by + end + + test "string route to nested mailbox" do + @router.add_route "first@example.com", to: "nested/first" + + inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply") + @router.route inbound_email + assert_equal "Nested::FirstMailbox", $processed_by + end + + test "all as the only route" do + @router.add_route :all, to: :first + @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply") + assert_equal "FirstMailbox", $processed_by + end + + test "all as the second route" do + @router.add_route FirstMailboxAddress.new, to: :first + @router.add_route :all, to: :second + + @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply") + assert_equal "FirstMailbox", $processed_by + + @router.route create_inbound_email_from_mail(to: "elsewhere@example.com", subject: "This is a reply") + assert_equal "SecondMailbox", $processed_by + end + + test "missing route" do + assert_raises(ActionMailbox::Router::RoutingError) do + inbound_email = create_inbound_email_from_mail(to: "going-nowhere@example.com", subject: "This is a reply") + @router.route inbound_email + assert inbound_email.bounced? + end + end + + test "invalid address" do + assert_raises(ArgumentError) do + @router.add_route Array.new, to: :first + end + end + end +end |