diff --git a/app/controllers/concerns/idv/getting_started_ab_test_concern.rb b/app/controllers/concerns/idv/getting_started_ab_test_concern.rb new file mode 100644 index 00000000000..212404a699c --- /dev/null +++ b/app/controllers/concerns/idv/getting_started_ab_test_concern.rb @@ -0,0 +1,13 @@ +module Idv + module GettingStartedAbTestConcern + def getting_started_a_b_test_bucket + AbTests::IDV_GETTING_STARTED.bucket(sp_session[:request_id] || session.id) + end + + def maybe_redirect_for_getting_started_ab_test + return if getting_started_a_b_test_bucket != :getting_started + + redirect_to idv_getting_started_url + end + end +end diff --git a/app/controllers/idv/getting_started_controller.rb b/app/controllers/idv/getting_started_controller.rb new file mode 100644 index 00000000000..e0a38117053 --- /dev/null +++ b/app/controllers/idv/getting_started_controller.rb @@ -0,0 +1,84 @@ +module Idv + class GettingStartedController < ApplicationController + include IdvStepConcern + include StepUtilitiesConcern + + before_action :confirm_agreement_needed + + def show + analytics.idv_doc_auth_getting_started_visited(**analytics_arguments) + + # Register both Welcome and Agreement steps in DocAuthLog + Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]). + call('welcome', :view, true) + Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]). + call('agreement', :view, true) + + @sp_name = decorated_session.sp_name || APP_NAME + @title = t('doc_auth.headings.getting_started', sp_name: @sp_name) + + render :show, locals: { flow_session: flow_session } + end + + def update + flow_session[:skip_upload_step] = true unless FeatureManagement.idv_allow_hybrid_flow? + skip_to_capture if params[:skip_upload] + + result = Idv::ConsentForm.new.submit(consent_form_params) + + analytics.idv_doc_auth_getting_started_submitted( + **analytics_arguments.merge(result.to_h), + ) + + if result.success? + idv_session.idv_consent_given = true + + create_document_capture_session + cancel_previous_in_person_enrollments + + redirect_to idv_hybrid_handoff_url + else + redirect_to idv_getting_started_url + end + end + + private + + def analytics_arguments + { + step: 'getting_started', + analytics_id: 'Doc Auth', + irs_reproofing: irs_reproofing?, + } + end + + def create_document_capture_session + document_capture_session = DocumentCaptureSession.create( + user_id: current_user.id, + issuer: sp_session[:issuer], + ) + flow_session[:document_capture_session_uuid] = document_capture_session.uuid + end + + def cancel_previous_in_person_enrollments + return unless IdentityConfig.store.in_person_proofing_enabled + UspsInPersonProofing::EnrollmentHelper. + cancel_stale_establishing_enrollments_for_user(current_user) + end + + def skip_to_capture + flow_session[:skip_upload_step] = true + idv_session.flow_path = 'standard' + end + + def consent_form_params + params.require(:doc_auth).permit(:ial2_consent_given) + end + + def confirm_agreement_needed + return unless idv_session.idv_consent_given + + redirect_to idv_hybrid_handoff_url + end + end +end diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb index d4913074e27..f352e6ca4ec 100644 --- a/app/controllers/idv/welcome_controller.rb +++ b/app/controllers/idv/welcome_controller.rb @@ -3,18 +3,18 @@ class WelcomeController < ApplicationController include IdvStepConcern include StepIndicatorConcern include StepUtilitiesConcern + include GettingStartedAbTestConcern before_action :confirm_welcome_needed + before_action :maybe_redirect_for_getting_started_ab_test def show analytics.idv_doc_auth_welcome_visited(**analytics_arguments) - Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]).call( - 'welcome', :view, - true - ) + Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]). + call('welcome', :view, true) - render :show, locals: { flow_session: flow_session } + render :show end def update diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index ed4c3385dbc..cb522455b77 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -682,10 +682,17 @@ def idv_doc_auth_exception_visited(step_name:, remaining_attempts:, **extra) ) end + def idv_doc_auth_getting_started_submitted(**extra) + track_event('IdV: doc auth getting_started submitted', **extra) + end + + def idv_doc_auth_getting_started_visited(**extra) + track_event('IdV: doc auth getting_started visited', **extra) + end + # The "hybrid handoff" step: Desktop user has submitted their choice to # either continue via desktop ("document_capture" destination) or switch # to mobile phone ("send_link" destination) to perform document upload. - # Mobile users still log this event but with skip_upload_step = true # @identity.idp.previous_event_name IdV: doc auth upload submitted def idv_doc_auth_hybrid_handoff_submitted(**extra) track_event('IdV: doc auth hybrid handoff submitted', **extra) diff --git a/app/services/marketing_site.rb b/app/services/marketing_site.rb index d18eba07056..02748dd4f5f 100644 --- a/app/services/marketing_site.rb +++ b/app/services/marketing_site.rb @@ -15,6 +15,7 @@ class MarketingSite verify-your-identity/verify-your-identity-in-person verify-your-identity/phone-number verify-your-identity/verify-your-address-by-mail + verify-your-identity/how-to-verify-your-identity ].to_set.freeze def self.locale_segment diff --git a/app/views/idv/getting_started/show.html.erb b/app/views/idv/getting_started/show.html.erb new file mode 100644 index 00000000000..99d27e1b30e --- /dev/null +++ b/app/views/idv/getting_started/show.html.erb @@ -0,0 +1,100 @@ +<% title @title %> + +<%= render 'shared/maintenance_window_alert' do %> + <%= render JavascriptRequiredComponent.new( + header: t('idv.getting_started.no_js_header'), + intro: t('idv.getting_started.no_js_intro', sp_name: @sp_name), + ) do %> + + <% if @current_user&.reproof_for_irs?(service_provider: @current_sp) %> + <%= render AlertComponent.new( + type: :info, + message: t('doc_auth.info.irs_reproofing_explanation'), + class: ['margin-bottom-2', 'usa-alert--info-important'], + ) + %> + <% end %> + + <%= render AlertComponent.new( + type: :error, + class: [ + 'js-consent-form-alert', + 'margin-bottom-4', + flow_session[:error_message].blank? && 'display-none', + ].select(&:present?), + message: flow_session[:error_message].presence || t('errors.doc_auth.consent_form'), + ) %> + + <%= render PageHeadingComponent.new.with_content(@title) %> +

