diff --git a/app/components/phone_input_component.rb b/app/components/phone_input_component.rb index 0a512043747..c718ac00a40 100644 --- a/app/components/phone_input_component.rb +++ b/app/components/phone_input_component.rb @@ -57,7 +57,6 @@ def strings { country_code_label: t('components.phone_input.country_code_label'), invalid_phone: t('errors.messages.invalid_phone_number'), - country_constraint_usa: t('errors.messages.phone_country_constraint_usa'), unsupported_country: unsupported_country_string, } end diff --git a/app/controllers/idv/otp_delivery_method_controller.rb b/app/controllers/idv/otp_delivery_method_controller.rb index 19ab4f28c58..60a7cadb82e 100644 --- a/app/controllers/idv/otp_delivery_method_controller.rb +++ b/app/controllers/idv/otp_delivery_method_controller.rb @@ -12,7 +12,7 @@ class OtpDeliveryMethodController < ApplicationController def new analytics.idv_phone_otp_delivery_selection_visit - render :new, locals: { gpo_letter_available: gpo_letter_available } + render :new, locals: view_locals end def create @@ -25,6 +25,23 @@ def create private + attr_reader :idv_phone + + def view_locals + { + gpo_letter_available: gpo_letter_available, + phone_number_capabilities: phone_number_capabilities, + } + end + + def phone_number_capabilities + PhoneNumberCapabilities.new(idv_phone, phone_confirmed: user_phone?) + end + + def user_phone? + MfaContext.new(current_user).phone_configurations.any? { |config| config.phone == idv_phone } + end + def confirm_phone_step_complete redirect_to idv_phone_url if idv_session.vendor_phone_confirmation != true end @@ -44,7 +61,7 @@ def otp_delivery_selection_params def render_new_with_error_message flash[:error] = t('idv.errors.unsupported_otp_delivery_method') - render :new, locals: { gpo_letter_available: gpo_letter_available } + render :new, locals: view_locals end def send_phone_confirmation_otp_and_handle_result diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index 875b565576c..b413297c86c 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -100,7 +100,8 @@ def set_idv_form @idv_form = Idv::PhoneForm.new( user: current_user, previous_params: idv_session.previous_phone_step_params, - allowed_countries: ['US'], + allowed_countries: + PhoneNumberCapabilities::ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES, ) end diff --git a/app/forms/idv/phone_form.rb b/app/forms/idv/phone_form.rb index 8d476d1a185..6cc3af2b40c 100644 --- a/app/forms/idv/phone_form.rb +++ b/app/forms/idv/phone_form.rb @@ -54,19 +54,16 @@ def initial_phone_value(input_phone) def validate_valid_phone_for_allowed_countries return if valid_phone_for_allowed_countries?(phone) - - if allowed_countries == ['US'] - errors.add(:phone, :must_have_us_country_code, type: :must_have_us_country_code) - else - errors.add(:phone, :improbable_phone, type: :improbable_phone) - end + errors.add(:phone, :improbable_phone, type: :improbable_phone) end def validate_phone_delivery_methods return unless valid_phone_for_allowed_countries?(phone) capabilities = PhoneNumberCapabilities.new(phone, phone_confirmed: user_phone?(phone)) - unsupported_delivery_methods(capabilities).each do |delivery_method| + unsupported_methods = unsupported_delivery_methods(capabilities) + return if unsupported_methods.count != delivery_methods.count + unsupported_methods.each do |delivery_method| errors.add( :phone, I18n.t( @@ -80,7 +77,7 @@ def validate_phone_delivery_methods def valid_phone_for_allowed_countries?(phone) if allowed_countries.present? - allowed_countries.all? { |country| Phonelib.valid_for_country?(phone, country) } + allowed_countries.any? { |country| Phonelib.valid_for_country?(phone, country) } else Phonelib.valid?(phone) end diff --git a/app/javascript/packages/phone-input/index.js b/app/javascript/packages/phone-input/index.js index e66726c0293..7fee5ce34ae 100644 --- a/app/javascript/packages/phone-input/index.js +++ b/app/javascript/packages/phone-input/index.js @@ -10,7 +10,6 @@ import { replaceVariables } from '@18f/identity-i18n'; * * @prop {string=} country_code_label * @prop {string=} invalid_phone - * @prop {string=} country_constraint_usa * @prop {string=} unsupported_country */ @@ -46,32 +45,34 @@ export class PhoneInput extends HTMLElement { countryCodePairs = {}; connectedCallback() { - /** @type {HTMLInputElement?} */ - this.textInput = this.querySelector('.phone-input__number'); - /** @type {HTMLSelectElement?} */ - this.codeInput = this.querySelector('.phone-input__international-code'); - this.codeWrapper = this.querySelector('.phone-input__international-code-wrapper'); - this.exampleText = this.querySelector('.phone-input__example'); - - try { - this.deliveryMethods = JSON.parse(this.dataset.deliveryMethods || ''); - this.countryCodePairs = JSON.parse(this.dataset.translatedCountryCodeNames || ''); - } catch {} - - if (!this.textInput || !this.codeInput) { - return; - } + if (this.isConnected) { + /** @type {HTMLInputElement?} */ + this.textInput = this.querySelector('.phone-input__number'); + /** @type {HTMLSelectElement?} */ + this.codeInput = this.querySelector('.phone-input__international-code'); + this.codeWrapper = this.querySelector('.phone-input__international-code-wrapper'); + this.exampleText = this.querySelector('.phone-input__example'); + + try { + this.deliveryMethods = JSON.parse(this.dataset.deliveryMethods || ''); + this.countryCodePairs = JSON.parse(this.dataset.translatedCountryCodeNames || ''); + } catch {} - this.iti = this.initializeIntlTelInput(); + if (!this.textInput || !this.codeInput) { + return; + } + + this.iti = this.initializeIntlTelInput(); - this.textInput.addEventListener('countrychange', () => this.syncCountryChangeToCodeInput()); - this.textInput.addEventListener('input', () => this.validate()); - this.codeInput.addEventListener('change', () => this.formatTextInput()); - this.codeInput.addEventListener('change', () => this.setExampleNumber()); - this.codeInput.addEventListener('change', () => this.validate()); + this.textInput.addEventListener('countrychange', () => this.syncCountryChangeToCodeInput()); + this.textInput.addEventListener('input', () => this.validate()); + this.codeInput.addEventListener('change', () => this.formatTextInput()); + this.codeInput.addEventListener('change', () => this.setExampleNumber()); + this.codeInput.addEventListener('change', () => this.validate()); - this.setExampleNumber(); - this.validate(); + this.setExampleNumber(); + this.validate(); + } } get selectedOption() { @@ -160,7 +161,7 @@ export class PhoneInput extends HTMLElement { } validate() { - const { textInput, codeInput, supportedCountryCodes, selectedOption } = this; + const { textInput, codeInput, selectedOption } = this; if (!textInput || !codeInput || !selectedOption) { return; } @@ -173,14 +174,9 @@ export class PhoneInput extends HTMLElement { return; } - const isInvalidCountry = - supportedCountryCodes?.length === 1 && !isValidNumberForRegion(phoneNumber, countryCode); + const isInvalidCountry = !isValidNumberForRegion(phoneNumber, countryCode); if (isInvalidCountry) { - if (countryCode === 'US') { - textInput.setCustomValidity(this.strings.country_constraint_usa || ''); - } else { - textInput.setCustomValidity(this.strings.invalid_phone || ''); - } + textInput.setCustomValidity(this.strings.invalid_phone || ''); } const isInvalidPhoneNumber = !isPhoneValid(phoneNumber, countryCode); diff --git a/app/javascript/packages/phone-input/index.spec.js b/app/javascript/packages/phone-input/index.spec.js index 86891e53940..e5731266e03 100644 --- a/app/javascript/packages/phone-input/index.spec.js +++ b/app/javascript/packages/phone-input/index.spec.js @@ -43,7 +43,6 @@ describe('PhoneInput', () => { { "country_code_label": "Country code", "invalid_phone": "Phone number is not valid", - "country_constraint_usa": "Must be a U.S. phone number", "unsupported_country": "We are unable to verify phone numbers from %{location}" } @@ -133,25 +132,13 @@ describe('PhoneInput', () => { }); it('validates phone from region', async () => { - const input = createAndConnectElement({ isSingleOption: true }); + const input = createAndConnectElement({ isNonUSSingleOption: true }); /** @type {HTMLInputElement} */ const phoneNumber = getByLabelText(input, 'Phone number'); - await userEvent.type(phoneNumber, '306-555-1234'); - expect(phoneNumber.validationMessage).to.equal('Must be a U.S. phone number'); - }); - - context('with non-U.S. single option', () => { - it('validates phone from region', async () => { - const input = createAndConnectElement({ isNonUSSingleOption: true }); - - /** @type {HTMLInputElement} */ - const phoneNumber = getByLabelText(input, 'Phone number'); - - await userEvent.type(phoneNumber, '513-555-1234'); - expect(phoneNumber.validationMessage).to.equal('Phone number is not valid'); - }); + await userEvent.type(phoneNumber, '513-555-1234'); + expect(phoneNumber.validationMessage).to.equal('Phone number is not valid'); }); }); diff --git a/app/services/idv/phone_step.rb b/app/services/idv/phone_step.rb index 521e8fbe6ff..b5c3a99e7d8 100644 --- a/app/services/idv/phone_step.rb +++ b/app/services/idv/phone_step.rb @@ -120,8 +120,13 @@ def start_phone_confirmation_session end def extra_analytics_attributes + parsed_phone = Phonelib.parse(applicant[:phone]) + { vendor: idv_result.except(:errors, :success), + area_code: parsed_phone.area_code, + country_code: parsed_phone.country, + phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), } end diff --git a/app/services/phone_number_capabilities.rb b/app/services/phone_number_capabilities.rb index cd63da50ab7..32a24b65faa 100644 --- a/app/services/phone_number_capabilities.rb +++ b/app/services/phone_number_capabilities.rb @@ -6,6 +6,8 @@ def self.load_config end INTERNATIONAL_CODES = load_config.freeze + ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES = + IdentityConfig.store.address_identity_proofing_supported_country_codes attr_reader :phone, :phone_confirmed diff --git a/app/views/idv/otp_delivery_method/new.html.erb b/app/views/idv/otp_delivery_method/new.html.erb index a8bdd01f95c..955324501a1 100644 --- a/app/views/idv/otp_delivery_method/new.html.erb +++ b/app/views/idv/otp_delivery_method/new.html.erb @@ -24,33 +24,37 @@ ) do |f| %>
- <%= radio_button_tag( - 'otp_delivery_preference', - :sms, - false, - disabled: VendorStatus.new.vendor_outage?(:sms), - class: 'usa-radio__input usa-radio__input--tile', - ) %> - + <% if phone_number_capabilities.supports_sms? %> + <%= radio_button_tag( + 'otp_delivery_preference', + :sms, + false, + disabled: VendorStatus.new.vendor_outage?(:sms), + class: 'usa-radio__input usa-radio__input--tile', + ) %> + + <% end %> - <%= radio_button_tag( - 'otp_delivery_preference', - :voice, - false, - disabled: VendorStatus.new.vendor_outage?(:voice), - class: 'usa-radio__input usa-radio__input--tile', - ) %> - + <% if phone_number_capabilities.supports_voice? %> + <%= radio_button_tag( + 'otp_delivery_preference', + :voice, + false, + disabled: VendorStatus.new.vendor_outage?(:voice), + class: 'usa-radio__input usa-radio__input--tile', + ) %> + + <% end %>
<%= submit_tag(t('idv.buttons.send_confirmation_code'), class: 'usa-button usa-button--big usa-button--wide') %> diff --git a/app/views/idv/phone/new.html.erb b/app/views/idv/phone/new.html.erb index 14dcd6debf0..f65733d4750 100644 --- a/app/views/idv/phone/new.html.erb +++ b/app/views/idv/phone/new.html.erb @@ -56,7 +56,7 @@ ) do |f| %> <%= render PhoneInputComponent.new( form: f, - allowed_countries: ['US'], + allowed_countries: @idv_form.allowed_countries, required: true, class: 'margin-bottom-4', ) %> diff --git a/config/application.yml.default b/config/application.yml.default index e75c698e042..5d9ec142536 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -41,6 +41,7 @@ acuant_upload_image_timeout: 1.0 acuant_get_results_timeout: 1.0 acuant_create_document_timeout: 1.0 add_email_link_valid_for_hours: 24 +address_identity_proofing_supported_country_codes: '["AS", "GU", "MP", "PR", "US", "VI"]' asset_host: '' async_wait_timeout_seconds: 60 async_stale_job_timeout_seconds: 300 diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml index 1c887dbc1a7..a58141d4a61 100644 --- a/config/locales/errors/en.yml +++ b/config/locales/errors/en.yml @@ -67,15 +67,12 @@ en: invalid_voice_number: Invalid phone number. Check that you’ve entered the correct country code or area code. missing_field: Please fill in this field. - must_have_us_country_code: Invalid phone number. Please make sure you enter a - phone number with a U.S. country code. no_pending_profile: No profile is waiting for verification not_a_number: is not a number otp_failed: Your security code failed to send. password_incorrect: Incorrect password personal_key_incorrect: Incorrect personal key phone_confirmation_throttled: You tried too many times, please try again in %{timeout}. - phone_country_constraint_usa: Must be a U.S. phone number phone_duplicate: This account is already using the phone number you entered as an authenticator. Please use a different phone number. phone_required: Phone number is required diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml index 4304186aa3d..40842a7d7f5 100644 --- a/config/locales/errors/es.yml +++ b/config/locales/errors/es.yml @@ -69,15 +69,12 @@ es: invalid_voice_number: Numero de telefono invalido. Verifique que haya ingresado el código de país o de área correcto. missing_field: Por favor, rellene este campo. - must_have_us_country_code: Número de teléfono no válido. Asegúrate de introducir - un número de teléfono con un código país de EE. UU. no_pending_profile: Ningún perfil está esperando verificación not_a_number: no es un número otp_failed: Se produjo un error al enviar el código de seguridad. password_incorrect: La contraseña es incorrecta personal_key_incorrect: La clave personal es incorrecta phone_confirmation_throttled: Lo intentaste muchas veces, vuelve a intentarlo en %{timeout}. - phone_country_constraint_usa: Debe ser un número telefónico de los Estados Unidos phone_duplicate: Esta cuenta ya está utilizando el número de teléfono que ingresó como autenticador. Por favor, use un número de teléfono diferente. diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml index 62a2a1f873f..2a5a3597c3c 100644 --- a/config/locales/errors/fr.yml +++ b/config/locales/errors/fr.yml @@ -76,15 +76,12 @@ fr: invalid_voice_number: Numéro de téléphone invalide. Vérifiez que vous avez entré le bon indicatif international ou régional. missing_field: Veuillez remplir ce champ. - must_have_us_country_code: Numéro de téléphone non valide. Veuillez vous assurer - de saisir un numéro de téléphone avec un code pays des États-Unis. no_pending_profile: Aucun profil en attente de vérification not_a_number: N’est pas un nombre otp_failed: Échec de l’envoi de votre code de sécurité. password_incorrect: Mot de passe incorrect personal_key_incorrect: Clé personnelle incorrecte phone_confirmation_throttled: Vous avez essayé plusieurs fois, essayez à nouveau dans %{timeout}. - phone_country_constraint_usa: Dois être un numéro de téléphone américain phone_duplicate: Ce compte utilise déjà le numéro de téléphone que vous avez entré en tant qu’authentificateur. Veuillez utiliser un numéro de téléphone différent. diff --git a/lib/identity_config.rb b/lib/identity_config.rb index f3549883d93..c6509c89a8a 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -100,6 +100,7 @@ def self.build_store(config_map) config.add(:acuant_get_results_timeout, type: :float) config.add(:acuant_create_document_timeout, type: :float) config.add(:add_email_link_valid_for_hours, type: :integer) + config.add(:address_identity_proofing_supported_country_codes, type: :json) config.add(:asset_host, type: :string) config.add(:async_wait_timeout_seconds, type: :integer) config.add(:async_stale_job_timeout_seconds, type: :integer) diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index 7224b4a5b63..e54240baf9c 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -143,14 +143,14 @@ it 'renders #new' do put :create, params: { idv_phone_form: { phone: '703' } } - expect(flash[:error]).to eq t('errors.messages.must_have_us_country_code') + expect(flash[:error]).to eq t('errors.messages.improbable_phone') expect(response).to render_template(:new) end it 'disallows non-US numbers' do put :create, params: { idv_phone_form: { phone: international_phone } } - expect(flash[:error]).to eq t('errors.messages.must_have_us_country_code') + expect(flash[:error]).to eq t('errors.messages.improbable_phone') expect(response).to render_template(:new) end @@ -162,10 +162,10 @@ result = { success: false, errors: { - phone: [t('errors.messages.must_have_us_country_code')], + phone: [t('errors.messages.improbable_phone')], }, error_details: { - phone: [:must_have_us_country_code], + phone: [:improbable_phone], }, pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], country_code: nil, @@ -189,7 +189,7 @@ success: false, phone_number: '703', failure_reason: { - phone: [t('errors.messages.must_have_us_country_code')], + phone: [t('errors.messages.improbable_phone')], }, ) end @@ -314,6 +314,7 @@ end it 'tracks event with valid phone' do + proofing_phone = Phonelib.parse(good_phone) user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now }) stub_verify_steps_one_and_two(user) @@ -324,6 +325,9 @@ success: true, new_phone_added: true, errors: {}, + phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), + country_code: proofing_phone.country, + area_code: proofing_phone.area_code, pii_like_keypaths: [[:errors, :phone], [:context, :stages, :address]], vendor: { vendor_name: 'AddressMock', @@ -362,6 +366,7 @@ end it 'tracks event with invalid phone' do + proofing_phone = Phonelib.parse(bad_phone) user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now }) stub_verify_steps_one_and_two(user) @@ -371,6 +376,9 @@ result = { success: false, new_phone_added: true, + phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), + country_code: proofing_phone.country, + area_code: proofing_phone.area_code, errors: { phone: ['The phone number could not be verified.'], }, diff --git a/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb index d1ebb9ea0f5..09f9f942a63 100644 --- a/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb +++ b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb @@ -71,7 +71,7 @@ it 'displays an error message' do expect(Telephony).to_not receive(:send_confirmation_otp) - expect(page).to have_content(t('errors.messages.phone_country_constraint_usa')) + expect(page).to have_content(t('errors.messages.invalid_phone_number')) expect(current_path).to eq(idv_phone_path) end end diff --git a/spec/forms/idv/phone_form_spec.rb b/spec/forms/idv/phone_form_spec.rb index ffdb92675c3..1b7fc5e6bd2 100644 --- a/spec/forms/idv/phone_form_spec.rb +++ b/spec/forms/idv/phone_form_spec.rb @@ -89,7 +89,7 @@ end context 'with specific allowed countries' do - let(:allowed_countries) { ['MP'] } + let(:allowed_countries) { ['MP', 'US'] } it 'validates to only allow numbers from permitted countries' do invalid_phones = ['+81 54 354 3643', '+12423270143'] @@ -116,7 +116,7 @@ result = subject.submit(phone: phone) expect(result.success?).to eq(false) - expect(result.errors[:phone]).to include(t('errors.messages.must_have_us_country_code')) + expect(result.errors[:phone]).to include(t('errors.messages.improbable_phone')) end valid_phones = ['7035551234'] valid_phones.each do |phone| @@ -154,5 +154,44 @@ end end end + + context 'with unsupported delivery method' do + let(:unsupported_delivery_methods) { [] } + let(:result) { subject.submit(params) } + + before do + allow(subject).to receive(:unsupported_delivery_methods). + and_return(unsupported_delivery_methods) + end + + context 'with one unsupported delivery method' do + let(:unsupported_delivery_methods) { [:sms] } + + it 'is valid' do + expect(result.success?).to eq(true) + expect(result.errors).to eq({}) + end + end + + context 'with all delivery methods unsupported' do + let(:unsupported_delivery_methods) { [:sms, :voice] } + + it 'is invalid' do + expect(result.success?).to eq(false) + expect(result.errors).to eq( + phone: [ + t( + 'two_factor_authentication.otp_delivery_preference.sms_unsupported', + location: 'United States', + ), + t( + 'two_factor_authentication.otp_delivery_preference.voice_unsupported', + location: 'United States', + ), + ], + ) + end + end + end end end diff --git a/spec/services/idv/phone_step_spec.rb b/spec/services/idv/phone_step_spec.rb index eb219503f70..42e004100b4 100644 --- a/spec/services/idv/phone_step_spec.rb +++ b/spec/services/idv/phone_step_spec.rb @@ -49,7 +49,11 @@ let(:throttle) { Throttle.new(throttle_type: :proof_address, user: user) } it 'succeeds with good params' do + proofing_phone = Phonelib.parse(good_phone) extra = { + phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), + country_code: proofing_phone.country, + area_code: proofing_phone.area_code, vendor: { vendor_name: 'AddressMock', exception: nil, @@ -78,7 +82,11 @@ end it 'fails with bad params' do + proofing_phone = Phonelib.parse(bad_phone) extra = { + phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), + country_code: proofing_phone.country, + area_code: proofing_phone.area_code, vendor: { vendor_name: 'AddressMock', exception: nil, diff --git a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb b/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb index 97b0accbd72..46d4585739d 100644 --- a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb +++ b/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb @@ -3,8 +3,17 @@ describe 'idv/otp_delivery_method/new.html.erb' do let(:gpo_letter_available) { false } let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS } + let(:supports_sms) { true } + let(:supports_voice) { true } before do + phone_number_capabilities = instance_double( + PhoneNumberCapabilities, + supports_sms?: supports_sms, + supports_voice?: supports_voice, + ) + + allow(view).to receive(:phone_number_capabilities).and_return(phone_number_capabilities) allow(view).to receive(:user_signing_up?).and_return(false) allow(view).to receive(:user_fully_authenticated?).and_return(true) allow(view).to receive(:gpo_letter_available).and_return(gpo_letter_available) @@ -46,4 +55,27 @@ expect(rendered).to have_field('otp_delivery_preference', with: :sms, disabled: true) end end + + it 'renders sms and voice options' do + expect(rendered).to have_field('otp_delivery_preference', with: :voice) + expect(rendered).to have_field('otp_delivery_preference', with: :sms) + end + + context 'without sms support' do + let(:supports_sms) { false } + + it 'renders voice option' do + expect(rendered).to have_field('otp_delivery_preference', with: :voice) + expect(rendered).not_to have_field('otp_delivery_preference', with: :sms) + end + end + + context 'without voice support' do + let(:supports_voice) { false } + + it 'renders sms option' do + expect(rendered).not_to have_field('otp_delivery_preference', with: :voice) + expect(rendered).to have_field('otp_delivery_preference', with: :sms) + end + end end