From 52cb3839bbc3e194147ebb2d9c31496cd770358b Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Wed, 22 Nov 2017 12:35:54 -0600 Subject: [PATCH] Get rid of vendor validator abstraction **Why**: The vendor validator made it difficult to clearly see where and how the logic that commuicates with the proofer gem was. It will also be less helpful in a world where we have multiple vendors per proofing step. This commit tears the vendor validator down in favor of a unique job class for each step. That job class then invokes the proofer gem code itself. --- .reek | 3 +- app/controllers/verify/finance_controller.rb | 5 +- app/controllers/verify/phone_controller.rb | 5 +- app/controllers/verify/sessions_controller.rb | 5 +- app/jobs/idv/finance_job.rb | 9 ++ app/jobs/idv/phone_job.rb | 9 ++ app/jobs/idv/profile_job.rb | 23 ++++ app/jobs/idv/proofer_job.rb | 57 ++++++++++ app/jobs/vendor_validator_job.rb | 49 --------- app/services/idv/financials_validator.rb | 11 -- app/services/idv/phone_validator.rb | 11 -- app/services/idv/profile_validator.rb | 15 --- app/services/idv/submit_idv_job.rb | 50 +++++++++ app/services/idv/vendor_result.rb | 4 +- app/services/idv/vendor_validator.rb | 42 ------- app/services/submit_idv_job.rb | 37 ------- .../verify/finance_controller_spec.rb | 3 +- .../verify/phone_controller_spec.rb | 5 +- spec/features/idv/failed_job_spec.rb | 2 +- spec/features/idv/flow_spec.rb | 15 ++- spec/jobs/idv/finance_job_spec.rb | 75 +++++++++++++ spec/jobs/idv/phone_job_spec.rb | 75 +++++++++++++ spec/jobs/idv/profile_job_spec.rb | 75 +++++++++++++ spec/jobs/vendor_validator_job_spec.rb | 82 -------------- spec/rails_helper.rb | 6 +- .../services/idv/financials_validator_spec.rb | 54 --------- spec/services/idv/phone_validator_spec.rb | 55 ---------- spec/services/idv/submit_idv_job_spec.rb | 103 ++++++++++++++++++ spec/services/submit_idv_job_spec.rb | 58 ---------- 29 files changed, 508 insertions(+), 435 deletions(-) create mode 100644 app/jobs/idv/finance_job.rb create mode 100644 app/jobs/idv/phone_job.rb create mode 100644 app/jobs/idv/profile_job.rb create mode 100644 app/jobs/idv/proofer_job.rb delete mode 100644 app/jobs/vendor_validator_job.rb delete mode 100644 app/services/idv/financials_validator.rb delete mode 100644 app/services/idv/phone_validator.rb delete mode 100644 app/services/idv/profile_validator.rb create mode 100644 app/services/idv/submit_idv_job.rb delete mode 100644 app/services/idv/vendor_validator.rb delete mode 100644 app/services/submit_idv_job.rb create mode 100644 spec/jobs/idv/finance_job_spec.rb create mode 100644 spec/jobs/idv/phone_job_spec.rb create mode 100644 spec/jobs/idv/profile_job_spec.rb delete mode 100644 spec/jobs/vendor_validator_job_spec.rb delete mode 100644 spec/services/idv/financials_validator_spec.rb delete mode 100644 spec/services/idv/phone_validator_spec.rb create mode 100644 spec/services/idv/submit_idv_job_spec.rb delete mode 100644 spec/services/submit_idv_job_spec.rb diff --git a/.reek b/.reek index 747f8d7e29c..296fde376da 100644 --- a/.reek +++ b/.reek @@ -51,7 +51,7 @@ NilCheck: LongParameterList: exclude: - IdentityLinker#optional_attributes - - VendorValidatorJob#perform + - Idv::ProoferJob#perform - Idv::VendorResult#initialize RepeatedConditional: exclude: @@ -66,6 +66,7 @@ TooManyInstanceVariables: - OpenidConnectAuthorizeForm - OpenidConnectRedirector - Idv::VendorResult + - Idv::ProoferJob TooManyStatements: max_statements: 6 exclude: diff --git a/app/controllers/verify/finance_controller.rb b/app/controllers/verify/finance_controller.rb index 0f9513a0762..e283f32bffe 100644 --- a/app/controllers/verify/finance_controller.rb +++ b/app/controllers/verify/finance_controller.rb @@ -41,11 +41,10 @@ def show private def submit_idv_job - SubmitIdvJob.new( - vendor_validator_class: Idv::FinancialsValidator, + Idv::SubmitIdvJob.new( idv_session: idv_session, vendor_params: vendor_params - ).call + ).submit_finance_job end def step_name diff --git a/app/controllers/verify/phone_controller.rb b/app/controllers/verify/phone_controller.rb index 3e2e7141e4d..970ff7b0fbf 100644 --- a/app/controllers/verify/phone_controller.rb +++ b/app/controllers/verify/phone_controller.rb @@ -53,11 +53,10 @@ def phone_confirmation_required? end def submit_idv_job - SubmitIdvJob.new( - vendor_validator_class: Idv::PhoneValidator, + Idv::SubmitIdvJob.new( idv_session: idv_session, vendor_params: idv_session.params[:phone] - ).call + ).submit_phone_job end def step_name diff --git a/app/controllers/verify/sessions_controller.rb b/app/controllers/verify/sessions_controller.rb index 4d4750aa5e1..b4ce9ed9008 100644 --- a/app/controllers/verify/sessions_controller.rb +++ b/app/controllers/verify/sessions_controller.rb @@ -51,11 +51,10 @@ def destroy private def submit_idv_job - SubmitIdvJob.new( - vendor_validator_class: Idv::ProfileValidator, + Idv::SubmitIdvJob.new( idv_session: idv_session, vendor_params: idv_session.vendor_params - ).call + ).submit_profile_job end def step_name diff --git a/app/jobs/idv/finance_job.rb b/app/jobs/idv/finance_job.rb new file mode 100644 index 00000000000..6a65b66aa24 --- /dev/null +++ b/app/jobs/idv/finance_job.rb @@ -0,0 +1,9 @@ +module Idv + class FinanceJob < ProoferJob + def verify_identity_with_vendor + confirmation = agent.submit_financials(vendor_params, vendor_session_id) + result = extract_result(confirmation) + store_result(result) + end + end +end diff --git a/app/jobs/idv/phone_job.rb b/app/jobs/idv/phone_job.rb new file mode 100644 index 00000000000..32b888af61c --- /dev/null +++ b/app/jobs/idv/phone_job.rb @@ -0,0 +1,9 @@ +module Idv + class PhoneJob < ProoferJob + def verify_identity_with_vendor + confirmation = agent.submit_phone(vendor_params, vendor_session_id) + result = extract_result(confirmation) + store_result(result) + end + end +end diff --git a/app/jobs/idv/profile_job.rb b/app/jobs/idv/profile_job.rb new file mode 100644 index 00000000000..290bbee8198 --- /dev/null +++ b/app/jobs/idv/profile_job.rb @@ -0,0 +1,23 @@ +module Idv + class ProfileJob < ProoferJob + def verify_identity_with_vendor + resolution = agent.start(vendor_params) + result = extract_result(resolution) + store_result(result) + end + + private + + def extract_result(resolution) + vendor_resp = resolution.vendor_resp + + Idv::VendorResult.new( + success: resolution.success?, + errors: resolution.errors, + reasons: vendor_resp.reasons, + normalized_applicant: vendor_resp.normalized_applicant, + session_id: resolution.session_id + ) + end + end +end diff --git a/app/jobs/idv/proofer_job.rb b/app/jobs/idv/proofer_job.rb new file mode 100644 index 00000000000..c131926b0fc --- /dev/null +++ b/app/jobs/idv/proofer_job.rb @@ -0,0 +1,57 @@ +module Idv + class ProoferJob < ApplicationJob + queue_as :idv + + attr_reader :result_id, :vendor, :vendor_params, :applicant, :vendor_session_id + + def perform(result_id:, vendor:, vendor_params:, applicant_json:, vendor_session_id: nil) + @result_id = result_id + @vendor = vendor.to_sym + @vendor_params = vendor_params + @applicant = applicant_from_json(applicant_json) + @vendor_session_id = vendor_session_id + perform_identity_proofing + end + + def verify_identity_with_vendor + raise NotImplementedError, "subclass must implement #{__method__}" + end + + private + + def agent + Idv::Agent.new(applicant: applicant, vendor: vendor) + end + + def applicant_from_json(applicant_json) + applicant_attributes = JSON.parse(applicant_json, symbolize_names: true) + Proofer::Applicant.new(applicant_attributes) + end + + def perform_identity_proofing + verify_identity_with_vendor + rescue StandardError + store_failed_job_result + raise + end + + def extract_result(confirmation) + vendor_resp = confirmation.vendor_resp + + Idv::VendorResult.new( + success: confirmation.success?, + errors: confirmation.errors, + reasons: vendor_resp.reasons + ) + end + + def store_failed_job_result + job_failed_result = Idv::VendorResult.new(errors: { job_failed: true }) + VendorValidatorResultStorage.new.store(result_id: result_id, result: job_failed_result) + end + + def store_result(vendor_result) + VendorValidatorResultStorage.new.store(result_id: result_id, result: vendor_result) + end + end +end diff --git a/app/jobs/vendor_validator_job.rb b/app/jobs/vendor_validator_job.rb deleted file mode 100644 index 4dd691aaf6c..00000000000 --- a/app/jobs/vendor_validator_job.rb +++ /dev/null @@ -1,49 +0,0 @@ -class VendorValidatorJob < ApplicationJob - queue_as :idv - - def perform(result_id:, vendor_validator_class:, vendor:, vendor_params:, applicant_json:, - vendor_session_id:) - vendor_validator = vendor_validator_class.constantize.new( - applicant: Proofer::Applicant.new(JSON.parse(applicant_json, symbolize_names: true)), - vendor: vendor.to_sym, - vendor_params: indifferent_access(vendor_params), - vendor_session_id: vendor_session_id - ) - - extract_result_and_store_if_job_passed(result_id, vendor_validator) - rescue StandardError - store_failed_job_result(result_id) - raise - end - - private - - def extract_result_and_store_if_job_passed(result_id, validator) - validator_result = extract_result(validator.result) - - VendorValidatorResultStorage.new.store(result_id: result_id, result: validator_result) - end - - def extract_result(result) - vendor_resp = result.vendor_resp - - Idv::VendorResult.new( - success: result.success?, - errors: result.errors, - reasons: vendor_resp.reasons, - normalized_applicant: vendor_resp.try(:normalized_applicant), - session_id: result.try(:session_id) - ) - end - - def store_failed_job_result(result_id) - job_failed_result = Idv::VendorResult.new(errors: { job_failed: true }) - - VendorValidatorResultStorage.new.store(result_id: result_id, result: job_failed_result) - end - - def indifferent_access(params) - return params if params.is_a?(String) - params.with_indifferent_access - end -end diff --git a/app/services/idv/financials_validator.rb b/app/services/idv/financials_validator.rb deleted file mode 100644 index 187dcabd348..00000000000 --- a/app/services/idv/financials_validator.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Idv - class FinancialsValidator < VendorValidator - private - - def try_submit - try_agent_action do - idv_agent.submit_financials(vendor_params, vendor_session_id) - end - end - end -end diff --git a/app/services/idv/phone_validator.rb b/app/services/idv/phone_validator.rb deleted file mode 100644 index 7e4b26cc009..00000000000 --- a/app/services/idv/phone_validator.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Idv - class PhoneValidator < VendorValidator - private - - def try_submit - try_agent_action do - idv_agent.submit_phone(vendor_params, vendor_session_id) - end - end - end -end diff --git a/app/services/idv/profile_validator.rb b/app/services/idv/profile_validator.rb deleted file mode 100644 index f3c72a064e3..00000000000 --- a/app/services/idv/profile_validator.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Idv - class ProfileValidator < VendorValidator - def result - @_result ||= try_start - end - - private - - def try_start - try_agent_action do - idv_agent.start(vendor_params) - end - end - end -end diff --git a/app/services/idv/submit_idv_job.rb b/app/services/idv/submit_idv_job.rb new file mode 100644 index 00000000000..f18e1962788 --- /dev/null +++ b/app/services/idv/submit_idv_job.rb @@ -0,0 +1,50 @@ +module Idv + class SubmitIdvJob + def initialize(idv_session:, vendor_params:) + @idv_session = idv_session + @vendor_params = vendor_params + end + + def submit_profile_job + update_idv_session + ProfileJob.perform_later(proofer_job_params) + end + + def submit_finance_job + update_idv_session + FinanceJob.perform_later(proofer_job_params) + end + + def submit_phone_job + update_idv_session + PhoneJob.perform_later(proofer_job_params) + end + + private + + attr_reader :idv_session, :vendor_params + + def proofer_job_params + { + result_id: result_id, + vendor: vendor.to_s, + vendor_params: vendor_params, + vendor_session_id: idv_session.vendor_session_id, + applicant_json: idv_session.applicant.to_json, + } + end + + def result_id + @_result_id ||= SecureRandom.uuid + end + + def update_idv_session + idv_session.async_result_id = result_id + idv_session.async_result_started_at = Time.zone.now.to_i + end + + def vendor + idv_session.vendor || Idv::Vendor.new.pick + end + end +end diff --git a/app/services/idv/vendor_result.rb b/app/services/idv/vendor_result.rb index 09938789358..a1a528b2b61 100644 --- a/app/services/idv/vendor_result.rb +++ b/app/services/idv/vendor_result.rb @@ -22,11 +22,11 @@ def initialize(success: nil, errors: {}, reasons: [], session_id: nil, end def success? - success + success == true end def timed_out? - timed_out + timed_out == true end def job_failed? diff --git a/app/services/idv/vendor_validator.rb b/app/services/idv/vendor_validator.rb deleted file mode 100644 index 8b0c6222101..00000000000 --- a/app/services/idv/vendor_validator.rb +++ /dev/null @@ -1,42 +0,0 @@ -# abstract base class for proofing vendor validation -module Idv - class VendorValidator - attr_reader :applicant, :vendor, :vendor_params, :vendor_session_id - - def initialize(applicant:, vendor:, vendor_params:, vendor_session_id:) - @applicant = applicant - @vendor = vendor - @vendor_params = vendor_params - @vendor_session_id = vendor_session_id - end - - def result - @_result ||= try_submit - end - - private - - def idv_agent - @_agent ||= Idv::Agent.new( - applicant: applicant, - vendor: vendor - ) - end - - def try_agent_action - yield - rescue => err - err_msg = err.to_s - NewRelic::Agent.notice_error(err) - agent_error_resolution(err_msg) - end - - def agent_error_resolution(err_msg) - Proofer::Resolution.new( - success: false, - errors: { agent: [err_msg] }, - vendor_resp: OpenStruct.new(reasons: [err_msg]) - ) - end - end -end diff --git a/app/services/submit_idv_job.rb b/app/services/submit_idv_job.rb deleted file mode 100644 index 17f932b0870..00000000000 --- a/app/services/submit_idv_job.rb +++ /dev/null @@ -1,37 +0,0 @@ -class SubmitIdvJob - def initialize(vendor_validator_class:, idv_session:, vendor_params:) - @vendor_validator_class = vendor_validator_class - @idv_session = idv_session - @vendor_params = vendor_params - end - - def call - update_idv_session - - VendorValidatorJob.perform_later( - result_id: result_id, - vendor_validator_class: vendor_validator_class.to_s, - vendor: vendor.to_s, - vendor_params: vendor_params, - vendor_session_id: idv_session.vendor_session_id, - applicant_json: idv_session.applicant.to_json - ) - end - - private - - attr_reader :vendor_validator_class, :idv_session, :vendor_params - - def result_id - @_result_id ||= SecureRandom.uuid - end - - def update_idv_session - idv_session.async_result_id = result_id - idv_session.async_result_started_at = Time.zone.now.to_i - end - - def vendor - idv_session.vendor || Idv::Vendor.new.pick - end -end diff --git a/spec/controllers/verify/finance_controller_spec.rb b/spec/controllers/verify/finance_controller_spec.rb index e3cc4010c99..e0398e42e52 100644 --- a/spec/controllers/verify/finance_controller_spec.rb +++ b/spec/controllers/verify/finance_controller_spec.rb @@ -87,7 +87,7 @@ stub_analytics allow(@analytics).to receive(:track_event) - allow(Idv::FinancialsValidator).to receive(:new) + expect(Idv::SubmitIdvJob).to_not receive(:submit_finance_job) put :create, params: { idv_finance_form: { finance_type: :ccn, ccn: '123' } } @@ -99,7 +99,6 @@ expect(@analytics).to have_received(:track_event). with(Analytics::IDV_FINANCE_CONFIRMATION_FORM, result) expect(subject.idv_session.financials_confirmation).to be_falsy - expect(Idv::FinancialsValidator).to_not have_received(:new) end end diff --git a/spec/controllers/verify/phone_controller_spec.rb b/spec/controllers/verify/phone_controller_spec.rb index 144496a7712..f7490035920 100644 --- a/spec/controllers/verify/phone_controller_spec.rb +++ b/spec/controllers/verify/phone_controller_spec.rb @@ -77,7 +77,7 @@ end it 'tracks form error and does not make a vendor API call' do - expect(Idv::PhoneValidator).to_not receive(:new) + expect(Idv::SubmitIdvJob).to_not receive(:submit_phone_job) put :create, params: { idv_phone_form: { phone: '703' } } @@ -270,8 +270,7 @@ user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) stub_verify_steps_one_and_two(user) - expect(SubmitIdvJob).to receive(:new).with( - vendor_validator_class: Idv::PhoneValidator, + expect(Idv::SubmitIdvJob).to receive(:new).with( idv_session: subject.idv_session, vendor_params: normalized_phone ).and_call_original diff --git a/spec/features/idv/failed_job_spec.rb b/spec/features/idv/failed_job_spec.rb index 833d711199c..f2d6a76b015 100644 --- a/spec/features/idv/failed_job_spec.rb +++ b/spec/features/idv/failed_job_spec.rb @@ -3,7 +3,7 @@ feature 'IdV session' do include IdvHelper - context 'VendorValidatorJob raises an error', idv_job: true do + context 'Idv job raises an error', idv_job: true do it 'displays a warning that something went wrong' do sign_in_and_2fa_user diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index b13b4c90140..cd523383d7d 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -63,9 +63,22 @@ fill_out_idv_form_ok fill_in 'profile_first_name', with: first_name_to_trigger_exception + + expect(Idv::ProfileJob).to receive(:perform_now).and_wrap_original do |perform, *args| + exception_raised = false + begin + perform.call(*args) + rescue RuntimeError => err + expect(err.message).to eq('Failed to contact proofing vendor') + exception_raised = true + ensure + expect(exception_raised).to eq(true) + end + end + click_idv_continue - expect(current_path).to eq verify_session_result_path + expect(current_path).to eq(verify_session_result_path) expect(page).to have_css('.modal-warning', text: t('idv.modal.sessions.heading')) end diff --git a/spec/jobs/idv/finance_job_spec.rb b/spec/jobs/idv/finance_job_spec.rb new file mode 100644 index 00000000000..a6a8f933cb7 --- /dev/null +++ b/spec/jobs/idv/finance_job_spec.rb @@ -0,0 +1,75 @@ +require 'rails_helper' + +describe Idv::FinanceJob do + describe '#perform' do + let(:result_id) { SecureRandom.uuid } + let(:applicant_json) { { first_name: 'Jean-Luc', last_name: 'Picard' }.to_json } + let(:vendor_params) { { ccn: '12345678' } } + let(:vendor_session_id) { SecureRandom.uuid } + + context 'when verification succeeds' do + it 'should save a successful result' do + Idv::FinanceJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(true) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.reasons).to eq(['Good number']) + expect(result.errors).to eq({}) + end + end + + context 'when verification fails' do + let(:vendor_params) { { ccn: '00000000' } } + + it 'should save an unsuccessful result' do + Idv::FinanceJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.reasons).to eq(['Bad number']) + expect(result.errors).to eq(ccn: 'The ccn could not be verified.') + end + end + + context 'when the idv agent raises' do + before do + agent = instance_double(Idv::Agent) + allow(agent).to receive(:submit_financials).and_raise(RuntimeError, '🔥🔥🔥') + allow(Idv::Agent).to receive(:new).and_return(agent) + end + + it 'should rescue from errors and save a failed job result' do + expect do + Idv::FinanceJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + end.to raise_error(RuntimeError, '🔥🔥🔥') + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(true) + end + end + end +end diff --git a/spec/jobs/idv/phone_job_spec.rb b/spec/jobs/idv/phone_job_spec.rb new file mode 100644 index 00000000000..01bc3ede8e4 --- /dev/null +++ b/spec/jobs/idv/phone_job_spec.rb @@ -0,0 +1,75 @@ +require 'rails_helper' + +describe Idv::PhoneJob do + describe '#perform' do + let(:result_id) { SecureRandom.uuid } + let(:applicant_json) { { first_name: 'Jean-Luc', last_name: 'Picard' }.to_json } + let(:vendor_params) { '5555550000' } + let(:vendor_session_id) { SecureRandom.uuid } + + context 'when verification succeeds' do + it 'should save a successful result' do + Idv::PhoneJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(true) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.reasons).to eq(['Good number']) + expect(result.errors).to eq({}) + end + end + + context 'when verification fails' do + let(:vendor_params) { '5555555555' } + + it 'should save an unsuccessful result' do + Idv::PhoneJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.reasons).to eq(['Bad number']) + expect(result.errors).to eq(phone: 'The phone number could not be verified.') + end + end + + context 'when the idv agent raises' do + before do + agent = instance_double(Idv::Agent) + allow(agent).to receive(:submit_phone).and_raise(RuntimeError, '🔥🔥🔥') + allow(Idv::Agent).to receive(:new).and_return(agent) + end + + it 'should rescue from errors and save a failed job result' do + expect do + Idv::PhoneJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json, + vendor_session_id: vendor_session_id + ) + end.to raise_error(RuntimeError, '🔥🔥🔥') + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(true) + end + end + end +end diff --git a/spec/jobs/idv/profile_job_spec.rb b/spec/jobs/idv/profile_job_spec.rb new file mode 100644 index 00000000000..8764ede5ac9 --- /dev/null +++ b/spec/jobs/idv/profile_job_spec.rb @@ -0,0 +1,75 @@ +require 'rails_helper' + +describe Idv::ProfileJob do + describe '#perform' do + let(:result_id) { SecureRandom.uuid } + let(:applicant_json) { { first_name: 'Jean-Luc', last_name: 'Picard' }.to_json } + let(:vendor_params) { { dob: '07/13/2035' } } + + context 'when verification succeeds' do + it 'should save a successful result' do + Idv::ProfileJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(true) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.normalized_applicant.first_name).to eq('JEAN-LUC') + expect(result.normalized_applicant.last_name).to eq('PICARD') + expect(result.reasons).to eq(['Everything looks good']) + expect(result.errors).to eq({}) + expect(result.session_id).to be_present + end + end + + context 'when verification fails' do + let(:applicant_json) { { first_name: 'Bad', last_name: 'McBadson' }.to_json } + + it 'should save an unsuccessful result' do + Idv::ProfileJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json + ) + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(false) + expect(result.reasons).to eq(['The name was suspicious']) + expect(result.errors).to eq(first_name: 'Unverified first name.') + expect(result.session_id).to be_present + end + end + + context 'when the idv agent raises' do + before do + agent = instance_double(Idv::Agent) + allow(agent).to receive(:start).and_raise(RuntimeError, '🔥🔥🔥') + allow(Idv::Agent).to receive(:new).and_return(agent) + end + + it 'should rescue from errors and save a failed job result' do + expect do + Idv::ProfileJob.perform_now( + result_id: result_id, + vendor: :mock, + vendor_params: vendor_params, + applicant_json: applicant_json + ) + end.to raise_error(RuntimeError, '🔥🔥🔥') + result = VendorValidatorResultStorage.new.load(result_id) + + expect(result.success?).to eq(false) + expect(result.timed_out?).to eq(false) + expect(result.job_failed?).to eq(true) + end + end + end +end diff --git a/spec/jobs/vendor_validator_job_spec.rb b/spec/jobs/vendor_validator_job_spec.rb deleted file mode 100644 index 58e43c00877..00000000000 --- a/spec/jobs/vendor_validator_job_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'rails_helper' - -RSpec.describe VendorValidatorJob do - let(:result_id) { SecureRandom.uuid } - let(:vendor_validator_class) { 'Idv::PhoneValidator' } - let(:vendor) { :mock } - let(:vendor_params) { '+1 (888) 123-4567' } - let(:applicant) { Proofer::Applicant.new(first_name: 'Test') } - let(:applicant_json) { applicant.to_json } - let(:vendor_session_id) { SecureRandom.uuid } - - subject(:job) { VendorValidatorJob.new } - - describe '#perform' do - subject(:perform) do - job.perform( - result_id: result_id, - vendor_validator_class: vendor_validator_class, - vendor: vendor, - vendor_params: vendor_params, - applicant_json: applicant_json, - vendor_session_id: vendor_session_id - ) - end - - it 'calls out to a vendor and serializes the result' do - expect(Idv::PhoneValidator).to receive(:new). - with( - applicant: kind_of(Proofer::Applicant), - vendor: vendor, - vendor_params: vendor_params, - vendor_session_id: vendor_session_id - ).and_call_original - - before_result = VendorValidatorResultStorage.new.load(result_id) - expect(before_result).to be_nil - - perform - - after_result = VendorValidatorResultStorage.new.load(result_id) - expect(after_result).to be_a(Idv::VendorResult) - end - - context 'when the vendor throws an exception' do - let(:vendor_validator_class) { 'Idv::ProfileValidator' } - let(:applicant) { Proofer::Applicant.new(first_name: 'Fail') } - - let(:exception_msg) { 'Failed to contact proofing vendor' } - - it 'notifies NewRelic and does not raise' do - expect(NewRelic::Agent).to receive(:notice_error). - with(kind_of(StandardError)) - - perform - end - - it 'writes a failure result to redis' do - perform - - result = VendorValidatorResultStorage.new.load(result_id) - expect(result.success?).to eq(false) - expect(result.errors).to eq(agent: [exception_msg]) - expect(result.reasons).to eq([exception_msg]) - end - end - - context 'when parsing the vendor response throws an exception' do - it 'rescues the error and stores the job failed result' do - allow(Idv::PhoneValidator).to receive(:new).and_raise(StandardError) - - storage = instance_double(VendorValidatorResultStorage) - result = instance_double(Idv::VendorResult, errors: { job_failed: true }) - allow(Idv::VendorResult).to receive(:new).and_return(result) - - expect(VendorValidatorResultStorage).to receive(:new).and_return(storage) - expect(storage).to receive(:store).with(result_id: result_id, result: result) - - expect { perform }.to raise_error StandardError - end - end - end -end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c0685a3a469..1405719dcbf 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -59,8 +59,10 @@ end config.before(:each, idv_job: true) do - allow(VendorValidatorJob).to receive(:perform_later) do |*args| - VendorValidatorJob.perform_now(*args) + [Idv::ProfileJob, Idv::FinanceJob, Idv::PhoneJob].each do |job_class| + allow(job_class).to receive(:perform_later) do |*args| + job_class.perform_now(*args) + end end end diff --git a/spec/services/idv/financials_validator_spec.rb b/spec/services/idv/financials_validator_spec.rb deleted file mode 100644 index 2010f5bd8bb..00000000000 --- a/spec/services/idv/financials_validator_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'rails_helper' - -describe Idv::FinancialsValidator do - let(:user) { build(:user) } - - let(:applicant) { Proofer::Applicant.new({}) } - let(:vendor) { :mock } - let(:vendor_session_id) { SecureRandom.uuid } - - let(:params) do - { ccn: '123-45-6789' } - end - - let(:confirmation) { instance_double(Proofer::Confirmation) } - - subject do - Idv::FinancialsValidator.new( - applicant: applicant, - vendor: vendor, - vendor_params: params, - vendor_session_id: vendor_session_id - ) - end - - def stub_agent_calls - agent = instance_double(Idv::Agent) - allow(Idv::Agent).to receive(:new). - with(applicant: applicant, vendor: vendor). - and_return(agent) - expect(agent).to receive(:submit_financials). - with(params, vendor_session_id).and_return(confirmation) - end - - describe '#result' do - it 'has success' do - stub_agent_calls - - success_string = 'true' - expect(confirmation).to receive(:success?).and_return(success_string) - - expect(subject.result.success?).to eq success_string - end - - it 'has errors' do - stub_agent_calls - - error_string = 'mucho errors' - - expect(confirmation).to receive(:errors).and_return(error_string) - - expect(subject.result.errors).to eq error_string - end - end -end diff --git a/spec/services/idv/phone_validator_spec.rb b/spec/services/idv/phone_validator_spec.rb deleted file mode 100644 index faa6755c778..00000000000 --- a/spec/services/idv/phone_validator_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'rails_helper' - -describe Idv::PhoneValidator do - let(:user) { build(:user) } - - let(:applicant) { Proofer::Applicant.new({}) } - let(:vendor) { :mock } - let(:vendor_session_id) { SecureRandom.uuid } - - let(:params) do - { phone: '202-555-1212' } - end - - let(:confirmation) { instance_double(Proofer::Confirmation) } - - subject do - Idv::PhoneValidator.new( - applicant: applicant, - vendor: vendor, - vendor_params: params, - vendor_session_id: vendor_session_id - ) - end - - def stub_agent_calls - agent = instance_double(Idv::Agent) - allow(Idv::Agent).to receive(:new). - with(applicant: applicant, vendor: vendor). - and_return(agent) - expect(agent).to receive(:submit_phone). - with(params, vendor_session_id).and_return(confirmation) - end - - describe '#result' do - it 'has success' do - stub_agent_calls - - success_string = 'true' - - expect(confirmation).to receive(:success?).and_return(success_string) - - expect(subject.result.success?).to eq success_string - end - - it 'has errors' do - stub_agent_calls - - error_string = 'mucho errors' - - expect(confirmation).to receive(:errors).and_return(error_string) - - expect(subject.result.errors).to eq error_string - end - end -end diff --git a/spec/services/idv/submit_idv_job_spec.rb b/spec/services/idv/submit_idv_job_spec.rb new file mode 100644 index 00000000000..6481859d6ef --- /dev/null +++ b/spec/services/idv/submit_idv_job_spec.rb @@ -0,0 +1,103 @@ +require 'rails_helper' + +RSpec.describe Idv::SubmitIdvJob do + subject(:service) do + Idv::SubmitIdvJob.new( + idv_session: idv_session, + vendor_params: vendor_params + ) + end + + let(:idv_session) do + Idv::Session.new( + current_user: user, + issuer: nil, + user_session: { + idv: { + applicant: applicant, + vendor_session_id: vendor_session_id, + vendor: :mock, + }, + } + ) + end + + let(:user) { build(:user) } + let(:applicant) { Proofer::Applicant.new(first_name: 'Greatest') } + let(:vendor_session_id) { '12345' } + let(:result_id) { 'abcdef' } + let(:vendor_params) { { dob: '01/01/1985' } } + + describe '#submit_profile_job' do + it 'generates a UUID and enqueues a Idv::ProfileJob and saves the UUID in the session' do + expect(Idv::ProfileJob).to receive(:perform_later). + with( + result_id: result_id, + vendor: 'mock', + vendor_params: vendor_params, + vendor_session_id: vendor_session_id, + applicant_json: applicant.to_json + ) + + expect(idv_session.async_result_id).to eq(nil) + expect(idv_session.async_result_started_at).to eq(nil) + + expect(SecureRandom).to receive(:uuid).and_return(result_id).once + + service.submit_profile_job + + expect(idv_session.async_result_id).to eq(result_id) + expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i) + end + end + + describe '#submit_finance_job' do + let(:vendor_params) { { ccn: '88888888' } } + + it 'generates a UUID and enqueues a Idv::FinanceJob and saves the UUID in the session' do + expect(Idv::FinanceJob).to receive(:perform_later). + with( + result_id: result_id, + vendor: 'mock', + vendor_params: vendor_params, + vendor_session_id: vendor_session_id, + applicant_json: applicant.to_json + ) + + expect(idv_session.async_result_id).to eq(nil) + expect(idv_session.async_result_started_at).to eq(nil) + + expect(SecureRandom).to receive(:uuid).and_return(result_id).once + + service.submit_finance_job + + expect(idv_session.async_result_id).to eq(result_id) + expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i) + end + end + + describe '#submit_phone_job' do + let(:vendor_params) { '5555550000' } + + it 'generates a UUID and enqueues a Idv::PhoneJob and saves the UUID in the session' do + expect(Idv::PhoneJob).to receive(:perform_later). + with( + result_id: result_id, + vendor: 'mock', + vendor_params: vendor_params, + vendor_session_id: vendor_session_id, + applicant_json: applicant.to_json + ) + + expect(idv_session.async_result_id).to eq(nil) + expect(idv_session.async_result_started_at).to eq(nil) + + expect(SecureRandom).to receive(:uuid).and_return(result_id).once + + service.submit_phone_job + + expect(idv_session.async_result_id).to eq(result_id) + expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i) + end + end +end diff --git a/spec/services/submit_idv_job_spec.rb b/spec/services/submit_idv_job_spec.rb deleted file mode 100644 index 4c72132cc14..00000000000 --- a/spec/services/submit_idv_job_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'rails_helper' - -RSpec.describe SubmitIdvJob do - subject(:service) do - SubmitIdvJob.new( - vendor_validator_class: vendor_validator_class, - idv_session: idv_session, - vendor_params: vendor_params - ) - end - - let(:idv_session) do - Idv::Session.new( - current_user: user, - issuer: nil, - user_session: { - idv: { - applicant: applicant, - vendor_session_id: vendor_session_id, - vendor: :mock, - }, - } - ) - end - - let(:user) { build(:user) } - let(:applicant) { Proofer::Applicant.new(first_name: 'Greatest') } - let(:vendor_session_id) { '12345' } - let(:result_id) { 'abcdef' } - let(:vendor_params) { '+1 (888) 123-4567' } - let(:vendor_validator_class) { 'Idv::PhoneValidator' } - - describe '#call' do - subject(:call) { service.call } - - it 'generates a UUID and enqueues a job, and saves the UUID in the session' do - expect(SecureRandom).to receive(:uuid).and_return(result_id).once - - expect(VendorValidatorJob).to receive(:perform_later). - with( - result_id: result_id, - vendor_validator_class: vendor_validator_class, - vendor: 'mock', - vendor_params: vendor_params, - vendor_session_id: vendor_session_id, - applicant_json: applicant.to_json - ) - - expect(idv_session.async_result_id).to eq(nil) - expect(idv_session.async_result_started_at).to eq(nil) - - call - - expect(idv_session.async_result_id).to eq(result_id) - expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i) - end - end -end