Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mailer_helper update #11665

Merged
merged 3 commits into from
Dec 19, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
expect_delivered_email(
to: [user.confirmed_email_addresses.first.email],
subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
body: ['<table class="button expanded large radius">', 'localhost:3000'],
body: [
'http://www.example.com/redirect/return_to_sp/account_verified_cta',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out body: is only comparing against the plaintext version of the email, so checking for HTML here fails.

Previously, we weren't correctly processing these body checks, so this was erroneously passing prior to this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sometimes wonder how many tests erroneously pass and conceal bugs. 😬 Glad you found this.

],
)
end
end
Expand All @@ -50,7 +52,9 @@
described_class.call(profile: profile)

email_body = last_email.text_part.decoded.squish
expect(email_body).to_not include('<table class="button expanded large radius">')
expect(email_body).to_not include(
'http://www.example.com/redirect/return_to_sp/account_verified_cta',
)
end
end

Expand All @@ -69,7 +73,7 @@
expect_delivered_email(
to: [user.confirmed_email_addresses.first.email],
subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
body: ['<table class="button expanded large radius">', 'http://example.com'],
body: ['http://example.com'],
)
end
end
Expand Down
104 changes: 92 additions & 12 deletions spec/support/mailer_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,106 @@ def strip_tags(str)
ActionController::Base.helpers.strip_tags(str)
end

# @param [String,String[],nil] to The email address(es) the message must've been sent to.
# @param [String,nil] subject The subject the email must've had
# @param [String[],nil] Array of substrings that must appear in body.
def expect_delivered_email(to: nil, subject: nil, body: nil)
email = ActionMailer::Base.deliveries.find do |sent_mail|
next unless to.present? && sent_mail.to == to
next unless subject.present? && sent_mail.subject == subject
if body.present?
delivered_body = sent_mail.text_part.decoded.squish
body.to_a.each do |expected_body|
next unless delivered_body.include?(expected_body)
end
end
true
end
email = find_sent_email(to:, subject:, body:)

error_message = <<~ERROR
Unable to find email matching args:
to: #{to}
subject: #{subject}
body: #{body}
Sent mails: #{ActionMailer::Base.deliveries}
Sent mails:
#{summarize_all_deliveries(to:, subject:, body:).indent(2)}
ERROR

expect(email).to_not be(nil), error_message
end

# @param [String,nil] to If provided, the email address the message must've been sent to
# @param [String,nil] subject If provided, the subject the email must've had
# @param [String[],nil] Array of substrings that must appear in body.
def expect_email_not_delivered(to: nil, subject: nil, body: nil)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I would like to rework this as an Rspec matcher, something like

expect { some_code }.to have_delivered_email(...)

But I needed to limit scope a little here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you on both counts—that would be nice, but this is a good step forward for now.

email = find_sent_email(to:, subject:, body:)

error_message = <<~ERROR
Found an email matching the below (but shouldn't have):
to: #{to}
subject: #{subject}
body: #{body}
Sent mails:
#{summarize_all_deliveries(to:, subject:, body:).indent(2)}
ERROR

expect(email).to be(nil), error_message
end

private

def body_matches(email:, body:)
return true if body.nil?

delivered_body = email.text_part.decoded.squish

Array.wrap(body).all? do |expected_substring|
delivered_body.include?(expected_substring)
end
end

def to_matches(email:, to:)
return true if to.nil?

to = Array.wrap(to).to_set

(email.to.to_set - to).empty?
end

def find_sent_email(
to:,
subject:,
body:
)
ActionMailer::Base.deliveries.find do |email|
to_ok = to_matches(email:, to:)
subject_ok = subject.nil? || email.subject == subject
body_ok = body_matches(email:, body:)

to_ok && subject_ok && body_ok
end
end

def summarize_delivery(
email:,
to:,
subject:,
body:
)
body_text = email.text_part.decoded.squish

body_summary = body.presence && Array.wrap(body).map do |substring|
found = body_text.include?(substring)
"- #{substring.inspect} (#{found ? 'found' : 'not found'})"
end.join("\n")

to_ok = to_matches(email:, to:)
subject_ok = subject.nil? || subject == email.subject

[
"To: #{email.to}#{to_ok ? '' : ' (did not match)'}",
"Subject: #{email.subject}#{subject_ok ? '' : ' (did not match)'}",
body.presence && "Body:\n#{body_summary.indent(2)}",
].compact.join("\n")
end

def summarize_all_deliveries(query)
ActionMailer::Base.deliveries.map do |email|
summary = summarize_delivery(email:, **query)
[
"- #{summary.lines.first.chomp}",
*summary.lines.drop(1).map { |l| l.chomp.indent(2) },
].join("\n")
end.join("\n")
end
end
Loading