From 092612ff5e64e2a78d01864ec854e5883d95b63a Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 3 Dec 2024 12:47:15 -0800 Subject: [PATCH 1/8] Rename in_person_please_call user_mailer method - Change to idv_please_call to indicate it is used by idv generally - Keep in_person_please_call as an alias on the theory that we need it for 1 deploy [skip changelog] --- app/mailers/user_mailer.rb | 29 +++++++++---------- ...call.html.erb => idv_please_call.html.erb} | 6 ++-- config/locales/en.yml | 8 ++--- config/locales/es.yml | 8 ++--- config/locales/fr.yml | 8 ++--- config/locales/zh.yml | 8 ++--- spec/mailers/previews/user_mailer_preview.rb | 7 ++--- .../previews/user_mailer_preview_spec.rb | 2 +- spec/mailers/user_mailer_spec.rb | 25 ++++++++++++++++ .../support/shared_examples/mailer_preview.rb | 4 +-- 10 files changed, 63 insertions(+), 42 deletions(-) rename app/views/user_mailer/{in_person_please_call.html.erb => idv_please_call.html.erb} (69%) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 32678fad843..46e480dab2a 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -248,6 +248,20 @@ def account_verified(profile:) end end + def idv_please_call(**) + with_user_locale(user) do + @hide_title = true + + mail( + to: email_address.email, + subject: t('user_mailer.idv_please_call.subject', app_name: APP_NAME), + template_name: 'idv_please_call', + ) + end + end + + alias_method :in_person_please_call, :idv_please_call + def in_person_completion_survey with_user_locale(user) do @header = t('user_mailer.in_person_completion_survey.header') @@ -373,21 +387,6 @@ def in_person_failed_fraud(enrollment:, visited_location_name: nil) end end - def in_person_please_call(enrollment:, visited_location_name: nil) - with_user_locale(user) do - @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( - enrollment: enrollment, - url_options: url_options, - visited_location_name: visited_location_name, - ) - @hide_title = true - mail( - to: email_address.email, - subject: t('user_mailer.in_person_please_call.subject', app_name: APP_NAME), - ) - end - end - def account_rejected with_user_locale(user) do mail( diff --git a/app/views/user_mailer/in_person_please_call.html.erb b/app/views/user_mailer/idv_please_call.html.erb similarity index 69% rename from app/views/user_mailer/in_person_please_call.html.erb rename to app/views/user_mailer/idv_please_call.html.erb index f8cef7a4ae8..54f4a89ba9f 100644 --- a/app/views/user_mailer/in_person_please_call.html.erb +++ b/app/views/user_mailer/idv_please_call.html.erb @@ -4,16 +4,16 @@ width: 88, height: 88, ) %> -

<%= t('user_mailer.in_person_please_call.header') %>

+

<%= t('user_mailer.idv_please_call.header') %>

<%= t( - 'user_mailer.in_person_please_call.body.intro_html', + 'user_mailer.idv_please_call.body.intro_html', date: I18n.l(14.days.from_now, format: I18n.t('time.formats.full_date')), ) %>