+ <%= t( + 'doc_auth.info.getting_started_html', + sp_name: @sp_name, + link_html: new_tab_link_to( + t('doc_auth.info.getting_started_learn_more'), + help_center_redirect_path( + category: 'verify-your-identity', + article: 'how-to-verify-your-identity', + flow: :idv, + step: :getting_started, + location: 'intro_paragraph', + ), + ), + ) %> +

+ +

<%= t('doc_auth.getting_started.instructions.getting_started') %>

+ + <%= render ProcessListComponent.new(heading_level: :h3, class: 'margin-y-3') do |c| %> + <%= c.with_item(heading: t('doc_auth.getting_started.instructions.bullet1')) do %> +

<%= t('doc_auth.getting_started.instructions.text1') %>

+ <% end %> + <%= c.with_item(heading: t('doc_auth.getting_started.instructions.bullet2')) do %> +

<%= t('doc_auth.getting_started.instructions.text2') %>

+ <% end %> + <%= c.with_item(heading: t('doc_auth.getting_started.instructions.bullet3')) do %> +

<%= t('doc_auth.getting_started.instructions.text3') %>

+ <% end %> + <%= c.with_item(heading: t('doc_auth.getting_started.instructions.bullet4', app_name: APP_NAME)) do %> +

<%= t('doc_auth.getting_started.instructions.text4') %>

+ <% end %> + <% end %> + + <%= simple_form_for( + :doc_auth, + url: url_for, + method: 'put', + html: { autocomplete: 'off', class: 'margin-top-2 margin-bottom-5 js-consent-continue-form' }, + ) do |f| %> + <%= render ClickObserverComponent.new(event_name: 'IdV: consent checkbox toggled') do %> + <%= render ValidatedFieldComponent.new( + form: f, + name: :ial2_consent_given, + as: :boolean, + label: t('doc_auth.getting_started.instructions.consent', app_name: APP_NAME), + required: true, + ) %> + <% end %> +

