actionmailbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +159 -0
  5. data/LICENSE +21 -0
  6. data/README.md +278 -0
  7. data/Rakefile +27 -0
  8. data/actionmailbox.gemspec +27 -0
  9. data/app/controllers/action_mailbox/base_controller.rb +43 -0
  10. data/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb +50 -0
  11. data/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +99 -0
  12. data/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb +78 -0
  13. data/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb +55 -0
  14. data/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb +50 -0
  15. data/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +27 -0
  16. data/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +15 -0
  17. data/app/controllers/rails/conductor/base_controller.rb +10 -0
  18. data/app/jobs/action_mailbox/incineration_job.rb +18 -0
  19. data/app/jobs/action_mailbox/routing_job.rb +9 -0
  20. data/app/models/action_mailbox/inbound_email.rb +43 -0
  21. data/app/models/action_mailbox/inbound_email/incineratable.rb +18 -0
  22. data/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +22 -0
  23. data/app/models/action_mailbox/inbound_email/message_id.rb +36 -0
  24. data/app/models/action_mailbox/inbound_email/routable.rb +22 -0
  25. data/app/views/layouts/rails/conductor.html.erb +7 -0
  26. data/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb +15 -0
  27. data/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +42 -0
  28. data/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb +15 -0
  29. data/bin/test +5 -0
  30. data/config/routes.rb +19 -0
  31. data/db/migrate/20180917164000_create_action_mailbox_tables.rb +11 -0
  32. data/lib/action_mailbox.rb +15 -0
  33. data/lib/action_mailbox/base.rb +111 -0
  34. data/lib/action_mailbox/callbacks.rb +32 -0
  35. data/lib/action_mailbox/engine.rb +34 -0
  36. data/lib/action_mailbox/mail_ext.rb +4 -0
  37. data/lib/action_mailbox/mail_ext/address_equality.rb +5 -0
  38. data/lib/action_mailbox/mail_ext/address_wrapping.rb +5 -0
  39. data/lib/action_mailbox/mail_ext/addresses.rb +25 -0
  40. data/lib/action_mailbox/mail_ext/from_source.rb +5 -0
  41. data/lib/action_mailbox/mail_ext/recipients.rb +5 -0
  42. data/lib/action_mailbox/postfix_relayer.rb +67 -0
  43. data/lib/action_mailbox/router.rb +38 -0
  44. data/lib/action_mailbox/router/route.rb +38 -0
  45. data/lib/action_mailbox/routing.rb +20 -0
  46. data/lib/action_mailbox/test_case.rb +8 -0
  47. data/lib/action_mailbox/test_helper.rb +42 -0
  48. data/lib/action_mailbox/version.rb +3 -0
  49. data/lib/tasks/ingress.rake +24 -0
  50. data/lib/tasks/install.rake +20 -0
  51. data/lib/templates/installer.rb +4 -0
  52. data/lib/templates/mailboxes/application_mailbox.rb +3 -0
  53. data/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb +20 -0
  54. data/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb +89 -0
  55. data/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb +58 -0
  56. data/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb +54 -0
  57. data/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb +44 -0
  58. data/test/dummy/.babelrc +18 -0
  59. data/test/dummy/.gitignore +3 -0
  60. data/test/dummy/.postcssrc.yml +3 -0
  61. data/test/dummy/Rakefile +6 -0
  62. data/test/dummy/app/assets/config/manifest.js +3 -0
  63. data/test/dummy/app/assets/images/.keep +0 -0
  64. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  65. data/test/dummy/app/assets/stylesheets/scaffold.css +80 -0
  66. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  67. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  68. data/test/dummy/app/controllers/application_controller.rb +2 -0
  69. data/test/dummy/app/controllers/concerns/.keep +0 -0
  70. data/test/dummy/app/helpers/application_helper.rb +2 -0
  71. data/test/dummy/app/javascript/packs/application.js +0 -0
  72. data/test/dummy/app/jobs/application_job.rb +2 -0
  73. data/test/dummy/app/mailboxes/application_mailbox.rb +2 -0
  74. data/test/dummy/app/mailboxes/messages_mailbox.rb +4 -0
  75. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  76. data/test/dummy/app/models/application_record.rb +3 -0
  77. data/test/dummy/app/models/concerns/.keep +0 -0
  78. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  79. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  80. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  81. data/test/dummy/bin/bundle +3 -0
  82. data/test/dummy/bin/rails +4 -0
  83. data/test/dummy/bin/rake +4 -0
  84. data/test/dummy/bin/setup +36 -0
  85. data/test/dummy/bin/update +31 -0
  86. data/test/dummy/bin/yarn +11 -0
  87. data/test/dummy/config.ru +5 -0
  88. data/test/dummy/config/application.rb +19 -0
  89. data/test/dummy/config/boot.rb +5 -0
  90. data/test/dummy/config/cable.yml +10 -0
  91. data/test/dummy/config/database.yml +25 -0
  92. data/test/dummy/config/environment.rb +5 -0
  93. data/test/dummy/config/environments/development.rb +63 -0
  94. data/test/dummy/config/environments/production.rb +96 -0
  95. data/test/dummy/config/environments/test.rb +46 -0
  96. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  97. data/test/dummy/config/initializers/assets.rb +14 -0
  98. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  99. data/test/dummy/config/initializers/content_security_policy.rb +22 -0
  100. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  101. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  102. data/test/dummy/config/initializers/inflections.rb +16 -0
  103. data/test/dummy/config/initializers/mime_types.rb +4 -0
  104. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  105. data/test/dummy/config/locales/en.yml +33 -0
  106. data/test/dummy/config/puma.rb +34 -0
  107. data/test/dummy/config/routes.rb +4 -0
  108. data/test/dummy/config/spring.rb +6 -0
  109. data/test/dummy/config/storage.yml +35 -0
  110. data/test/dummy/config/webpack/development.js +3 -0
  111. data/test/dummy/config/webpack/environment.js +3 -0
  112. data/test/dummy/config/webpack/production.js +3 -0
  113. data/test/dummy/config/webpack/test.js +3 -0
  114. data/test/dummy/config/webpacker.yml +65 -0
  115. data/test/dummy/db/migrate/20180208205311_create_action_mailroom_tables.rb +11 -0
  116. data/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb +26 -0
  117. data/test/dummy/db/schema.rb +43 -0
  118. data/test/dummy/lib/assets/.keep +0 -0
  119. data/test/dummy/log/.keep +0 -0
  120. data/test/dummy/package.json +11 -0
  121. data/test/dummy/public/404.html +67 -0
  122. data/test/dummy/public/422.html +67 -0
  123. data/test/dummy/public/500.html +66 -0
  124. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  125. data/test/dummy/public/apple-touch-icon.png +0 -0
  126. data/test/dummy/public/favicon.ico +0 -0
  127. data/test/dummy/storage/.keep +0 -0
  128. data/test/dummy/yarn.lock +6071 -0
  129. data/test/fixtures/files/welcome.eml +631 -0
  130. data/test/jobs/incineration_job_test.rb +17 -0
  131. data/test/test_helper.rb +54 -0
  132. data/test/unit/inbound_email/incineration_test.rb +45 -0
  133. data/test/unit/inbound_email/message_id_test.rb +13 -0
  134. data/test/unit/inbound_email_test.rb +13 -0
  135. data/test/unit/mail_ext/address_equality_test.rb +9 -0
  136. data/test/unit/mail_ext/address_wrapping_test.rb +11 -0
  137. data/test/unit/mail_ext/recipients_test.rb +33 -0
  138. data/test/unit/mailbox/bouncing_test.rb +29 -0
  139. data/test/unit/mailbox/callbacks_test.rb +75 -0
  140. data/test/unit/mailbox/routing_test.rb +30 -0
  141. data/test/unit/mailbox/state_test.rb +49 -0
  142. data/test/unit/postfix_relayer_test.rb +90 -0
  143. data/test/unit/router_test.rb +137 -0
  144. metadata +355 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class ActionMailbox::IncinerationJobTest < ActiveJob::TestCase
