Rspec tests failing when using amazon ses mail sending - ruby-on-rails

I recently switched over a rails app to send mail through amazon ses. The sending of the mail works fine, however the tests are failing around the mail items with the following error:
TypeError:
can't convert Fixnum into String
# (eval):3:in 'send_raw_email'
I have the test environment mail settings set properly to :test
config.action_mailer.delivery_method = :test
I can prevent the errors from happening by not sending emails, but then I get errors about the mail not being delivered.
config.action_mailer.perform_deliveries = false
should_change -> { ActionMailer::Base.deliveries.size } do
result should have changed, but is still 0
Gem versions are below:
rails (3.2.12)
rspec (2.8.0)
cucumber (2.3.3)
aws-sdk (1.33.0)
Any suggestions on what is going on, or how to work around this. If any other info is needed let me know. This worked fine before I switched the sending of mail to use amazon ses.
EDIT:
The tests that are failing, are all that involve an email being sent using .deliver
BlahMailer.send_invite(team).deliver
This is inside a model and controller which the tests just call the associated route in the controller or call the associated function in the model. Not sure what else to show, since the error is showing its coming from send_raw_email which is the ses function, not any code from the app.
EDIT 2: More code
Here is one of the tests, in this example it sets up the factory for the model and each test.
before :each do
subject = Factory(:ticket, action_type: 'name_change', team_name: 'test123')
end
Here is the factory in rspec
Factory.define :ticket do |ticket|
team = nil
ticket.team { team = Factory(:team) }
ticket.creator { team.captain }
ticket.subject { Factory(:team_assignment, team: team, user: Factory(:active_user)).user }
end
And then here is the code in the model around where the email is sent
%w(requested approved cancelled accepted).each do |state|
define_method "notify_#{state}" do
TicketMailer.send("#{action_type}_#{state}", self).deliver
end
end
I have tried putting in straight text for basically everything having to do with the mailer, just to make sure there are no issues with that. If you need anymore of the model or the actual mailer let me know. I just really cant figure out where this is dieing. My only conclusion is its just a bug with the older versions of the aws sdk and rspec gems. I even tried replacing aws-sdk with newer versions but was still unsuccessful in getting anything else to work.

The problem is that you're setting config.action_mailer.perform_deliveries = false, so no deliveries are being made and therefore ActionMailer::Base.deliveries will always be an empty collection.
If you want to test email deliveries, then set config.action_mailer.perform_deliveries = true and use an interceptor to ensure you're not sending emails to users.
lass SandboxEmailInterceptor
def self.delivering_email(message)
recipients =
if Rails.env.test?
[
"user#example.com"
]
end
message.to = recipients
message.cc = []
end
end
unless Rails.env.production? || Rails.env.test?
ActionMailer::Base.register_interceptor(SandboxEmailInterceptor)
end

Related

How to avoid sending emails in Rails test-env with sendgrid-ruby?

