Skip to content

Commit

Permalink
Update the Mailer
Browse files Browse the repository at this point in the history
The Mailer is what is used to provide applcations with Notify emails.

This class is used as the parent for new subclasses that provide the
application specific emails.

Like a module, we have to test this by creating a new TestMailer that
inherits from our updated class.

The main thing to notice here is that we have to prevent Rails from
carrying out the default behaviour of looking for views to provide the
parts of the email (the html and plain text content), we do so by
calling `mail` with a block that sets the parts to empty object - these
are then replaced later with the actual content.
  • Loading branch information
mec committed Apr 22, 2024
1 parent a80a1ef commit 22cb87b
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 64 deletions.
90 changes: 84 additions & 6 deletions lib/mail/notify/mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,97 @@

module Mail
module Notify
##
# The Mail Notify base Mailer class, overridden in Rails applications to provide the additional
# Notify behaviour along with the application behaviour.

class Mailer < ActionMailer::Base
def view_mail(template_id, headers)
raise ArgumentError, "You must specify a template ID" if template_id.blank?
##
# Set a default from address, will only be used in previews if a from address is not supplied
# by subclasses

default from: "preview@notifications.service.gov.uk"

##
# Send an email where the content is managed in the Notify template.
#
# The required arguments are:
#
# - template_id
# - to address
#
# Can include personalisation.
#
# Add any additional headers in the options hash.
#
# A default subject is supplied as ActionMailer requires one, however it will never be used as
# the subject is assumed to be managed in the Notify template.

def template_mail(template_id, options)
raise ArgumentError, "You must specify a Notify template ID" if template_id.blank?
raise ArgumentError, "You must specify a to address" if options[:to].nil? || options[:to].blank?

message.template_id = template_id
message.reply_to_id = options[:reply_to_id]
message.reference = options[:reference]

message.personalisation = options[:personalisation] || {}

headers = options.except([:personalisation, :reply_to_id, :reference])

mail(headers.merge(template_id: template_id))
headers[:subject] = "Subject managed in Notify" unless options[:subject]

# We have to set the html and the plain text content to nil to prevent Rails from looking
# for the content in the views. We replace nil with the content returned from Notify before
# sending or previewing
mail(headers) do |format|
format.text { nil }
format.html { nil }
end
end

def template_mail(template_id, headers)
raise ArgumentError, "You must specify a template ID" if template_id.blank?
##
# Send an email where the content is managed in the Rails application.
#
# The required arguments are:
#
# - template_id
# - to address
# - subject
#
# Personalisation will dropped as all content comes from the view provided by Rails.
#
# Add any additional headers in the options hash.

def view_mail(template_id, options)
raise ArgumentError, "You must specify a Notify template ID" if template_id.blank?
raise ArgumentError, "You must supply a to address" if options[:to].blank?
raise ArgumentError, "You must specify a subject" if options[:subject].blank?

message.template_id = template_id
message.reply_to_id = options[:reply_to_id]
message.reference = options[:reference]

subject = options[:subject]
headers = options.except([:personalisation, :reply_to_id, :reference])

mail(headers.merge(body: "", subject: "", template_id: template_id))
# we have to render the view for the message and grab the raw source, then we set that as the
# body in the personalisation for sending to the Notify API.
body = mail(headers).body.raw_source

# The 'view mail' works by sending a subject and body as personalisation options, these are
# then used in the Notify template to provide content.
message.personalisation = {subject: subject, body: body}

mail(headers) do |format|
format.text { nil }
format.html { nil }
end
end

##
# allows blank personalisation options

def blank_allowed(value)
value.presence || Personalisation::BLANK
end
Expand Down
220 changes: 193 additions & 27 deletions spec/mail/notify/mailer_spec.rb
Original file line number Diff line number Diff line change
@@ -1,53 +1,219 @@
# frozen_string_literal: true

require "spec_helper"
require "mailers/test_mailer"

RSpec.describe Mail::Notify::Mailer do
let(:mailer) { Mail::Notify::Mailer.new }
describe "#view_mail" do
it "sets the message template id" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "Test subject"}

context "with a view" do
it "sends the template" do
expect(mailer).to receive(:mail).with({template_id: "foo", bar: "baz"})
mailer.view_mail("foo", bar: "baz")
message = TestMailer.with(message_params).test_view_mail

expect(message.template_id).to eql("template-id")
end

it "sets the message subject" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect(message.header[:subject]).to be_a Mail::Field
expect(message.header[:subject].value).to eql("Test subject")
end

it "raises an error if the template ID is blank" do
expect { mailer.view_mail("", bar: "baz") }.to raise_error(
ArgumentError,
"You must specify a template ID"
it "sets the message to address" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect(message.header[:to]).to be_a Mail::Field
expect(message.header[:to].value).to eql("test.name@email.co.uk")
end

it "sets the subject on personalisation" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect(message.personalisation[:subject]).to eql("Test subject")
end

it "sets the body on personalisation" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect(message.personalisation[:body]).to eql("This is the body from the mailers view.\r\n")
end

it "drops any further personalisation" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
subject: "Test subject",
personalisation: {name: "Test Name"}
}

