Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion lib/event_summarizer/idv_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require 'event_summarizer/vendor_result_evaluators/aamva'
require 'event_summarizer/vendor_result_evaluators/instant_verify'
require 'event_summarizer/vendor_result_evaluators/phone_finder'
require 'event_summarizer/vendor_result_evaluators/true_id'

module EventSummarizer
Expand All @@ -13,6 +14,7 @@ class IdvMatcher
IDV_GPO_CODE_SUBMITTED_EVENT = 'IdV: enter verify by mail code submitted'
IDV_FINAL_RESOLUTION_EVENT = 'IdV: final resolution'
IDV_IMAGE_UPLOAD_VENDOR_SUBMITTED_EVENT = 'IdV: doc auth image upload vendor submitted'
IDV_PHONE_CONFIRMATION_VENDOR_EVENT = 'IdV: phone confirmation vendor'
IDV_VERIFY_PROOFING_RESULTS_EVENT = 'IdV: doc auth verify proofing results'
IPP_ENROLLMENT_STATUS_UPDATED_EVENT = 'GetUspsProofingResultsJob: Enrollment status updated'
PROFILE_ENCRYPTION_INVALID_EVENT = 'Profile Encryption: Invalid'
Expand All @@ -31,6 +33,11 @@ class IdvMatcher
name: 'Instant Verify',
evaluator_module: EventSummarizer::VendorResultEvaluators::InstantVerify,
},
'lexisnexis:phone_finder' => {
id: :phone_finder,
name: 'Phone Finder',
evaluator_module: EventSummarizer::VendorResultEvaluators::PhoneFinder,
},
'aamva:state_id' => {
id: :aamva,
name: 'AAMVA',
Expand Down Expand Up @@ -124,6 +131,11 @@ def handle_cloudwatch_event(event)
handle_image_upload_vendor_submitted(event:)
end

when IDV_PHONE_CONFIRMATION_VENDOR_EVENT
for_current_idv_attempt(event:) do
handle_phone_confirmation_vendor_event(event:)
end

when IDV_VERIFY_PROOFING_RESULTS_EVENT
for_current_idv_attempt(event:) do
handle_verify_proofing_results_event(event:)
Expand Down Expand Up @@ -423,6 +435,23 @@ def handle_image_upload_vendor_submitted(event:)
end
end

def handle_phone_confirmation_vendor_event(event:)
timestamp = event['@timestamp']
success = event.dig(*EVENT_PROPERTIES, 'success')

if success
add_significant_event(
timestamp:,
type: :passed_phone_finder,
description: 'Phone Finder check succeeded',
)

return
end

add_events_for_failed_vendor_result(event.dig(*EVENT_PROPERTIES), timestamp:)
end

def handle_verify_proofing_results_event(event:)
timestamp = event['@timestamp']
success = event.dig(*EVENT_PROPERTIES, 'success')
Expand Down Expand Up @@ -487,7 +516,8 @@ def handle_verify_proofing_results_event(event:)
def add_events_for_failed_vendor_result(result, timestamp:)
return if result['success']

vendor = VENDORS[result['vendor_name']] || UNKNOWN_VENDOR
vendor_name = result['vendor_name'] || result.dig('vendor', 'vendor_name')
vendor = VENDORS[vendor_name] || UNKNOWN_VENDOR
evaluator = vendor[:evaluator_module]

if !evaluator.present?
Expand Down
65 changes: 65 additions & 0 deletions lib/event_summarizer/vendor_result_evaluators/phone_finder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

module EventSummarizer
module VendorResultEvaluators
module PhoneFinder
def self.evaluate_result(result)
exception_payload(result) || failure_payload(result)
end

def self.exception_payload(result)
exception = result.dig('vendor', 'exception').to_s.strip

return nil if exception.empty?

{
type: :phone_finder_exception,
description: "Vendor exception: #{exception}",
}
end

def self.itemized_errors(result)
failed_items = []
pf_instances = result.dig('errors', 'PhoneFinder')
return [] unless pf_instances && !pf_instances.empty?

pf_instances.each do |pf_instance|
next if pf_instance['ProductStatus'] != 'fail'

items = pf_instance['Items']
failed_items.concat(items.select { |item| item['ItemStatus'] == 'fail' })
end

failed_items
.map { |item| item.dig('ItemReason', 'Description').to_s.strip }
.reject(&:empty?)
.uniq
end

def self.general_error(result)
checks = result.dig('errors', 'PhoneFinder Checks')
return nil unless checks && !checks.empty?

failed_status = checks.find { |status| status['ProductStatus'] == 'fail' }

failed_status&.dig('ProductReason', 'Description')
end

def self.failure_payload(result)
fail_reasons = [*itemized_errors(result), general_error(result)].compact

if fail_reasons.any?
{
type: :phone_finder_error,
description: "Phone Finder check failed: #{fail_reasons.uniq.join('; ')}",
}
else
{
type: :phone_finder_error,
description: 'Phone Finder check failed. Review logs for more information.',
}
end
end
end
end
end
35 changes: 35 additions & 0 deletions spec/lib/event_summarizer/idv_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,40 @@
end
end
end

context "On 'IdV: phone confirmation vendor' (PhoneFinder) event" do
let(:event) do
{
'@timestamp' => Time.zone.now,
'name' => 'IdV: phone confirmation vendor',
'@message' => {
'properties' => {
'event_properties' => {
'success' => true,
},
},
},
}
end

before do
allow(matcher).to receive(:current_idv_attempt).and_return(
EventSummarizer::IdvMatcher::IdvAttempt.new(
started_at: Time.zone.now,
),
)
end

it 'adds a passed_phone_finder significant event when successful' do
matcher.handle_cloudwatch_event(event)

expect(matcher.current_idv_attempt.significant_events).to include(
have_attributes(
type: :passed_phone_finder,
description: 'Phone Finder check succeeded',
),
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require 'json'
require 'event_summarizer/vendor_result_evaluators/phone_finder'

RSpec.describe EventSummarizer::VendorResultEvaluators::PhoneFinder do
subject(:evaluation) do
described_class.evaluate_result(
JSON.parse(JSON.generate(phone_finder_result)),
)
end

describe 'failed result' do
context 'general failure' do
let(:phone_finder_result) do
{
success: false,
errors: {
base: ["Verification failed with code: 'phone_finder_fail'"],
"PhoneFinder Checks": [
{
ProductStatus: 'fail',
ProductReason: {
Description: 'General failure reason',
},
},
],
},
}
end

it 'returns the correct result' do
expect(evaluation).to eql(
{
description: 'Phone Finder check failed: General failure reason',
type: :phone_finder_error,
},
)
end
end

context 'itemized failure' do
let(:phone_finder_result) do
{
success: false,
errors: {
base: ["Verification failed with code: 'phone_finder_fail'"],
PhoneFinder: [
{
ProductStatus: 'fail',
Items: [
{
ItemStatus: 'fail',
ItemReason: {
Description: 'Specific failure reason A',
},
},
{
ItemStatus: 'fail',
ItemReason: {
Description: 'Specific failure reason B',
},
},
],
},
],
},
}
end

it 'returns the correct result' do
expect(evaluation).to eql(
{
description: 'Phone Finder check failed: ' \
'Specific failure reason A; Specific failure reason B',
type: :phone_finder_error,
},
)
end
end
end
end