Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions spec/controllers/workshop_invitation_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@

before { login(member) }

# The invitation mailer sends multipart/mixed > multipart/alternative > text/html;
# the root body is empty, so read the nested html part.
def html_body(mail)
parts = mail.parts.flat_map { |part| part.multipart? ? part.parts : [part] }
parts.find { |part| part.content_type.match?('text/html') }&.body&.decoded
end

describe 'GET #show' do
it 'returns http success' do
get :show, params: { id: invitation.token }
Expand Down Expand Up @@ -192,16 +185,16 @@ def html_body(mail)
it 'emails the promoted member a confirmation they are attending' do
post :reject, params: { id: invitation.token }

mail = ActionMailer::Base.deliveries.find { |m| m.to.include?(waitlisted_member.email) }
mail = delivered_emails_to(waitlisted_member).last
expect(mail).not_to be_nil
expect(html_body(mail)).to include('been confirmed')
expect(email_html(mail)).to include('been confirmed')
end

it 'sends the promotion variant of the email (waiting-list flag set)' do
post :reject, params: { id: invitation.token }

mail = ActionMailer::Base.deliveries.find { |m| m.to.include?(waitlisted_member.email) }
expect(html_body(mail)).to include('A spot became available and your attendance has now been confirmed!')
expect(email_html(delivered_emails_to(waitlisted_member).last))
.to include('A spot became available and your attendance has now been confirmed!')
end

it 'does not email anyone else (the rejecting member gets no promotion copy)' do
Expand Down
25 changes: 25 additions & 0 deletions spec/shared_contexts/email_helpers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'rails_helper'

# Canary for EmailHelpers — same pattern as the 'with forgery protection
# enforced' canary. It delivers a real invitation email so a change to the
# mailer's part structure or to the test delivery path fails here instead of
# silently weakening every email assertion that uses the helper.
RSpec.describe EmailHelpers do
let(:member) { Fabricate(:member) }
let(:workshop) { Fabricate(:workshop) }
let(:invitation) { Fabricate(:workshop_invitation, workshop:, member:) }

it 'delivered_emails_to filters delivered mail by recipient' do
WorkshopInvitationMailer.attending(workshop, member, invitation, false).deliver_now

expect(delivered_emails_to(member)).not_to be_empty
expect(delivered_emails_to('nobody@example.com')).to be_empty
end

it 'email_html reads the waiting-list html copy from the nested part' do
WorkshopInvitationMailer.attending(workshop, member, invitation, true).deliver_now

expect(email_html(delivered_emails_to(member).last))
.to include('A spot became available and your attendance has now been confirmed!')
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
RSpec.configure do |config|
config.include ActiveJob::TestHelper
config.include ApplicationHelper
config.include EmailHelpers
config.include LoginHelpers
config.include ActiveSupport::Testing::TimeHelpers
config.include SelectFromChosen, type: :feature
Expand Down
18 changes: 18 additions & 0 deletions spec/support/helpers/email_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module EmailHelpers
# Mails the test-delivery path collected for a recipient (a Member or an
# address). The test queue adapter is delayed_job with Delayed::Worker.delay_jobs
# set to false in test, so deliver_later delivers immediately and mail lands in
# ActionMailer::Base.deliveries.
def delivered_emails_to(recipient)
address = recipient.respond_to?(:email) ? recipient.email : recipient
ActionMailer::Base.deliveries.select { |mail| mail.to.include?(address) }
end

# Decoded text/html body of a delivered Mail::Message. The invitation mailers
# send multipart/mixed > multipart/alternative > text/html, so the root
# mail.body.decoded is empty and the html part must be read directly.
def email_html(mail)
parts = mail.parts.flat_map { |part| part.multipart? ? part.parts : [part] }
parts.find { |part| part.content_type.match?('text/html') }&.body&.decoded
end
end
Loading