aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailbox/test/unit
diff options
context:
space:
mode:
authorGeorge Claghorn <george@basecamp.com>2018-12-24 15:16:22 -0500
committerGeorge Claghorn <george@basecamp.com>2018-12-25 21:32:35 -0500
commita5b2fff64ca0c1fa7be5124f40a251d991c10a85 (patch)
tree33a79841402b7151e52d9ad3949ce54f320c10aa /actionmailbox/test/unit
parent4298df00ae6219b9b5b7c40f281d4fa4d66f4383 (diff)
parentdcddff1d2d0c695318670686a27429a76f20ae03 (diff)
downloadrails-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.rb47
-rw-r--r--actionmailbox/test/unit/inbound_email/message_id_test.rb15
-rw-r--r--actionmailbox/test/unit/inbound_email_test.rb15
-rw-r--r--actionmailbox/test/unit/mail_ext/address_equality_test.rb11
-rw-r--r--actionmailbox/test/unit/mail_ext/address_wrapping_test.rb13
-rw-r--r--actionmailbox/test/unit/mail_ext/recipients_test.rb35
-rw-r--r--actionmailbox/test/unit/mailbox/bouncing_test.rb31
-rw-r--r--actionmailbox/test/unit/mailbox/callbacks_test.rb77
-rw-r--r--actionmailbox/test/unit/mailbox/routing_test.rb32
-rw-r--r--actionmailbox/test/unit/mailbox/state_test.rb51
-rw-r--r--actionmailbox/test/unit/postfix_relayer_test.rb92
-rw-r--r--actionmailbox/test/unit/router_test.rb139
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