6
+ setup { @inbound_email = receive_inbound_email_from_fixture("welcome.eml") }
7
+
8
+ test "ignoring a missing inbound email" do
9
+ @inbound_email.destroy!
10
+
11
+ perform_enqueued_jobs do
12
+ assert_nothing_raised do
13
+ ActionMailbox::IncinerationJob.perform_later @inbound_email
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = "tbsy84uSV1Kt3ZJZELY2TmShPRs91E3yL4tzf96297vBCkDWgL"
3
+
4
+ require_relative "../test/dummy/config/environment"
5
+ ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
6
+ require "rails/test_help"
7
+
8
+ require "byebug"
9
+ require "webmock/minitest"
10
+
11
+ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
12
+
13
+ require "rails/test_unit/reporter"
14
+ Rails::TestUnitReporter.executable = 'bin/test'
15
+
16
+ if ActiveSupport::TestCase.respond_to?(:fixture_path=)
17
+ ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
18
+ ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
19
+ ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
20
+ ActiveSupport::TestCase.fixtures :all
21
+ end
22
+
23
+ require "action_mailbox/test_helper"
24
+
25
+ class ActiveSupport::TestCase
26
+ include ActionMailbox::TestHelper, ActiveJob::TestHelper
27
+ end
28
+
29
+ class ActionDispatch::IntegrationTest
30
+ private
31
+ def credentials
32
+ ActionController::HttpAuthentication::Basic.encode_credentials "actionmailbox", ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
33
+ end
34
+
35
+ def switch_password_to(new_password)
36
+ previous_password, ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = ENV["RAILS_INBOUND_EMAIL_PASSWORD"], new_password
37
+ yield
38
+ ensure
39
+ ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = previous_password
40
+ end
41
+ end
42
+
43
+ if ARGV.include?("-v")
44
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
45
+ ActiveJob::Base.logger = Logger.new(STDOUT)
46
+ end
47
+
48
+ class BounceMailer < ActionMailer::Base
49
+ def bounce(to:)
50
+ mail from: "receiver@example.com", to: to, subject: "Your email was not delivered" do |format|
51
+ format.html { render plain: "Sorry!" }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class ActionMailbox::InboundEmail::IncinerationTest < ActiveSupport::TestCase
4
+ test "incinerating 30 days after delivery" do
5
+ freeze_time
6
+
7
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
8
+ create_inbound_email_from_fixture("welcome.eml").delivered!
9
+ end
10
+
11
+ travel 30.days
12
+
13
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
14
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
15
+ end
16
+ end
17
+
18
+ test "incinerating 30 days after bounce" do
19
+ freeze_time
20
+
21
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
22
+ create_inbound_email_from_fixture("welcome.eml").bounced!
23
+ end
24
+
25
+ travel 30.days
26
+
27
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
28
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
29
+ end
30
+ end
31
+
32
+ test "incinerating 30 days after failure" do
33
+ freeze_time
34
+
35
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
36
+ create_inbound_email_from_fixture("welcome.eml").failed!
37
+ end
38
+
39
+ travel 30.days
40
+
41
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
42
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class ActionMailbox::InboundEmail::MessageIdTest < ActiveSupport::TestCase
4
+ test "message id is extracted from raw email" do
5
+ inbound_email = create_inbound_email_from_fixture("welcome.eml")
6
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
7
+ end
8
+
9
+ test "message id is generated if its missing" do
10
+ 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!"
11
+ assert_not_nil inbound_email.message_id
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../test_helper'
2
+
3
+ module ActionMailbox
4
+ class InboundEmailTest < ActiveSupport::TestCase
5
+ test "mail provides the parsed source" do
6
+ assert_equal "Discussion: Let's debate these attachments", create_inbound_email_from_fixture("welcome.eml").mail.subject
7
+ end
8
+
9
+ test "source returns the contents of the raw email" do
10
+ assert_equal file_fixture("welcome.eml").read, create_inbound_email_from_fixture("welcome.eml").source
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module MailExt
4
+ class AddressEqualityTest < ActiveSupport::TestCase
5
+ test "two addresses with the same address are equal" do
6
+ assert_equal Mail::Address.new("david@basecamp.com"), Mail::Address.new("david@basecamp.com")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module MailExt
4
+ class AddressWrappingTest < ActiveSupport::TestCase
5
+ test "wrap" do
6
+ needing_wrapping = Mail::Address.wrap("david@basecamp.com")
7
+ wrapping_not_needed = Mail::Address.wrap(Mail::Address.new("david@basecamp.com"))
8
+ assert_equal needing_wrapping.address, wrapping_not_needed.address
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module MailExt
4
+ class RecipientsTest < ActiveSupport::TestCase
5
+ setup do
6
+ @mail = Mail.new \
7
+ to: "david@basecamp.com",
8
+ cc: "jason@basecamp.com",
9
+ bcc: "andrea@basecamp.com",
10
+ x_original_to: "ryan@basecamp.com"
11
+ end
12
+
13
+ test "recipients include everyone from to, cc, bcc, and x-original-to" do
14
+ assert_equal %w[ david@basecamp.com jason@basecamp.com andrea@basecamp.com ryan@basecamp.com ], @mail.recipients
15
+ end
16
+
17
+ test "recipients addresses use address objects" do
18
+ assert_equal "basecamp.com", @mail.recipients_addresses.first.domain
19
+ end
20
+
21
+ test "to addresses use address objects" do
22
+ assert_equal "basecamp.com", @mail.to_addresses.first.domain
23
+ end
24
+
25
+ test "cc addresses use address objects" do
26
+ assert_equal "basecamp.com", @mail.cc_addresses.first.domain
27
+ end
28
+
29
+ test "bcc addresses use address objects" do
30
+ assert_equal "basecamp.com", @mail.bcc_addresses.first.domain
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class BouncingWithReplyMailbox < ActionMailbox::Base
4
+ def process
5
+ bounce_with BounceMailer.bounce(to: mail.from)
6
+ end
7
+ end
8
+
9
+ class ActionMailbox::Base::BouncingTest < ActiveSupport::TestCase
10
+ include ActionMailer::TestHelper
11
+
12
+ setup do
13
+ @inbound_email = create_inbound_email_from_mail \
14
+ from: "sender@example.com", to: "replies@example.com", subject: "Bounce me"
15
+ end
16
+
17
+ test "bouncing with a reply" do
18
+ perform_enqueued_jobs only: ActionMailer::DeliveryJob do
19
+ BouncingWithReplyMailbox.receive @inbound_email
20
+ end
21
+
22
+ assert @inbound_email.bounced?
23
+ assert_emails 1
24
+
25
+ mail = ActionMailer::Base.deliveries.last
26
+ assert_equal %w[ sender@example.com ], mail.to
27
+ assert_equal "Your email was not delivered", mail.subject
28
+ end
29
+ end
@@ -0,0 +1,75 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class CallbackMailbox < ActionMailbox::Base
4
+ before_processing { $before_processing = "Ran that!" }
5
+ after_processing { $after_processing = "Ran that too!" }
6
+ around_processing ->(r, block) { block.call; $around_processing = "Ran that as well!" }
7
+
8
+ def process
9
+ $processed = mail.subject
10
+ end
11
+ end
12
+
13
+ class BouncingCallbackMailbox < ActionMailbox::Base
14
+ before_processing { $before_processing = [ "Pre-bounce" ] }
15
+
16
+ before_processing do
17
+ bounce_with BounceMailer.bounce(to: mail.from)
18
+ $before_processing << "Bounce"
19
+ end
20
+
21
+ before_processing { $before_processing << "Post-bounce" }
22
+
23
+ after_processing { $after_processing = true }
24
+
25
+ def process
26
+ $processed = true
27
+ end
28
+ end
29
+
30
+ class DiscardingCallbackMailbox < ActionMailbox::Base
31
+ before_processing { $before_processing = [ "Pre-discard" ] }
32
+
33
+ before_processing do
34
+ delivered!
35
+ $before_processing << "Discard"
36
+ end
37
+
38
+ before_processing { $before_processing << "Post-discard" }
39
+
40
+ after_processing { $after_processing = true }
41
+
42
+ def process
43
+ $processed = true
44
+ end
45
+ end
46
+
47
+ class ActionMailbox::Base::CallbacksTest < ActiveSupport::TestCase
48
+ setup do
49
+ $before_processing = $after_processing = $around_processing = $processed = false
50
+ @inbound_email = create_inbound_email_from_fixture("welcome.eml")
51
+ end
52
+
53
+ test "all callback types" do
54
+ CallbackMailbox.receive @inbound_email
55
+ assert_equal "Ran that!", $before_processing
56
+ assert_equal "Ran that too!", $after_processing
57
+ assert_equal "Ran that as well!", $around_processing
58
+ end
59
+
60
+ test "bouncing in a callback terminates processing" do
61
+ BouncingCallbackMailbox.receive @inbound_email
62
+ assert @inbound_email.bounced?
63
+ assert_equal [ "Pre-bounce", "Bounce" ], $before_processing
64
+ assert_not $processed
65
+ assert_not $after_processing
66
+ end
67
+
68
+ test "marking the inbound email as delivered in a callback terminates processing" do
69
+ DiscardingCallbackMailbox.receive @inbound_email
70
+ assert @inbound_email.delivered?
71
+ assert_equal [ "Pre-discard", "Discard" ], $before_processing
72
+ assert_not $processed
73
+ assert_not $after_processing
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class ApplicationMailbox < ActionMailbox::Base
4
+ routing "replies@example.com" => :replies
5
+ end
6
+
7
+ class RepliesMailbox < ActionMailbox::Base
8
+ def process
9
+ $processed = mail.subject
10
+ end
11
+ end
12
+
13
+ class ActionMailbox::Base::RoutingTest < ActiveSupport::TestCase
14
+ setup do
15
+ $processed = false
16
+ @inbound_email = create_inbound_email_from_fixture("welcome.eml")
17
+ end
18
+
19
+ test "string routing" do
20
+ ApplicationMailbox.route @inbound_email
21
+ assert_equal "Discussion: Let's debate these attachments", $processed
22
+ end
23
+
24
+ test "delayed routing" do
25
+ perform_enqueued_jobs only: ActionMailbox::RoutingJob do
26
+ create_inbound_email_from_fixture "welcome.eml", status: :pending
27
+ assert_equal "Discussion: Let's debate these attachments", $processed
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class SuccessfulMailbox < ActionMailbox::Base
4
+ def process
5
+ $processed = mail.subject
6
+ end
7
+ end
8
+
9
+ class UnsuccessfulMailbox < ActionMailbox::Base
10
+ rescue_from(RuntimeError) { $processed = :failure }
11
+
12
+ def process
13
+ raise "No way!"
14
+ end
15
+ end
16
+
17
+ class BouncingMailbox < ActionMailbox::Base
18
+ def process
19
+ $processed = :bounced
20
+ bounced!
21
+ end
22
+ end
23
+
24
+
25
+ class ActionMailbox::Base::StateTest < ActiveSupport::TestCase
26
+ setup do
27
+ $processed = false
28
+ @inbound_email = create_inbound_email_from_mail \
29
+ to: "replies@example.com", subject: "I was processed"
30
+ end
31
+
32
+ test "successful mailbox processing leaves inbound email in delivered state" do
33
+ SuccessfulMailbox.receive @inbound_email
34
+ assert @inbound_email.delivered?
35
+ assert_equal "I was processed", $processed
36
+ end
37
+
38
+ test "unsuccessful mailbox processing leaves inbound email in failed state" do
39
+ UnsuccessfulMailbox.receive @inbound_email
40
+ assert @inbound_email.failed?
41
+ assert_equal :failure, $processed
42
+ end
43
+
44
+ test "bounced inbound emails are not delivered" do
45
+ BouncingMailbox.receive @inbound_email
46
+ assert @inbound_email.bounced?
47
+ assert_equal :bounced, $processed
48
+ end
49
+ end
@@ -0,0 +1,90 @@
1
+ require_relative '../test_helper'
2
+
3
+ require 'action_mailbox/postfix_relayer'
4
+
5
+ module ActionMailbox
6
+ class PostfixRelayerTest < ActiveSupport::TestCase
7
+ URL = "https://example.com/rails/action_mailbox/postfix/inbound_emails"
8
+ INGRESS_PASSWORD = "secret"
9
+
10
+ setup do
11
+ @relayer = ActionMailbox::PostfixRelayer.new(url: URL, password: INGRESS_PASSWORD)
12
+ end
13
+
14
+ test "successfully relaying an email" do
15
+ stub_request(:post, URL).to_return status: 204
16
+
17
+ result = @relayer.relay(file_fixture("welcome.eml").read)
18
+ assert_equal "2.0.0 Successfully relayed message to Postfix ingress", result.output
19
+ assert result.success?
20
+ assert_not result.failure?
21
+
22
+ assert_requested :post, URL, body: file_fixture("welcome.eml").read,
23
+ basic_auth: [ "actionmailbox", INGRESS_PASSWORD ],
24
+ headers: { "Content-Type" => "message/rfc822", "User-Agent" => /\AAction Mailbox Postfix relayer v\d+\./ }
25
+ end
26
+
27
+ test "unsuccessfully relaying with invalid credentials" do
28
+ stub_request(:post, URL).to_return status: 401
29
+
30
+ result = @relayer.relay(file_fixture("welcome.eml").read)
31
+ assert_equal "4.7.0 Invalid credentials for Postfix ingress", result.output
32
+ assert_not result.success?
33
+ assert result.failure?
34
+ end
35
+
36
+ test "unsuccessfully relaying due to an unspecified server error" do
37
+ stub_request(:post, URL).to_return status: 500
38
+
39
+ result = @relayer.relay(file_fixture("welcome.eml").read)
40
+ assert_equal "4.0.0 HTTP 500", result.output
41
+ assert_not result.success?
42
+ assert result.failure?
43
+ end
44
+
45
+ test "unsuccessfully relaying due to a gateway timeout" do
46
+ stub_request(:post, URL).to_return status: 504
47
+
48
+ result = @relayer.relay(file_fixture("welcome.eml").read)
49
+ assert_equal "4.0.0 HTTP 504", result.output
50
+ assert_not result.success?
51
+ assert result.failure?
52
+ end
53
+
54
+ test "unsuccessfully relaying due to ECONNRESET" do
55
+ stub_request(:post, URL).to_raise Errno::ECONNRESET.new
56
+
57
+ result = @relayer.relay(file_fixture("welcome.eml").read)
58
+ assert_equal "4.4.2 Network error relaying to Postfix ingress: Connection reset by peer", result.output
59
+ assert_not result.success?
60
+ assert result.failure?
61
+ end
62
+
63
+ test "unsuccessfully relaying due to connection failure" do
64
+ stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443")
65
+
66
+ result = @relayer.relay(file_fixture("welcome.eml").read)
67
+ assert_equal "4.4.2 Network error relaying to Postfix ingress: Failed to open TCP connection to example.com:443", result.output
68
+ assert_not result.success?
69
+ assert result.failure?
70
+ end
71
+
72
+ test "unsuccessfully relaying due to client-side timeout" do
73
+ stub_request(:post, URL).to_timeout
74
+
75
+ result = @relayer.relay(file_fixture("welcome.eml").read)
76
+ assert_equal "4.4.2 Timed out relaying to Postfix ingress", result.output
77
+ assert_not result.success?
78
+ assert result.failure?
79
+ end
80
+
81
+ test "unsuccessfully relaying due to an unhandled exception" do
82
+ stub_request(:post, URL).to_raise StandardError.new("Something went wrong")
83
+
84
+ result = @relayer.relay(file_fixture("welcome.eml").read)
85
+ assert_equal "4.0.0 Error relaying to Postfix ingress: Something went wrong", result.output
86
+ assert_not result.success?
87
+ assert result.failure?
88
+ end
89
+ end
90
+ end