+ <%= new_tab_link_to( + t('doc_auth.getting_started.instructions.learn_more'), + policy_redirect_url(flow: :idv, step: :getting_started, location: :consent), + ) %> +

+
+ <%= render( + SpinnerButtonComponent.new( + type: :submit, + big: true, + wide: true, + spin_on_click: false, + ).with_content(t('doc_auth.buttons.continue')), + ) %> +
+ <% end %> + + <%= render 'shared/cancel', link: idv_cancel_path(step: 'getting_started') %> + <% end %> +<% end %> + +<%= javascript_packs_tag_once('document-capture-welcome') %> diff --git a/app/views/idv/welcome/show.html.erb b/app/views/idv/welcome/show.html.erb index 6a852e68406..c5db6c7538a 100644 --- a/app/views/idv/welcome/show.html.erb +++ b/app/views/idv/welcome/show.html.erb @@ -12,7 +12,7 @@ <%= render 'shared/maintenance_window_alert' do %> <%= render JavascriptRequiredComponent.new( header: t('idv.welcome.no_js_header'), - intro: t('idv.welcome.no_js_intro', sp_name: decorated_session.sp_name || t('doc_auth.info.no_sp_name')), + intro: t('idv.welcome.no_js_intro', sp_name: decorated_session.sp_name || APP_NAME), ) do %> <% if @current_user&.reproof_for_irs?(service_provider: @current_sp) %> @@ -26,7 +26,7 @@ <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.welcome')) %>

- <%= t('doc_auth.info.welcome', sp_name: decorated_session.sp_name || t('doc_auth.info.no_sp_name')) %> + <%= t('doc_auth.info.welcome', sp_name: decorated_session.sp_name || APP_NAME) %>

<%= t('doc_auth.instructions.welcome') %>