message = TestMailer.with(message_params).test_view_mail

expect(message.personalisation.key?(:name)).to be false
end

it "sets the reply_to_id" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
subject: "Test subject",
reply_to_id: "test-reply-to-id"
}

message = TestMailer.with(message_params).test_view_mail

expect(message.reply_to_id).to eql("test-reply-to-id")
end

it "sets the reference" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
subject: "Test subject",
reference: "test-reference"
}

message = TestMailer.with(message_params).test_view_mail

expect(message.reference).to eql("test-reference")
end

it "requires template id to be set" do
message_params = {to: "test.name@email.co.uk", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect { message.template_id }.to raise_error(
ArgumentError, "You must specify a Notify template ID"
)
end

it "requires to address to be set" do
message_params = {template_id: "template-id", subject: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect { message.header[:to] }.to raise_error(
ArgumentError, "You must supply a to address"
)
end
end

context "with a template" do
it "sends a blank body and subject" do
expect(mailer).to receive(:mail).with({
template_id: "foo",
bar: "baz",
body: "",
subject: ""
})
mailer.template_mail("foo", bar: "baz")
end

it "raises an error if the template ID is blank" do
expect { mailer.template_mail("", bar: "baz") }.to raise_error(
ArgumentError,
"You must specify a template ID"
it "requires subject to be set" do
message_params = {template_id: "template-id", to: "Test subject"}

message = TestMailer.with(message_params).test_view_mail

expect { message.header[:subject] }.to raise_error(
ArgumentError, "You must specify a subject"
)
end
end

describe "#template_email" do
it "sets the message template id" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk"}

message = TestMailer.with(message_params).test_template_mail

expect(message.template_id).to eql("template-id")
end

it "sets the message to address" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk"}

message = TestMailer.with(message_params).test_template_mail

expect(message.header[:to]).to be_a Mail::Field
expect(message.header[:to].value).to eql("test.name@email.co.uk")
end

it "sets the subject as ActionMailer requires one" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk"}

message = TestMailer.with(message_params).test_template_mail

expect(message.header[:subject]).to be_a Mail::Field
expect(message.header[:subject].value).to eql("Subject managed in Notify")
end

it "sets the subject if one is passed, even though it will not be used" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk", subject: "My subject"}

message = TestMailer.with(message_params).test_template_mail

expect(message.header[:subject]).to be_a Mail::Field
expect(message.header[:subject].value).to eql("My subject")
end

context "without personalisation" do
it "sets the personalisation of the message to an empty hash" do
message_params = {template_id: "template-id", to: "test.name@email.co.uk"}

message = TestMailer.with(message_params).test_template_mail

expect(message.personalisation).to eql({})
end
end

context "with personalisation" do
it "sets the personalisation of the message" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
personalisation: {name: "Name", age: "25"}
}

message = TestMailer.with(message_params).test_template_mail

expect(message.personalisation).to eql({name: "Name", age: "25"})
end
end

it "sets the reply_to_id" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
subject: "Test subject",
reply_to_id: "test-reply-to-id"
}

message = TestMailer.with(message_params).test_template_mail

expect(message.reply_to_id).to eql("test-reply-to-id")
end

it "sets the reference" do
message_params = {
template_id: "template-id",
to: "test.name@email.co.uk",
subject: "Test subject",
reference: "test-reference"
}

message = TestMailer.with(message_params).test_template_mail

expect(message.reference).to eql("test-reference")
end
end

describe "#blank_allowed" do
context "when given a non-blank value" do
it "returns the given value" do
expect(mailer.blank_allowed("foo")).to eq "foo"
expect(TestMailer.new.blank_allowed("foo")).to eq "foo"
end
end

context "when given a blank value" do
it "returns explicitly blank personalisation" do
expect(mailer.blank_allowed("")).to be Mail::Notify::Personalisation::BLANK
expect(TestMailer.new.blank_allowed("")).to be Mail::Notify::Personalisation::BLANK
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions spec/mailers/test_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class TestMailer < Mail::Notify::Mailer
def test_view_mail
template_id = params[:template_id]
options = params.except(:template_id)

view_mail(template_id, options)
end

def test_template_mail
template_id = params[:template_id]
options = params.except(:template_id)

template_mail(template_id, options)
end
end
3 changes: 2 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
SimpleCov.start

require "bundler/setup"
require "action_controller"
require "action_mailer"
require "pry"
require "webmock/rspec"

require "mail/notify"
require "support/test_mailer"

# we have to trick ActionMailer into looking for our test email view templates
ActionMailer::Base.view_paths = File.join(File.dirname(__FILE__), "support", "templates")

RSpec.configure do |config|
Expand Down
3 changes: 0 additions & 3 deletions spec/support/templates/test_mailer/my_mail.text.erb

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions spec/support/templates/test_mailer/test_view_mail.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is the body from the mailers view.

0 comments on commit 22cb87b

Please sign in to comment.