Problem
I seem to be facing a stubborn issue with my RSpec tests trying to constantly send emails in test-env despite my configuration should avoid it. Whatever I try it seems to totally ignore it.
My environment
Rails 6.1.1
Ruby 3.0.0
sendgrid-ruby gem 6.3.9
I have a mailer class inheritance-chain as follows: OrganizationMailer<-ApplicationMailer<-ActionMailer::Base
In my config/environments/test.rb I have the following mail-related configuration
config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = false
config.active_job.queue_adapter = :test
My config/application.rb and config/environment.rb don't contain any extra configuration.
Bit offtopic maybe, but just in case adding it as well:
I have ensured that the line ENV['RAILS_ENV'] ||= 'test' is present in both- spec/spec_helper.rb and spec/rails_helper.rb since for some reason Rails randomly triggered my tests in staging environment. I also had to change the .rspec file contents in my project from --require rspec_helper to --require rails_helper. I got the solution from here1, here2 and here3. But this I don't think plays any role in current problem.
Bad solution
I hacked my way through this issue atm by just adding the unless Rails.env.test? on top of each email-sending method I'm having to ensure none of them reach Sendgrid in tests, but this sucks big time I know. That's why I'm posting this question to get this fixed properly without such if/unless-clauses.
My theory
Can it be that sendgrid-ruby is here to blame? I inherit things from ApplicationMailer and ActionMailer but eventually what is sending the emails is Sendgrid ruby gem. If so, how to avoid it best? I didn't find any hints from the sendgrid-ruby documentation about that. I will post one of my simple mailer-methods below so you could see the situation atm:
# frozen_string_literal: true
# using SendGrid's Ruby Library
# https://github.com/sendgrid/sendgrid-ruby
require 'sendgrid-ruby'
class MyMailer < ApplicationMailer
include SendGrid
def my_mailer_method(my_object:)
unless Rails.env.test? # <---- Hack I'd like to get rid of
from = Email.new(email: 'no-reply#my.domain', name: t('general.title'))
to = Email.new(email: my_object.contact_email)
subject = "#{t('my_mailer.my_mailer_method.subject')}: #{my_object.my_object_title}"
content = Content.new(
type: 'text/html',
value: ApplicationController.render(
template: 'my_mailer/my_mailer_method',
locals: {
my_object: my_object
},
layout: nil
)
)
mail = SendGrid::Mail.new(from, subject, to, content)
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
# Send out mail
response = sg.client.mail._('send').post(request_body: mail.to_json)
end
end
end
The issue here is that you may have set config.action_mailer.delivery_method = :test but you are not actually using ActionMailer to deliver your emails. Instead, within the MyMailer class you are directly using the SendGrid API, and sidestepping ActionMailer entirely.
If you want to use SendGrid API to send your emails, then I actually recommend using the sendgrid-actionmailer gem. It allows you to use ActionMailer to build your emails and uses the SendGrid API under the hood to send them. This allows you to send other parameters that the API supports and would be more difficult or impossible with SMTP, while still using the Rails standard ActionMailer to send the emails.
To ensure that your mails are sent by SendGrid in production, but not sent in test, you would set:
config.action_mailer.delivery_method = :sendgrid_actionmailer
in your production.rb environment. And set:
config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = false
as you already have in your test.rb environment.

Rails hooks - `deliver.action_mailer` not firing

I'm trying to add some logging / auditing to ActionMailer in a Rails app, and I'm trying to hook into the deliver.action_mailer hook in an initializer like so:
ActiveSupport::Notifications.subscribe 'deliver.action_mailer' do |_name, _start, _finish, _id, payload|
# Do stuff here
end
However, when I run my tests and run MyMailer.mailer_method.deliver! the hook never gets fired. Am I missing a step somewhere?
In the test environment the emails are not send, but they are stored in an array
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
You can write the test like:
subject { email.deliver_now }
it 'sends an e-mail' do
expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(1)
end

Prevent Rails from logging email attachments

When I send an email with an attachment the data is logged in hex and fills up my whole log. Is there a way to disable logging of attachments?
I know I can disable mailer logging with config.action_mailer.logger = nil.
Unfortunately, the attachments are included in the logs if the logging level is set to :debug, the default level for non-production environments. This means that in production you should be fine, but your dev and staging environments could bloat during testing. You could turn down logging for your entire app (config.log_level = :info), but this is obviously less than ideal.
You can configure a custom logger:
config.action_mailer.logger = ActiveSupport::BufferedLogger.new("mailer.log")
config.action_mailer.logger.level = ActiveSupport::BufferedLogger::Severity::INFO
Rails 4
config.action_mailer.logger = ActiveSupport::Logger.new("mailer.log")
config.action_mailer.logger.level = ActiveSupport::Logger::Severity::INFO
This will split the log, but you can isolate the logging level change to the action mailer.
If you still want your log level to be debug, you can remove the attachments from the log output by overriding ActionMailer's LogSubscriber class. Look at the class in your actionmailer gem, and adjust accordingly. For my Rails 4.2.10 install, the relevant file is:
gems/actionmailer-4.2.10/lib/action_mailer/log_subscriber.rb
My module is:
module ActionMailer
class LogSubscriber < ActiveSupport::LogSubscriber
def deliver(event)
info do
recipients = Array(event.payload[:to]).join(', ')
"\nSent mail to #{recipients}, Subject: #{event.payload[:subject]}, on #{event.payload[:date]} (#{event.duration.round(1)}ms)"
end
debug { remove_attachments(event.payload[:mail]) }
end
def remove_attachments(message)
new_message = ''
skip = false
message.each_line do |line|
new_message << line unless skip
skip = true if line =~ /Content-Disposition: attachment;/
skip = false if skip && line =~ /----==_mimepart/
end
new_message
end
end
end
Save this to an .rb file anywhere under your app/ folder and it will be included.
in Application.rb you could try filtering the attachment parameter. I believe this should solve the issue, but I have not tested it myself
config.filter_parameters += [:attachment]