diff --git a/config/application.yml.default b/config/application.yml.default index 529d15b7716..2261141a3bf 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -127,6 +127,7 @@ idv_acuant_sdk_version_default: '11.8.2' idv_acuant_sdk_version_alternate: '11.8.1' idv_acuant_sdk_upgrade_a_b_testing_enabled: false idv_acuant_sdk_upgrade_a_b_testing_percent: 50 +idv_getting_started_a_b_testing: '{"welcome":100,"getting_started":0}' idv_send_link_attempt_window_in_minutes: 10 idv_send_link_max_attempts: 5 idv_tmx_test_csp_disabled_emails: '[]' diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index 4df55b07756..2d37e294cc4 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -18,4 +18,9 @@ module AbTests 0, }, ) + + IDV_GETTING_STARTED = AbTestBucket.new( + experiment_name: 'Idv: Getting Started Experience', + buckets: IdentityConfig.store.idv_getting_started_a_b_testing, + ) end diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index c5f00f01cba..bba28cd9e58 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -100,6 +100,23 @@ en: choose_file_html: Drag file here or choose from folder doc_success: We verified your information selected_file: Selected file + getting_started: + instructions: + bullet1: Add photos of your ID + bullet2: Enter your Social Security number + bullet3: Match to your phone number + bullet4: Re-enter your %{app_name} password + consent: By checking this box, you are letting %{app_name} ask for, use, keep, + and share your personal information. We will use it to verify your + identity. + getting_started: 'You’ll need to:' + learn_more: Learn more about our privacy and security measures + text1: Use your driver’s license or state ID card. Other forms of ID are not + accepted. + text2: You will not need your physical SSN card. + text3: Your phone number matches you to your personal information. After you + match, we’ll send you a code. + text4: Your password saves and encrypts your personal information. headings: address: Update your mailing address back: Back @@ -113,6 +130,7 @@ en: document_capture_back: Back of your ID document_capture_front: Front of your ID front: Front + getting_started: Let’s verify your identity for %{sp_name} hybrid_handoff: How would you like to add your ID? interstitial: We are processing your images lets_go: How verifying your identity works @@ -146,6 +164,9 @@ en: document_capture_intro_acknowledgment: We’ll collect information about you by reading your state‑issued ID. We use this information to verify your identity. + getting_started_html: '%{sp_name} needs to make sure you are you — not someone + pretending to be you. %{link_html}' + getting_started_learn_more: Learn more about what you need to verify your identity hybrid_handoff: We’ll collect information about you by reading your state‑issued ID. image_loaded: Image loaded image_loading: Image loading @@ -161,7 +182,6 @@ en: your state‑issued ID. link_sent_complete_no_polling: When you are done, click Continue here to finish verifying your identity. link_sent_complete_polling: The next step will load automatically. - no_sp_name: The agency that you are trying to access privacy: '%{app_name} is a secure, government website that adheres to the highest standards in data protection. We use your data to verify your identity.' diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 7c8caa16b9a..a71f8b4e2e7 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -125,6 +125,23 @@ es: carpeta doc_success: Verificamos sus datos selected_file: Archivo seleccionado + getting_started: + instructions: + bullet1: Incluir fotos de su identificación + bullet2: Introducir su número de Seguro Social + bullet3: Vincular su número de teléfono + bullet4: Volver a introducir su contraseña de %{app_name} + consent: Al marcar esta casilla, usted permite que %{app_name} solicite, + utilice, conserve y comparta su información personal. Los utilizamos + para verificar su identidad. + getting_started: 'Deberá:' + learn_more: Obtenga más información sobre nuestras medidas de privacidad y + seguridad + text1: Su documento de identidad no puede estar caducado. + text2: No necesitará la tarjeta con usted. + text3: Su número de teléfono se asocia a su información personal. Después de que + lo haya asociado, le enviaremos un código. + text4: Su contraseña guarda y encripta su información personal. headings: address: Actualice su dirección postal back: Parte Trasera @@ -138,6 +155,7 @@ es: document_capture_back: Parte trasera de su documento de identidad document_capture_front: Parte delantera de su documento de identidad front: Parte Delantera + getting_started: Vamos a verificar su identidad para %{sp_name} hybrid_handoff: '¿Cómo desea añadir su documento de identidad?' interstitial: Estamos procesando sus imágenes lets_go: Cómo funciona la verificación de su identidad @@ -174,6 +192,9 @@ es: document_capture_intro_acknowledgment: Recopilaremos información sobre usted leyendo su documento de identidad expedido por el Estado. Usamos esta información para verificar su identidad. + getting_started_html: '%{sp_name} necesita asegurarse de que es usted realmente + y no alguien que se hace pasar por usted. %{link_html}' + getting_started_learn_more: Obtenga más información sobre lo que necesita para verificar su identidad hybrid_handoff: Recopilaremos información sobre usted leyendo su documento de identidad expedido por el estado. image_loaded: Imagen cargada @@ -193,7 +214,6 @@ es: completar la verificación de tu identidad. link_sent_complete_polling: El siguiente paso se cargará automáticamente una vez que verifiques tu identidad a través de tu teléfono. - no_sp_name: La agencia a la que está intentando acceder privacy: '%{app_name} es un sitio web gubernamental seguro que cumple con las normas más estrictas de protección de datos. Utilizamos sus datos para verificar su identidad.' diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index cedab5fca79..02c71951d7a 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -131,6 +131,23 @@ fr: dossier doc_success: Nous avons vérifié vos informations selected_file: Fichier sélectionné + getting_started: + instructions: + bullet1: Ajoutez des photos de votre pièce d’identité + bullet2: Saisissez votre numéro de sécurité sociale + bullet3: Faire correspondre à votre numéro de téléphone + bullet4: Saisissez à nouveau votre mot de passe %{app_name} + consent: En cochant cette case, vous autorisez %{app_name} à demander, utiliser, + conserver et partager vos renseignements personnels. Nous les + utilisons pour vérifier votre identité. + getting_started: 'Vous aurez besoin de :' + learn_more: En savoir plus sur nos mesures de confidentialité et de sécurité + text1: Utilisez votre permis de conduire ou votre carte d’identité de l’État. + Les autres pièces d’identité ne sont pas acceptées. + text2: Vous n’aurez pas besoin de votre carte SSN physique. + text3: Votre numéro de téléphone correspond à vos informations personnelles. Une + fois la correspondance établie, nous vous enverrons un code. + text4: Votre mot de passe sauvegarde et crypte vos informations personnelles. headings: address: Mettre à jour votre adresse postale back: Verso @@ -144,6 +161,7 @@ fr: document_capture_back: Verso de votre carte d’identité document_capture_front: Recto de votre carte d’identité front: Recto + getting_started: Vérifions votre identité pour %{sp_name} hybrid_handoff: Comment voulez-vous ajouter votre identifiant ? interstitial: Nous traitons vos images lets_go: Comment fonctionne la vérification de votre identité @@ -179,6 +197,9 @@ fr: document_capture_intro_acknowledgment: Nous recueillons des informations sur vous en lisant votre pièce d’identité délivrée par l’État. Nous utilisons ces informations pour vérifier votre identité. + getting_started_html: '%{sp_name} doit s’assurer que c’est bien vous — et non + quelqu’un qui se fait passer pour vous. %{link_html}' + getting_started_learn_more: En savoir plus sur ce dont vous avez besoin pour vérifier votre identité hybrid_handoff: Nous recueillons des informations sur vous en lisant votre carte d’identité délivrée par l’État. image_loaded: Image chargée @@ -200,7 +221,6 @@ fr: link_sent_complete_polling: L’étape suivante se chargera automatiquement une fois que vous aurez confirmé votre identifiant à l’aide de votre téléphone. - no_sp_name: L’agence à laquelle vous essayez d’accéder privacy: '%{app_name} est un site gouvernemental sécurisé qui respecte les normes les plus strictes en matière de protection des données. Nous utilisons vos données pour vérifier votre identité.' diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 31a9a32e45f..0c2fe8e310d 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -166,6 +166,10 @@ en: ssn_label: Social Security number state: State zipcode: ZIP Code + getting_started: + no_js_header: You must enable JavaScript to verify your identity. + no_js_intro: '%{sp_name} needs you to verify your identity. You need to enable + JavaScript to continue this process.' images: come_back_later: Letter with a check mark index: diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 54626089440..dd8a0e31292 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -174,6 +174,10 @@ es: ssn_label: Número de Seguro Social state: Estado zipcode: Código postal + getting_started: + no_js_header: Debe habilitar JavaScript para verificar su identidad. + no_js_intro: '%{sp_name} requiere que usted verifique su identidad. Debe + habilitar JavaScript para continuar con este proceso.' images: come_back_later: Carta con una marca de verificación index: diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index 5e7d2fa128e..adc95adb52f 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -181,6 +181,10 @@ fr: ssn_label: Numéro de sécurité sociale state: État zipcode: Code postal + getting_started: + no_js_header: Vous devez activer JavaScript pour vérifier votre identité. + no_js_intro: '%{sp_name} a besoin de vous pour vérifier votre identité. Vous + devez activer JavaScript pour poursuivre ce processus.' images: come_back_later: Lettre avec un crochet index: diff --git a/config/routes.rb b/config/routes.rb index 26ecb855193..65fb8ce61ea 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -330,6 +330,8 @@ # This route is included in SMS messages sent to users who start the IdV hybrid flow. It # should be kept short, and should not include underscores ("_"). get '/documents' => 'hybrid_mobile/entry#show', as: :hybrid_mobile_entry + get '/getting_started' => 'getting_started#show' + put '/getting_started' => 'getting_started#update' get '/hybrid_mobile/document_capture' => 'hybrid_mobile/document_capture#show' put '/hybrid_mobile/document_capture' => 'hybrid_mobile/document_capture#update' get '/hybrid_mobile/capture_complete' => 'hybrid_mobile/capture_complete#show' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 2b122bb88a3..9119edc93f1 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -221,6 +221,7 @@ def self.build_store(config_map) config.add(:idv_acuant_sdk_version_alternate, type: :string) config.add(:idv_acuant_sdk_upgrade_a_b_testing_enabled, type: :boolean) config.add(:idv_acuant_sdk_upgrade_a_b_testing_percent, type: :integer) + config.add(:idv_getting_started_a_b_testing, type: :json, options: { symbolize_names: true }) config.add(:idv_send_link_attempt_window_in_minutes, type: :integer) config.add(:idv_send_link_max_attempts, type: :integer) config.add(:idv_sp_required, type: :boolean) diff --git a/spec/controllers/concerns/idv/getting_started_ab_test_concern_spec.rb b/spec/controllers/concerns/idv/getting_started_ab_test_concern_spec.rb new file mode 100644 index 00000000000..464e40ddf96 --- /dev/null +++ b/spec/controllers/concerns/idv/getting_started_ab_test_concern_spec.rb @@ -0,0 +1,106 @@ +require 'rails_helper' + +RSpec.describe 'GettingStartedAbTestConcern' do + let(:user) { create(:user, :fully_registered, email: 'old_email@example.com') } + let(:idv_session) do + Idv::Session.new(user_session: subject.user_session, current_user: user, service_provider: nil) + end + + module Idv + class StepController < ApplicationController + include GettingStartedAbTestConcern + + def show + render plain: 'Hello' + end + end + end + + describe '#getting_started_a_b_test_bucket' do + let(:sp_session) { {} } + + controller Idv::StepController do + end + + before do + allow(session).to receive(:id).and_return('session-id') + allow(controller).to receive(:sp_session).and_return(sp_session) + allow(AbTests::IDV_GETTING_STARTED).to receive(:bucket) do |discriminator| + case discriminator + when 'session-id' + :welcome + when 'request-id' + :getting_started + end + end + end + + it 'returns the bucket based on session id' do + expect(controller.getting_started_a_b_test_bucket).to eq(:welcome) + end + + context 'with associated sp session request id' do + let(:sp_session) { { request_id: 'request-id' } } + + it 'returns the bucket based on request id' do + expect(controller.getting_started_a_b_test_bucket).to eq(:getting_started) + end + end + end + + context '#maybe_redirect_for_getting_started_ab_test' do + controller Idv::StepController do + before_action :maybe_redirect_for_getting_started_ab_test + end + + before do + sign_in(user) + routes.draw do + get 'show' => 'idv/step#show' + end + end + + let(:session_uuid) { SecureRandom.uuid } + + context 'A/B test specifies getting started page' do + before do + allow(controller).to receive(:getting_started_a_b_test_bucket). + and_return(:getting_started) + end + + it 'redirects to idv_getting_started_url' do + get :show + + expect(response).to redirect_to(idv_getting_started_url) + end + end + + context 'A/B test specifies welcome page' do + before do + allow(controller).to receive(:getting_started_a_b_test_bucket). + and_return(:welcome) + end + + it 'does not redirect users away from welcome page' do + get :show + + expect(response.body).to eq('Hello') + expect(response.status).to eq(200) + end + end + + context 'A/B test specifies some other value' do + before do + allow(controller).to receive(:getting_started_a_b_test_bucket). + and_return(:something_else) + end + + it 'does not redirect users away from welcome page' do + get :show + + expect(response.body).to eq('Hello') + expect(response.status).to eq(200) + end + end + end +end diff --git a/spec/controllers/idv/getting_started_controller_spec.rb b/spec/controllers/idv/getting_started_controller_spec.rb new file mode 100644 index 00000000000..0eaaa0ac9c0 --- /dev/null +++ b/spec/controllers/idv/getting_started_controller_spec.rb @@ -0,0 +1,124 @@ +require 'rails_helper' + +RSpec.describe Idv::GettingStartedController do + include IdvHelper + + let(:user) { create(:user) } + + before do + stub_sign_in(user) + stub_analytics + subject.user_session['idv/doc_auth'] = {} + end + + describe 'before_actions' do + it 'includes authentication before_action' do + expect(subject).to have_actions( + :before, + :confirm_two_factor_authenticated, + ) + end + + it 'includes outage before_action' do + expect(subject).to have_actions( + :before, + :check_for_outage, + ) + end + end + + describe '#show' do + let(:analytics_name) { 'IdV: doc auth getting_started visited' } + let(:analytics_args) do + { step: 'getting_started', + analytics_id: 'Doc Auth', + irs_reproofing: false } + end + + it 'renders the show template' do + get :show + + expect(response).to render_template :show + end + + it 'sends analytics_visited event' do + get :show + + expect(@analytics).to have_logged_event(analytics_name, analytics_args) + end + + it 'updates DocAuthLog welcome_view_count' do + doc_auth_log = DocAuthLog.create(user_id: user.id) + + expect { get :show }.to( + change { doc_auth_log.reload.welcome_view_count }.from(0).to(1), + ) + end + + it 'updates DocAuthLog agreement_view_count' do + doc_auth_log = DocAuthLog.create(user_id: user.id) + + expect { get :show }.to( + change { doc_auth_log.reload.agreement_view_count }.from(0).to(1), + ) + end + + context 'getting_started already visited' do + it 'redirects to hybrid_handoff' do + subject.idv_session.idv_consent_given = true + + get :show + + expect(response).to redirect_to(idv_hybrid_handoff_url) + end + end + + it 'redirects to please call page if fraud review is pending' do + profile = create(:profile, :fraud_review_pending) + + stub_sign_in(profile.user) + + get :show + + expect(response).to redirect_to(idv_please_call_url) + end + end + + describe '#update' do + let(:analytics_name) { 'IdV: doc auth getting_started submitted' } + + let(:analytics_args) do + { success: true, + errors: {}, + step: 'getting_started', + analytics_id: 'Doc Auth', + irs_reproofing: false } + end + + it 'sends analytics_submitted event with consent given' do + put :update, params: { doc_auth: { ial2_consent_given: 1 } } + + expect(@analytics).to have_logged_event(analytics_name, analytics_args) + end + + it 'creates a document capture session' do + expect { put :update, params: { doc_auth: { ial2_consent_given: 1 } } }. + to change { subject.user_session['idv/doc_auth'][:document_capture_session_uuid] }.from(nil) + end + + context 'with previous establishing in-person enrollments' do + let!(:enrollment) { create(:in_person_enrollment, :establishing, user: user, profile: nil) } + + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + end + + it 'cancels all previous establishing enrollments' do + put :update, params: { doc_auth: { ial2_consent_given: 1 } } + + expect(enrollment.reload.status).to eq('cancelled') + expect(user.establishing_in_person_enrollment).to be_blank + end + end + end +end diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index f8da3e66d5f..ca531e9414b 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -25,6 +25,13 @@ :check_for_outage, ) end + + it 'includes getting started ab test before_action' do + expect(subject).to have_actions( + :before, + :maybe_redirect_for_getting_started_ab_test, + ) + end end describe '#show' do diff --git a/spec/features/idv/doc_auth/getting_started_spec.rb b/spec/features/idv/doc_auth/getting_started_spec.rb new file mode 100644 index 00000000000..661ce15beeb --- /dev/null +++ b/spec/features/idv/doc_auth/getting_started_spec.rb @@ -0,0 +1,78 @@ +require 'rails_helper' + +RSpec.feature 'getting started step' do + include IdvHelper + include DocAuthHelper + + let(:fake_analytics) { FakeAnalytics.new } + let(:maintenance_window) { [] } + let(:sp_name) { 'Test SP' } + + before do + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:sp_name).and_return(sp_name) + stub_const('AbTests::IDV_GETTING_STARTED', FakeAbTestBucket.new) + AbTests::IDV_GETTING_STARTED.assign_all(:getting_started) + + visit_idp_from_sp_with_ial2(:oidc) + sign_in_and_2fa_user + complete_doc_auth_steps_before_welcome_step + end + + it 'displays expected content with javascript enabled', :js do + expect(page).to have_current_path(idv_getting_started_path) + + # Try to continue with unchecked checkbox + click_continue + expect(page).to have_current_path(idv_getting_started_path) + expect(page).to have_content(t('forms.validation.required_checkbox')) + + complete_getting_started_step + expect(page).to have_current_path(idv_hybrid_handoff_path) + end + + it 'logs "intro_paragraph" learn more link click' do + click_on t('doc_auth.info.getting_started_learn_more') + + expect(fake_analytics).to have_logged_event( + 'External Redirect', + step: 'getting_started', + location: 'intro_paragraph', + flow: 'idv', + redirect_url: MarketingSite.help_center_article_url( + category: 'verify-your-identity', + article: 'how-to-verify-your-identity', + ), + ) + end + + context 'when JS is disabled' do + it 'shows the notice if the user clicks continue without giving consent' do + click_continue + + expect(page).to have_current_path(idv_getting_started_url) + expect(page).to have_content(t('errors.doc_auth.consent_form')) + end + + it 'allows the user to continue after checking the checkbox' do + check t('doc_auth.instructions.consent', app_name: APP_NAME) + click_continue + + expect(page).to have_current_path(idv_hybrid_handoff_path) + end + end + + context 'skipping hybrid_handoff step', :js, driver: :headless_chrome_mobile do + before do + complete_getting_started_step + end + + it 'progresses to document capture' do + expect(page).to have_current_path(idv_document_capture_url) + end + end + + def complete_getting_started_step + complete_agreement_step # it does the right thing + end +end diff --git a/spec/views/idv/getting_started/show.html.erb_spec.rb b/spec/views/idv/getting_started/show.html.erb_spec.rb new file mode 100644 index 00000000000..769e4e3fb31 --- /dev/null +++ b/spec/views/idv/getting_started/show.html.erb_spec.rb @@ -0,0 +1,103 @@ +require 'rails_helper' + +RSpec.describe 'idv/getting_started/show' do + let(:flow_session) { {} } + let(:user_fully_authenticated) { true } + let(:sp_name) { nil } + let(:user) { create(:user) } + + before do + @decorated_session = instance_double(ServiceProviderSessionDecorator) + @sp_name = 'Login.gov' + @title = t('doc_auth.headings.getting_started', sp_name: @sp_name) + allow(@decorated_session).to receive(:sp_name).and_return(sp_name) + allow(view).to receive(:decorated_session).and_return(@decorated_session) + allow(view).to receive(:flow_session).and_return(flow_session) + allow(view).to receive(:user_fully_authenticated?).and_return(user_fully_authenticated) + allow(view).to receive(:user_signing_up?).and_return(false) + allow(view).to receive(:url_for).and_wrap_original do |method, *args, &block| + method.call(*args, &block) + rescue + '' + end + render + end + + context 'in doc auth with an authenticated user' do + let(:need_irs_reproofing) { false } + + before do + allow(user).to receive(:reproof_for_irs?).and_return(need_irs_reproofing) + assign(:current_user, user) + + render + end + + it 'does not render the IRS reproofing explanation' do + expect(rendered).not_to have_text(t('doc_auth.info.irs_reproofing_explanation')) + end + + it 'renders a link to return to the SP' do + expect(rendered).to have_link(t('links.cancel')) + end + + context 'when trying to log in to the IRS' do + let(:need_irs_reproofing) { true } + + it 'renders the IRS reproofing explanation' do + expect(rendered).to have_text(t('doc_auth.info.irs_reproofing_explanation')) + end + end + end + + context 'during the acuant maintenance window' do + let(:start) { Time.zone.parse('2020-01-01T00:00:00Z') } + let(:now) { Time.zone.parse('2020-01-01T12:00:00Z') } + let(:finish) { Time.zone.parse('2020-01-01T23:59:59Z') } + + before do + allow(IdentityConfig.store).to receive(:acuant_maintenance_window_start).and_return(start) + allow(IdentityConfig.store).to receive(:acuant_maintenance_window_finish).and_return(finish) + end + + around do |ex| + travel_to(now) { ex.run } + end + + it 'renders the warning banner but no other content' do + render + + expect(rendered).to have_content('We are currently under maintenance') + expect(rendered).to_not have_content(t('doc_auth.headings.welcome')) + end + end + + it 'includes code to track clicks on the consent checkbox' do + selector = [ + 'lg-click-observer[event-name="IdV: consent checkbox toggled"]', + '[name="doc_auth[ial2_consent_given]"]', + ].join ' ' + + expect(rendered).to have_css(selector) + end + + it 'renders a link to help center article' do + expect(rendered).to have_link( + t('doc_auth.info.getting_started_learn_more'), + href: help_center_redirect_path( + category: 'verify-your-identity', + article: 'how-to-verify-your-identity', + flow: :idv, + step: :getting_started, + location: 'intro_paragraph', + ), + ) + end + + it 'renders a link to the privacy & security page' do + expect(rendered).to have_link( + t('doc_auth.getting_started.instructions.learn_more'), + href: policy_redirect_url(flow: :idv, step: :getting_started, location: :consent), + ) + end +end