Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions app/mailers/volunteer_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@ def case_contacts_reminder(user, cc_recipients)
@casa_organization = user.casa_org
mail(to: @user.email, cc: cc_recipients, subject: "Reminder to input case contacts")
end

def reimbursement_complete_email(user, case_contact)
@user = user
@case_contact = case_contact
@casa_organization = @user.casa_org
mail(to: @user.email, subject: "Your reimbursement request has been processed")
end
end
16 changes: 10 additions & 6 deletions app/notifications/reimbursement_complete_notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
# ReimbursementCompleteNotifier.with(case_contact: @case_contact).deliver(current_user)
#
class ReimbursementCompleteNotifier < BaseNotifier
# deliver_by :email do |config|
# config.mailer = "UserMailer"
# ...
# end
# deliver_by :slack
# deliver_by :custom, class: "MyDeliveryMethod"
# This notifier is delivered to both the volunteer who submitted the
# reimbursement and their supervisor (see ReimbursementsController#change_complete_status).
# The email delivery is scoped to volunteer recipients only, since the
# supervisor already knows they just marked it complete.
deliver_by :email do |config|
config.mailer = "VolunteerMailer"
config.method = "reimbursement_complete_email"
config.args = -> { [recipient, params[:case_contact]] }
config.if = -> { recipient.volunteer? && recipient.receive_email_notifications? }
end

required_param :case_contact

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<meta itemprop="name" content="Reimbursement Processed" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<style>/* Email styles need to be inline */</style>
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<tr style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
Hello <%= @user.display_name %>,
</td>
</tr>
<tr style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
Your reimbursement request for <%= @case_contact.miles_driven %>mi
<% if @case_contact.reimbursement_amount %>
(<%= number_to_currency(@case_contact.reimbursement_amount) %>)
<% end %>
on <%= @case_contact.occurred_at_display %> has been processed and is en route.
</td>
</tr>
<tr style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
You can view your case contacts and their reimbursement status by visiting
<a href="<%= "#{case_contacts_url(casa_case_id: @case_contact.casa_case_id)}" %>" target="_blank">this link</a>.
</td>
</tr>
<tr style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
If you have any questions, please contact your most recent CASA supervisor for assistance.
</td>
</tr>
</table>
13 changes: 13 additions & 0 deletions lib/mailers/debug_preview_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,17 @@ def invalid_user(role)
body: "User does not exist or is not a #{role}"
)
end

# Use when the looked-up user is valid but has no related record to preview
# with, e.g. a volunteer with no case contact wanting reimbursement. Keeps
# the "missing_#{role}@example.com" convention so existing preview specs
# asserting on that address don't need to change.
def no_data(role)
mail(
from: "reply@example.com",
to: "missing_#{role}@example.com",
subject: "no_preview_data",
body: "No #{role} record was found to preview"
)
end
end
18 changes: 18 additions & 0 deletions lib/mailers/previews/volunteer_mailer_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,23 @@ def case_contacts_reminder
VolunteerMailer.court_report_reminder(volunteer, true)
end
end

# Unlike the other previews above, a valid volunteer here isn't enough on
# its own, they also need a case contact wanting reimbursement to render
# against. If they don't have one, DebugPreviewMailer.no_data renders that
# distinctly from invalid_user, which is reserved for a failed ID lookup.
def reimbursement_complete_email
volunteer = params.has_key?(:id) ? Volunteer.find_by(id: params[:id]) : Volunteer.last
if volunteer.nil?
DebugPreviewMailer.invalid_user("volunteer")
else
case_contact = CaseContact.where(creator: volunteer, want_driving_reimbursement: true).last
if case_contact.nil?
DebugPreviewMailer.no_data("case_contact")
else
VolunteerMailer.reimbursement_complete_email(volunteer, case_contact)
end
end
end
end
# :nocov:
35 changes: 35 additions & 0 deletions spec/mailers/previews/volunteer_mailer_preview_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,39 @@
it { expect(email.to).to eq ["missing_volunteer@example.com"] }
end
end

describe "#reimbursement_complete_email" do
let!(:case_contact) { create(:case_contact, :wants_reimbursement, creator: volunteer) }

context "When no ID is passed" do
let(:preview) { described_class.new }
let(:email) { preview.reimbursement_complete_email }

it { expect(email.to).to eq [volunteer.email] }
end

context "When passed ID is valid" do
let(:preview) { described_class.new(id: volunteer.id) }
let(:email) { preview.reimbursement_complete_email }