<%= t( - 'user_mailer.in_person_please_call.body.contact_message_html', + 'user_mailer.idv_please_call.body.contact_message_html', contact_number: IdentityConfig.store.idv_contact_phone_number, support_code: IdentityConfig.store.lexisnexis_threatmetrix_support_code, ) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 89b9f965ec0..a999cd2a1cd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1875,6 +1875,10 @@ user_mailer.email_deleted.header: An email address was deleted from your %{app_n user_mailer.email_deleted.help_html: If you did not want to delete this email address, please visit the %{app_name_html} %{help_link_html} or %{contact_link_html}. user_mailer.email_deleted.subject: Email address deleted user_mailer.help_link_text: Help Center +user_mailer.idv_please_call.body.contact_message_html: Call %{contact_number} and provide them with the error code %{support_code}. +user_mailer.idv_please_call.body.intro_html: Call our contact center by %{date} to continue verifying your identity. +user_mailer.idv_please_call.header: Please give us a call +user_mailer.idv_please_call.subject: Call %{app_name} to continue with your identity verification user_mailer.in_person_completion_survey.body.cta.callout: Click the button below to get started. user_mailer.in_person_completion_survey.body.cta.label: Take our survey user_mailer.in_person_completion_survey.body.greeting: Hello, @@ -1900,10 +1904,6 @@ user_mailer.in_person_failed.intro: Your identity could not be verified at the % user_mailer.in_person_failed.subject: Your identity could not be verified in person user_mailer.in_person_failed.verifying_identity: 'When verifying your identity:' user_mailer.in_person_failed.verifying_step_not_expired: Your state‑issued ID or driver’s license must not be expired. We do not currently accept any other forms of identification, such as passports and military IDs. -user_mailer.in_person_please_call.body.contact_message_html: Call %{contact_number} and provide them with the error code %{support_code}. -user_mailer.in_person_please_call.body.intro_html: Call our contact center by %{date} to continue verifying your identity. -user_mailer.in_person_please_call.header: Please give us a call -user_mailer.in_person_please_call.subject: Call %{app_name} to continue with your identity verification user_mailer.in_person_ready_to_verify_reminder.greeting: Hello, user_mailer.in_person_ready_to_verify_reminder.heading.one: You have %{count} day left to verify your identity in person user_mailer.in_person_ready_to_verify_reminder.heading.other: You have %{count} days left to verify your identity in person diff --git a/config/locales/es.yml b/config/locales/es.yml index e790124af69..57b1e9bf60f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1887,6 +1887,10 @@ user_mailer.email_deleted.header: Se eliminó una dirección de correo electrón user_mailer.email_deleted.help_html: Si no deseaba eliminar esta dirección de correo electrónico, visite %{help_link_html} de %{app_name_html} o %{contact_link_html}. user_mailer.email_deleted.subject: Dirección de correo electrónico eliminada user_mailer.help_link_text: Centro de ayuda +user_mailer.idv_please_call.body.contact_message_html: Llame al %{contact_number} y proporcione el código de error %{support_code}. +user_mailer.idv_please_call.body.intro_html: Llame a nuestro centro de contacto antes del %{date} para seguir verificando su identidad. +user_mailer.idv_please_call.header: Llámenos +user_mailer.idv_please_call.subject: Llame a %{app_name} para continuar con la verificación de identidad user_mailer.in_person_completion_survey.body.cta.callout: Haga clic en el botón siguiente para empezar. user_mailer.in_person_completion_survey.body.cta.label: Responda a nuestra encuesta user_mailer.in_person_completion_survey.body.greeting: 'Hola:' @@ -1912,10 +1916,6 @@ user_mailer.in_person_failed.intro: No se pudo verificar su identidad en la ofic user_mailer.in_person_failed.subject: No se pudo verificar su identidad en persona user_mailer.in_person_failed.verifying_identity: 'Cuando verifique su identidad:' user_mailer.in_person_failed.verifying_step_not_expired: Su licencia de conducir o identificación emitida por el estado debe estar vigente. Actualmente no aceptamos otras formas de identificación, como pasaportes o identificaciones militares. -user_mailer.in_person_please_call.body.contact_message_html: Llame al %{contact_number} y proporcione el código de error %{support_code}. -user_mailer.in_person_please_call.body.intro_html: Llame a nuestro centro de contacto antes del %{date} para seguir verificando su identidad. -user_mailer.in_person_please_call.header: Llámenos -user_mailer.in_person_please_call.subject: Llame a %{app_name} para continuar con la verificación de identidad user_mailer.in_person_ready_to_verify_reminder.greeting: 'Hola:' user_mailer.in_person_ready_to_verify_reminder.heading.one: Le queda %{count} día para verificar su identidad en persona user_mailer.in_person_ready_to_verify_reminder.heading.other: Le quedan %{count} días para verificar su identidad en persona diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6775afb05c9..5ea53a3c147 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1875,6 +1875,10 @@ user_mailer.email_deleted.header: Une adresse e-mail a été supprimée de votre user_mailer.email_deleted.help_html: Si vous ne souhaitez pas supprimer cette adresse e-mail, veuillez visiter le %{help_link_html} de %{app_name_html} ou %{contact_link_html}. user_mailer.email_deleted.subject: Adresse e-mail supprimée user_mailer.help_link_text: Centre d’aide +user_mailer.idv_please_call.body.contact_message_html: Appelez le %{contact_number} et indiquez le code d’erreur %{support_code}. +user_mailer.idv_please_call.body.intro_html: Appelez notre centre de contact avant le %{date} pour continuer à vérifier votre identité. +user_mailer.idv_please_call.header: S’il vous plaît, appelez-nous +user_mailer.idv_please_call.subject: Appeler %{app_name} afin de poursuivre la vérification de votre identité user_mailer.in_person_completion_survey.body.cta.callout: Cliquez sur le bouton ci-dessous pour commencer. user_mailer.in_person_completion_survey.body.cta.label: Répondez à notre enquête user_mailer.in_person_completion_survey.body.greeting: Bonjour, @@ -1900,10 +1904,6 @@ user_mailer.in_person_failed.intro: Votre identité n’a pas pu être vérifié user_mailer.in_person_failed.subject: Votre identité n’a pas pu être vérifiée en personne user_mailer.in_person_failed.verifying_identity: 'Lors de la vérification de votre identité :' user_mailer.in_person_failed.verifying_step_not_expired: Votre carte d’identité délivrée par l’État ou votre permis de conduire ne doit pas être périmé. Nous n’acceptons actuellement aucune autre pièce d’identité, comme les passeports et les cartes d’identité militaires. -user_mailer.in_person_please_call.body.contact_message_html: Appelez le %{contact_number} et indiquez le code d’erreur %{support_code}. -user_mailer.in_person_please_call.body.intro_html: Appelez notre centre de contact avant le %{date} pour continuer à vérifier votre identité. -user_mailer.in_person_please_call.header: S’il vous plaît, appelez-nous -user_mailer.in_person_please_call.subject: Appeler %{app_name} afin de poursuivre la vérification de votre identité user_mailer.in_person_ready_to_verify_reminder.greeting: Bonjour, user_mailer.in_person_ready_to_verify_reminder.heading.one: Il vous reste %{count} jour pour vérifier votre identité en personne user_mailer.in_person_ready_to_verify_reminder.heading.other: Il vous reste %{count} jours pour vérifier votre identité en personne diff --git a/config/locales/zh.yml b/config/locales/zh.yml index aaebc735fad..2797d52219e 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1888,6 +1888,10 @@ user_mailer.email_deleted.header: 一个电邮地址被从你的 %{app_name} 用 user_mailer.email_deleted.help_html: 如果你没有想删除这一电邮地址,请访问 %{app_name_html} %{help_link_html} 或者 %{contact_link_html}。 user_mailer.email_deleted.subject: 电邮地址已删除 user_mailer.help_link_text: 帮助中心 +user_mailer.idv_please_call.body.contact_message_html: 打电话给 %{contact_number} 并向他们提供错误 代码 %{support_code}。 +user_mailer.idv_please_call.body.intro_html: 请在 %{date} 之前给我们的联系中心打电话,以继续验证你的身份。 +user_mailer.idv_please_call.header: 请给我们打个电话 +user_mailer.idv_please_call.subject: 致电 %{app_name} 继续进行身份验证 user_mailer.in_person_completion_survey.body.cta.callout: 点击下面的按钮来开始 user_mailer.in_person_completion_survey.body.cta.label: 填写我们的意见调查 user_mailer.in_person_completion_survey.body.greeting: 你好, @@ -1913,10 +1917,6 @@ user_mailer.in_person_failed.intro: 你的身份于 %{date}在 %{location} 邮 user_mailer.in_person_failed.subject: 你的身份未能亲身被验证。 user_mailer.in_person_failed.verifying_identity: '验证你的身份时:' user_mailer.in_person_failed.verifying_step_not_expired: 你的州政府颁发的身份证件或驾照绝对没有过期。我们目前不接受任何其他形式的身份证件,比如护照和军队身份证件。 -user_mailer.in_person_please_call.body.contact_message_html: 打电话给 %{contact_number} 并向他们提供错误 代码 %{support_code}。 -user_mailer.in_person_please_call.body.intro_html: 请在 %{date} 之前给我们的联系中心打电话,以继续验证你的身份。 -user_mailer.in_person_please_call.header: 请给我们打个电话 -user_mailer.in_person_please_call.subject: 致电 %{app_name} 继续进行身份验证 user_mailer.in_person_ready_to_verify_reminder.greeting: 你好, user_mailer.in_person_ready_to_verify_reminder.heading.one: 你距离亲身验证身份截止日期还有 %{count} 天 user_mailer.in_person_ready_to_verify_reminder.heading.other: 你距离亲身验证身份截止日期还有 %{count} 天 diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 297b4c5a7f9..66edf8f05ee 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -243,11 +243,8 @@ def in_person_failed_fraud ) end - def in_person_please_call - UserMailer.with(user: user, email_address: email_address_record).in_person_please_call( - enrollment: in_person_enrollment_id_ipp, - visited_location_name: in_person_visited_location_name, - ) + def idv_please_call + UserMailer.with(user: user, email_address: email_address_record).idv_please_call end def account_rejected diff --git a/spec/mailers/previews/user_mailer_preview_spec.rb b/spec/mailers/previews/user_mailer_preview_spec.rb index fcc2bda9e53..d533387f9e5 100644 --- a/spec/mailers/previews/user_mailer_preview_spec.rb +++ b/spec/mailers/previews/user_mailer_preview_spec.rb @@ -2,7 +2,7 @@ require_relative './user_mailer_preview' RSpec.describe UserMailerPreview do - it_behaves_like 'a mailer preview' + it_behaves_like 'a mailer preview', preview_methods_that_can_be_missing: [:in_person_please_call] it 'uses user and email records that cannot be saved' do expect(User.count).to eq(0) diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 8e273ac41ef..5ac90eb4ebf 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -799,6 +799,23 @@ def expect_email_body_to_have_help_and_contact_links end end + describe '#idv_please_call' do + let(:mail) do + UserMailer.with(user: user, email_address: email_address).idv_please_call + end + + it_behaves_like 'a system email' + it_behaves_like 'an email that respects user email locale preference' + + it 'renders the idv_please_call template' do + expect_any_instance_of(ActionMailer::Base).to receive(:mail) + .with(hash_including(template_name: 'idv_please_call')) + .and_call_original + + mail.deliver_later + end + end + context 'in person emails' do let(:current_address_matches_id) { false } let!(:enrollment) do @@ -1306,6 +1323,14 @@ def expect_email_body_to_have_help_and_contact_links it_behaves_like 'a system email' it_behaves_like 'an email that respects user email locale preference' + it 'renders the idv_please_call template' do + expect_any_instance_of(ActionMailer::Base).to receive(:mail) + .with(hash_including(template_name: 'idv_please_call')) + .and_call_original + + mail.deliver_later + end + context 'when the keyword argument visited_location_name is missing' do let(:mail) do UserMailer.with(user: user, email_address: email_address).in_person_please_call( diff --git a/spec/support/shared_examples/mailer_preview.rb b/spec/support/shared_examples/mailer_preview.rb index 04918203c96..4f359c1f43b 100644 --- a/spec/support/shared_examples/mailer_preview.rb +++ b/spec/support/shared_examples/mailer_preview.rb @@ -1,10 +1,10 @@ -RSpec.shared_examples 'a mailer preview' do +RSpec.shared_examples 'a mailer preview' do |preview_methods_that_can_be_missing: []| let(:mailer_class) { described_class.class_name.gsub(/Preview$/, '').constantize } it 'has a preview method for each mailer method' do mailer_methods = mailer_class.instance_methods(false) preview_methods = described_class.instance_methods(false) - expect(mailer_methods - preview_methods).to be_empty + expect(mailer_methods - preview_methods).to eql(preview_methods_that_can_be_missing) end described_class.instance_methods(false).each do |mailer_method| From 913b6e278bb131f0c9dbb59f85e814c95c003d95 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 17 Dec 2024 09:51:59 -0800 Subject: [PATCH 2/8] Refactor expect_delivered_email I was having some trouble working with this, so I reworked it a little and added param docs --- spec/support/mailer_helper.rb | 86 ++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/spec/support/mailer_helper.rb b/spec/support/mailer_helper.rb index eb264b0adca..41ab83c5f9c 100644 --- a/spec/support/mailer_helper.rb +++ b/spec/support/mailer_helper.rb @@ -15,26 +15,88 @@ 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 + + private + + def body_matches(email:, body:) + return true if body.nil? + + delivered_body = email.text_part.decoded.squish + + body.to_a.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? || sent_mail.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 && body.to_a.map do |substring| + found = body_text.include?(substring) + "- #{substring} (#{found ? 'found' : 'not found'})" + end + + 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}", + ].compact.join("\n") + end + + def summarize_all_deliveries(query) + ActionMailer::Base.deliveries.map do |email| + summary = summarize_delivery(email:, **query) + [ + "- #{summary.lines.first.strip}", + *summary.lines.drop(1).map { |l| l.strip.indent(2) }, + ].join("\n") + end.join("\n") + end end From 4616d9fa714e9ddfdb9e1e1e0dd1803aa14e5b33 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 17 Dec 2024 09:53:16 -0800 Subject: [PATCH 3/8] Add expect_email_not_delivered helper --- spec/support/mailer_helper.rb | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/support/mailer_helper.rb b/spec/support/mailer_helper.rb index 41ab83c5f9c..2926e7aa6a9 100644 --- a/spec/support/mailer_helper.rb +++ b/spec/support/mailer_helper.rb @@ -33,6 +33,34 @@ def expect_delivered_email(to: nil, subject: nil, body: nil) 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) + email = ActionMailer::Base.deliveries.find do |sent_mail| + to_matches = to.nil? || sent_mail.to.include?(to) + subject_matches = subject.nil? || sent_mail.subject == subject + body_matches = body.nil? || begin + delivered_body = sent_mail.text_part.decoded.squish + body.to_a.all? do |expected_substring| + delivered_body.include?(expected_substring) + end + end + + to_matches && subject_matches && body_matches + end + + error_message = <<~ERROR + Found an email matching the below (but shouldn't have): + to: #{to} + subject: #{subject} + body: #{body} + Sent mails: #{ActionMailer::Base.deliveries} + ERROR + + expect(email).to be(nil), error_message + end + private def body_matches(email:, body:) @@ -92,7 +120,7 @@ def summarize_delivery( def summarize_all_deliveries(query) ActionMailer::Base.deliveries.map do |email| - summary = summarize_delivery(email:, **query) + summary = summarize_delivery(delivery:, **query) [ "- #{summary.lines.first.strip}", *summary.lines.drop(1).map { |l| l.strip.indent(2) }, From ab90bcaea141996679a9794a193b2353418c60e6 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Mon, 16 Dec 2024 10:46:33 -0800 Subject: [PATCH 4/8] Update IPP code to use idv_please_call email This is the exact same email, content-wise, just with a new name to reflect the fact that it is used throughout idv. [skip changelog] --- app/jobs/get_usps_proofing_results_job.rb | 2 +- spec/jobs/get_usps_proofing_results_job_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index 78ad7a08c5b..ebb16537316 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -610,7 +610,7 @@ def send_failed_fraud_email(enrollment:, visited_location_name:) def send_please_call_email(enrollment:, visited_location_name:) enrollment.user.confirmed_email_addresses.each do |email_address| # rubocop:disable IdentityIdp/MailLaterLinter - UserMailer.with(user: enrollment.user, email_address: email_address).in_person_please_call( + UserMailer.with(user: enrollment.user, email_address: email_address).idv_please_call( enrollment: enrollment, visited_location_name: visited_location_name, ).deliver_later(**notification_delivery_params(enrollment)) diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index 99ff7bfaa0a..5ea9d8b9131 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -1490,7 +1490,7 @@ allow(analytics).to receive( :idv_in_person_usps_proofing_results_job_please_call_email_initiated, ) - allow(user_mailer).to receive(:in_person_please_call).and_return(mail_deliverer) + allow(user_mailer).to receive(:idv_please_call).and_return(mail_deliverer) subject.perform(current_time) end @@ -1545,7 +1545,7 @@ end it 'sends the please call email' do - expect(user_mailer).to have_received(:in_person_please_call).with( + expect(user_mailer).to have_received(:idv_please_call).with( enrollment: enrollment, visited_location_name: visited_location_name, ) From d5f4cf09b302e61f7c0e4196d7f5907af0dfd3bd Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 17 Dec 2024 10:40:00 -0800 Subject: [PATCH 5/8] Send idv_please_call for GPO verification --- .../idv/by_mail/enter_code_controller.rb | 15 +++++++++++++++ .../idv/by_mail/enter_code_controller_spec.rb | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb index 76f0654177b..d49b3fe4dd7 100644 --- a/app/controllers/idv/by_mail/enter_code_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_controller.rb @@ -55,6 +55,8 @@ def create result = @gpo_verify_form.submit(resolved_authn_context_result.enhanced_ipp?) analytics.idv_verify_by_mail_enter_code_submitted(**result) + send_please_call_email_if_necessary(result:) + if !result.success? if rate_limiter.limited? redirect_to idv_enter_code_rate_limited_url @@ -120,6 +122,19 @@ def rate_limiter ) end + # @param [FormResponse] result GpoVerifyForm result + def send_please_call_email_if_necessary(result:) + return if !result.success? + + return if result.extra[:pending_in_person_enrollment] + + return if !result.extra[:fraud_check_failed] + + return if !FeatureManagement.proofing_device_profiling_decisioning_enabled? + + current_user.send_email_to_all_addresses(:idv_please_call) + end + def build_gpo_verify_form GpoVerifyForm.new( user: current_user, diff --git a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb index 5e0a8581413..e9f5ef0f835 100644 --- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb @@ -285,6 +285,14 @@ expect(event_count).to eq 1 expect(response).to redirect_to(idv_personal_key_url) end + + it 'does not send the "Please Call" email' do + action + expect_email_not_delivered( + to: user.confirmed_email_addresses.first.email, + subject: t('user_mailer.idv_please_call.subject', app_name: APP_NAME), + ) + end end end @@ -323,6 +331,14 @@ expect(UserAlerts::AlertUserAboutAccountVerified).not_to have_received(:call) end + + it 'sends the "Please Call" email' do + action + expect_delivered_email( + to: user.confirmed_email_addresses.first.email, + subject: t('user_mailer.idv_please_call.subject', app_name: APP_NAME), + ) + end end context 'with threatmetrix status of "review"' do From e19ad302d6cb296ae4e6c3929be4d03af8a95a17 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 17 Dec 2024 15:21:44 -0800 Subject: [PATCH 6/8] Send 'Please Call' email for phone verification users --- .../idv/enter_password_controller.rb | 4 + .../idv/enter_password_controller_spec.rb | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/app/controllers/idv/enter_password_controller.rb b/app/controllers/idv/enter_password_controller.rb index af6da96312f..dc5bdfaa327 100644 --- a/app/controllers/idv/enter_password_controller.rb +++ b/app/controllers/idv/enter_password_controller.rb @@ -143,6 +143,10 @@ def init_profile log_letter_enqueued_analytics(resend: false) end + if profile.fraud_review_pending? && !profile.in_person_verification_pending? + current_user.send_email_to_all_addresses(:idv_please_call) + end + if profile.active? create_user_event(:account_verified) UserAlerts::AlertUserAboutAccountVerified.call( diff --git a/spec/controllers/idv/enter_password_controller_spec.rb b/spec/controllers/idv/enter_password_controller_spec.rb index fcb3b8b8e70..a96a19998d4 100644 --- a/spec/controllers/idv/enter_password_controller_spec.rb +++ b/spec/controllers/idv/enter_password_controller_spec.rb @@ -13,6 +13,8 @@ end let(:applicant) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE } let(:use_gpo) { false } + let(:threatmetrix_enabled) { true } + let(:threatmetrix_result) { 'pass' } let(:idv_session) do subject.idv_session end @@ -28,6 +30,7 @@ subject.idv_session.pii_from_doc = Pii::StateId.new(**Idp::Constants::MOCK_IDV_APPLICANT) subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn] subject.idv_session.threatmetrix_session_id = 'random-session-id' + subject.idv_session.threatmetrix_review_status = threatmetrix_result subject.idv_session.resolution_successful = true subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE subject.idv_session.resolution_successful = true @@ -43,6 +46,9 @@ end subject.idv_session.applicant = applicant.with_indifferent_access + + allow(IdentityConfig.store).to receive(:proofing_device_profiling) + .and_return(threatmetrix_enabled ? :enabled : :disabled) end describe '#step_info' do @@ -404,6 +410,41 @@ def show expect(events_count).to eq 1 end + context 'user was flagged by ThreatMetrix' do + let(:threatmetrix_result) { 'reject' } + + it 'sends the idv_please_call email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_delivered_email( + to: user.confirmed_email_addresses.first.email, + subject: t('user_mailer.idv_please_call.subject', app_name: APP_NAME), + ) + end + + it 'does not send the account_verified email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_email_not_delivered( + subject: t('user_mailer.account_verified.subject', app_name: APP_NAME), + ) + end + + context 'but ThreatMetrix disabled' do + let(:threatmetrix_enabled) { false } + it 'does not send the idv_please_call email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_email_not_delivered( + subject: t('user_mailer.idv_please_call.subject'), + ) + end + it 'sends the account_verified email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_delivered_email( + subject: t('user_mailer.account_verified.subject', app_name: APP_NAME), + ) + end + end + end + context 'with in person profile' do let!(:enrollment) do create(:in_person_enrollment, :establishing, user: user, profile: nil) @@ -472,6 +513,17 @@ def show ) end + context 'user was flagged by ThreatMetrix' do + let(:threatmetrix_result) { 'reject' } + + it 'does not send the idv_please_call email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_email_not_delivered( + subject: t('user_mailer.idv_please_call.subject'), + ) + end + end + context 'when there is a 4xx error' do before do stub_request_enroll_bad_request_response @@ -965,6 +1017,27 @@ def show expect(user.reload.gpo_verification_pending_profile).to be_nil end end + + context 'user was flagged by ThreatMetrix' do + let(:threatmetrix_review_status) { 'reject' } + + it 'does not send the idv_please_call email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_email_not_delivered( + subject: t('user_mailer.idv_please_call.subject'), + ) + end + + context 'but ThreatMetrix disabled' do + let(:threatmetrix_enabled) { false } + it 'does not send the idv_please_call email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect_email_not_delivered( + subject: t('user_mailer.idv_please_call.subject'), + ) + end + end + end end context 'user is going through enhanced ipp' do From e917b8a5cd3e705ca6c3c86f5dc7dca773301e9f Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Tue, 17 Dec 2024 15:43:41 -0800 Subject: [PATCH 7/8] Attach phone_icon.png rather than referencing by URL Many clients won't display inline images referenced by URL for privacy reasons. --- app/mailers/user_mailer.rb | 3 +++ app/views/user_mailer/idv_please_call.html.erb | 2 +- spec/mailers/user_mailer_spec.rb | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 46e480dab2a..eb623771eed 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -249,6 +249,9 @@ def account_verified(profile:) end def idv_please_call(**) + attachments.inline['phone_icon.png'] = + Rails.root.join('app/assets/images/email/phone_icon.png').read + with_user_locale(user) do @hide_title = true diff --git a/app/views/user_mailer/idv_please_call.html.erb b/app/views/user_mailer/idv_please_call.html.erb index 54f4a89ba9f..989409d1602 100644 --- a/app/views/user_mailer/idv_please_call.html.erb +++ b/app/views/user_mailer/idv_please_call.html.erb @@ -1,5 +1,5 @@ <%= image_tag( - asset_url('email/phone_icon.png'), + attachments['phone_icon.png'].url, alt: t('image_description.phone_icon'), width: 88, height: 88, diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 5ac90eb4ebf..c33ece27391 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -814,6 +814,13 @@ def expect_email_body_to_have_help_and_contact_links mail.deliver_later end + + it 'attaches the icon inline' do + icon_part = mail.attachments['phone_icon.png'] + expect(icon_part).not_to be(nil) + expect(icon_part.inline?).to eql(true) + expect(icon_part.url).to start_with('cid:') + end end context 'in person emails' do From 6975326ec2ddd21b4b47032638746c0f9b681061 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Mon, 16 Dec 2024 11:04:09 -0800 Subject: [PATCH 8/8] Remove in_person_please_call email This has been renamed to idv_please_call. [skip changelog] --- app/mailers/user_mailer.rb | 2 -- .../previews/user_mailer_preview_spec.rb | 2 +- spec/mailers/user_mailer_spec.rb | 31 ------------------- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index eb623771eed..70d27f774c9 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -263,8 +263,6 @@ def idv_please_call(**) end end - alias_method :in_person_please_call, :idv_please_call - def in_person_completion_survey with_user_locale(user) do @header = t('user_mailer.in_person_completion_survey.header') diff --git a/spec/mailers/previews/user_mailer_preview_spec.rb b/spec/mailers/previews/user_mailer_preview_spec.rb index d533387f9e5..fcc2bda9e53 100644 --- a/spec/mailers/previews/user_mailer_preview_spec.rb +++ b/spec/mailers/previews/user_mailer_preview_spec.rb @@ -2,7 +2,7 @@ require_relative './user_mailer_preview' RSpec.describe UserMailerPreview do - it_behaves_like 'a mailer preview', preview_methods_that_can_be_missing: [:in_person_please_call] + it_behaves_like 'a mailer preview' it 'uses user and email records that cannot be saved' do expect(User.count).to eq(0) diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index c33ece27391..3a2c9142d71 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -1319,37 +1319,6 @@ def expect_email_body_to_have_help_and_contact_links end end - describe '#in_person_please_call' do - let(:mail) do - UserMailer.with(user: user, email_address: email_address).in_person_please_call( - enrollment: enrollment, - visited_location_name: visited_location_name, - ) - end - - it_behaves_like 'a system email' - it_behaves_like 'an email that respects user email locale preference' - - it 'renders the idv_please_call template' do - expect_any_instance_of(ActionMailer::Base).to receive(:mail) - .with(hash_including(template_name: 'idv_please_call')) - .and_call_original - - mail.deliver_later - end - - context 'when the keyword argument visited_location_name is missing' do - let(:mail) do - UserMailer.with(user: user, email_address: email_address).in_person_please_call( - enrollment: enrollment, - ) - end - it 'sends the email successfully' do - mail.deliver_later - end - end - end - describe '#in_person_completion_survey' do let(:mail) do UserMailer.with(user: user, email_address: email_address).in_person_completion_survey