Cucumber/Capybara/Email Spec not working correctly with Selenium Driver

I've running some Cucumber/Capybara tests. I've been using the email_spec gem to check email stuff. Some steps where of the kind 'And "someone#email.com" should receive an email'. They give no problem when I run the test using the rack_test driver. However, they fail when using the selenium driver:
And "someone#email.com" should receive an email
expected: 1
got: 0 (using ==) (RSpec::Expectations::ExpectationNotMetError)
Can you help me? Thanks
You have to put your emails into a file because by default rails in test environemnt saves them in static variable that cannot be accessed from test thread. If you're using rails3 set delivery method to :file in cucumber environment. If you're on rails 2.x put this into your cucumber initializer:
ActionMailer::Base.class_eval do
DELIVERIES_CACHE_PATH =
File.join(RAILS_ROOT,'tmp','cache',"action_mailer_cache_deliveries {ENV['TEST_ENV_NUMBER']}.cache")
def perform_delivery_cache(mail)
deliveries = File.open(DELIVERIES_CACHE_PATH, 'r') do |f|
Marshal.load(f)
end
deliveries << mail
File.open(DELIVERIES_CACHE_PATH,'w') { |f| Marshal.dump(deliveries, f) }
end
def self.cached_deliveries
File.open(DELIVERIES_CACHE_PATH,'r') { |f| Marshal.load(f) }
end
end
config.action_mailer.delivery_method = :cache

RSpec sends emails (not storing in ActionMailer::Base.deliveries) - dont know why?

Im using rspec and when i run rake spec, the user mailer sends email through smtp and not stores the email in the ActionMailer::Base.deliveries-array (invoked by an user-observer)...
Could you give me a hint why?
# Rails version
rails -v
=> Rails 3.0.1
# Ruby version with rvm (rvm version 1.0.16)
ruby -v
=> ruby 1.9.2p7 (2010-09-29 revision 29373) [x86_64-darwin10.4.0]
# Gemfile
gem "rspec-rails", ">= 2.0.1"
Config-files:
# config/environments/test.rb
MyApp::Application.configure do
config.action_mailer.delivery_method = :test
config.action_mailer.raise_delivery_errors = true
end
# spec/spec_helper.rb
...
ENV["RAILS_ENV"] ||= 'test'
...
# app/models/user_observer.rb
class UserObserver < ActiveRecord::Observer
observe User
def after_create(record)
puts Rails.env
# => "test"
UserMailer.new_user_notification(record).deliver
puts ActionMailer::Base.deliveries.inspect
# => []
# Sends it via smtp!
end
end
I'm so dumb... had "ActionMailer::Base.delivery_method = :smtp" in my setup_mail.rb-initializer... AAARGH....
To elaborate on the solution to this problem a little bit more:
In your config/enviroments/test.rb, by default you should have the line config.action_mailer.delivery_method = :test
What this does is tell ActionMailer not to send the email normally, but rather store the sent email in the array ActionMailer::Base.deliveries. This is helpful for testing because you can tell how many emails have been snt by using the inspect, count, length method on the ActionMailer::Base.deliveries array.
However, if you set the delivery method to something like config.action_mailer.delivery_method = :smtp, that could be overwriting your previous delivery_method = :test; therefore, your ActionMailer::Base.deliveries won't be populated.
I had done exactly this while using MailCatcher to view my emails being sent, thus causing my tests to fail, even though I was certain that emails were properly being sent!
So, make sure you're not setting a delivery_method other than :test in your test environment.
As a side note: If you're using Devise, you may want to check the Devise.mailer.deliveries array instead.
In my case I had
config.action_mailer.delivery_method = :test
and I also had
config.action_mailer.perform_deliveries = false
Which in effect (I suppose) doesn't send the email and also doesn't stuff it into ActionMailer::Base.deliveries array either.
Another possible gotcha...do not make the mistake, as I did, of including
ActionMailer::Base.delivery_method = :smtp
in config/environment.rb, which I incorrectly believed would be overridden by the more-specific config/environments/test.rb. Deleting the above line from config/environment.rb fixed the problem for me.
In my case, I forgot to .deliver method inside my spec.rb. It's weird, however, I get a STDOUT msg from Rails (Rails 5) of "Email delivered" even without it, which was quite confusing. Looking at your spec made me realize I forgot it. Thanks!

Resources