it { expect(email.to).to eq [volunteer.email] }
end

context "When passed ID is invalid" do
let(:preview) { described_class.new(id: -1) }
let(:email) { preview.reimbursement_complete_email }

it { expect(email.to).to eq ["missing_volunteer@example.com"] }
end

context "When the volunteer has no case contact wanting reimbursement" do
let(:other_volunteer) { create(:volunteer) }
let(:preview) { described_class.new(id: other_volunteer.id) }
let(:email) { preview.reimbursement_complete_email }

it "does not fall back to a different volunteer's case contact" do
expect(email.to).to eq ["missing_case_contact@example.com"]
end
end
end
end
20 changes: 20 additions & 0 deletions spec/mailers/volunteer_mailer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@
end
end

describe ".reimbursement_complete_email" do
let(:case_contact) { create(:case_contact, :wants_reimbursement, creator: volunteer) }
let(:mail) { VolunteerMailer.reimbursement_complete_email(volunteer, case_contact) }

it "sends an email confirming the reimbursement was processed" do
expect(mail.to).to eq([volunteer.email])
expect(mail.subject).to eq("Your reimbursement request has been processed")
expect(mail.body.encoded).to match("#{case_contact.miles_driven}mi")
expect(mail.body.encoded).to match(case_contact.occurred_at_display)
end

context "when the casa org has a mileage rate" do
let!(:mileage_rate) { create(:mileage_rate, casa_org: case_contact.casa_case.casa_org, amount: 6.50, effective_date: 3.days.ago) }

it "includes the reimbursement amount" do
expect(mail.body.encoded).to match("$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is going to catch any string consider $ is a regex end-anchor. Think you may need to use something like match(/$[\d,]+.\d{2}/)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@Jberma23 Fixed both, thanks for catching these, and here's what changed for a brief summary (although please do check this further):
Almost used your regex as is, just had to escape $ and . as well, otherwise those are the end-of-line anchor and any-character wildcard, not literal characters. And the test now uses: /$[\d,]+.\d{2}/. 👍

Controller now guards the notifier call with ActiveModel::Type::Boolean.new.cast(reimbursement_complete), so unchecking no longer fires it. Worth noting: that bug predates this PR, was harmless before as an in-app-only notification, but our email delivery is what made it consequential. This PR successfully both exposed and fixed it, specially thanks to your call out!

Added tests covering both directions and value formats, plus the no-mileage-rate case.

end
end
end

describe ".invitation_instructions for a volunteer" do
let(:mail) { volunteer.invite! }
let(:expiration_date) { I18n.l(volunteer.invitation_due_at, format: :full, default: nil) }
Expand Down
36 changes: 36 additions & 0 deletions spec/notifications/reimbursement_complete_notifier_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require "rails_helper"

RSpec.describe ReimbursementCompleteNotifier, type: :model do
include ActiveJob::TestHelper

describe "title" do
it "returns 'Reimbursement Approved'" do
case_contact = build(:case_contact, :wants_reimbursement)
Expand Down Expand Up @@ -31,6 +33,40 @@
end
end

describe "email delivery" do
let(:volunteer) { create(:volunteer, receive_email_notifications: true) }
let(:supervisor) { create(:supervisor, receive_email_notifications: true) }
let(:case_contact) { create(:case_contact, :wants_reimbursement, creator: volunteer) }

it "emails the volunteer recipient" do
perform_enqueued_jobs do
ReimbursementCompleteNotifier.with(case_contact: case_contact).deliver(volunteer)
end

expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(volunteer.email)
end

it "does not email a volunteer who has opted out of email notifications" do
# UserValidator requires at least one communication channel enabled, so
# disabling email requires enabling SMS, which in turn requires a phone number.
volunteer.update!(receive_email_notifications: false, receive_sms_notifications: true, phone_number: "5555555555")

perform_enqueued_jobs do
ReimbursementCompleteNotifier.with(case_contact: case_contact).deliver(volunteer)
end

expect(ActionMailer::Base.deliveries.map(&:to).flatten).not_to include(volunteer.email)
end

it "does not email a supervisor recipient" do
perform_enqueued_jobs do
ReimbursementCompleteNotifier.with(case_contact: case_contact).deliver(supervisor)
end

expect(ActionMailer::Base.deliveries.map(&:to).flatten).not_to include(supervisor.email)
end
end

describe "url" do
it "returns the case contacts URL path for the given case contact" do
case_contact = create(:case_contact, :wants_reimbursement)
Expand Down
Loading