Skip to content
47 changes: 37 additions & 10 deletions app/services/doc_auth/mock/result_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class ResultResponse < DocAuth::Response
def initialize(uploaded_file, selfie_check_performed, config)
@uploaded_file = uploaded_file.to_s
@config = config
@selfie_check_performed = selfie_check_performed
super(
success: success?,
errors: errors,
Expand All @@ -17,9 +18,10 @@ def initialize(uploaded_file, selfie_check_performed, config)
selfie_check_performed: selfie_check_performed,
extra: {
doc_auth_result: doc_auth_result,
portrait_match_results: portrait_match_results,
billed: true,
classification_info: classification_info,
},
}.compact,
)
end

Expand All @@ -34,20 +36,32 @@ def errors
image_metrics = file_data.dig('image_metrics')
failed = file_data.dig('failed_alerts')
passed = file_data.dig('passed_alerts')
liveness_result = file_data.dig('liveness_result')
face_match_result = file_data.dig('portrait_match_results', 'FaceMatchResult')
classification_info = file_data.dig('classification_info')
# Pass and doc type is ok
if [doc_auth_result, image_metrics, failed, passed,
liveness_result, classification_info].any?(&:present?)
has_fields = [
doc_auth_result,
image_metrics,
failed,
passed,
face_match_result,
classification_info,
].any?(&:present?)

if has_fields
# Error generator is not to be called when it's not failure
# allows us to test successful results
return {} if doc_auth_result == 'Passed' && id_type_supported?
return {} if all_doc_capture_values_passing?(
doc_auth_result, id_type_supported?,
face_match_result
)

mock_args = {}
mock_args[:doc_auth_result] = doc_auth_result if doc_auth_result.present?
mock_args[:image_metrics] = image_metrics.symbolize_keys if image_metrics.present?
mock_args[:failed] = failed.map!(&:symbolize_keys) if failed.present?
mock_args[:failed] = failed.map!(&:symbolize_keys) unless failed.nil?
mock_args[:passed] = passed.map!(&:symbolize_keys) if passed.present?
mock_args[:liveness_result] = liveness_result if liveness_result.present?
mock_args[:liveness_enabled] = @selfie_check_performed
mock_args[:classification_info] = classification_info if classification_info.present?
fake_response_info = create_response_info(**mock_args)
ErrorGenerator.new(config).generate_doc_auth_errors(fake_response_info)
Expand Down Expand Up @@ -136,6 +150,12 @@ def doc_auth_result_from_uploaded_file
parsed_data_from_uploaded_file&.[]('doc_auth_result')
end

def portrait_match_results
parsed_data_from_uploaded_file.dig('portrait_match_results')&.
transform_keys! { |key| key.to_s.camelize }&.
deep_symbolize_keys
end

def classification_info
info = parsed_data_from_uploaded_file&.[]('classification_info') || {}
info.to_h.symbolize_keys
Expand All @@ -149,6 +169,12 @@ def doc_auth_result_from_success
end
end

def all_doc_capture_values_passing?(doc_auth_result, id_type_supported, face_match_result)
doc_auth_result == 'Passed' &&
id_type_supported &&
(@selfie_check_performed ? face_match_result == 'Pass' : true)
end

def parse_uri
uri = URI.parse(uploaded_file.chomp)
if uri.scheme == 'data'
Expand Down Expand Up @@ -181,7 +207,7 @@ def create_response_info(
doc_auth_result: 'Failed',
passed: [],
failed: DEFAULT_FAILED_ALERTS,
liveness_result: nil,
liveness_enabled: false,
image_metrics: DEFAULT_IMAGE_METRICS,
classification_info: nil
)
Expand All @@ -195,9 +221,10 @@ def create_response_info(
},
alert_failure_count: failed&.count.to_i,
image_metrics: merged_image_metrics,
portrait_match_results: { FaceMatchResult: liveness_result },
liveness_enabled: liveness_enabled,
classification_info: classification_info,
}
portrait_match_results: @selfie_check_performed ? portrait_match_results : nil,
}.compact
end
end
end
Expand Down
158 changes: 150 additions & 8 deletions spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,27 +185,47 @@
end

describe 'selfie check performed flag' do
let(:response) do
client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
liveness_checking_required: liveness_checking_required,
)
end

context 'when a liveness check is required' do
let(:liveness_checking_required) { true }

image = <<~YAML
portrait_match_results:
FaceMatchResult: Pass
FaceErrorMessage: 'Successful. Liveness: Live'
doc_auth_result: Passed
failed_alerts: []
YAML

it 'sets selfie_check_performed to true' do
response = client.post_images(
front_image: image,
back_image: image,
liveness_checking_required: liveness_checking_required,
selfie_image: image,
)

expect(response.selfie_check_performed?).to be(true)
expect(response.extra).to have_key(:portrait_match_results)
end
end

context 'when a liveness check is not required' do
let(:liveness_checking_required) { false }

image = <<~YAML
doc_auth_result: Passed
failed_alerts: []
YAML

it 'sets selfie_check_performed to false' do
response = client.post_images(
front_image: image,
back_image: image,
liveness_checking_required: liveness_checking_required,
)

expect(response.selfie_check_performed?).to be(false)
expect(response.extra).not_to have_key(:portrait_match_results)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me the best place to test that if we're not checking the selfie, we don't have the portrait_match_results key, rather than building a whole new test for the negative condition. I thought making the opposite example of including the key above was then also warranted, and doing that changed the setup.

end
end
end
Expand Down Expand Up @@ -261,4 +281,126 @@
)
end
end

context 'liveness checking is required and doc_auth_result is passed' do
let(:liveness_checking_required) { true }

describe 'when sending a selfie image that is successful (both live and a match)' do
it 'returns a success response' do
image = <<~YAML
portrait_match_results:
FaceMatchResult: Pass
FaceErrorMessage: 'Successful. Liveness: Live'
doc_auth_result: Passed
failed_alerts: []
YAML

post_images_response = client.post_images(
front_image: image,
back_image: image,
selfie_image: image,
liveness_checking_required: liveness_checking_required,
)

expect(post_images_response.success?).to eq(true)
expect(post_images_response.extra[:portrait_match_results]).to eq(
{
FaceMatchResult: 'Pass', FaceErrorMessage: 'Successful. Liveness: Live'
},
)
expect(post_images_response.errors).to be_empty
end
end

describe 'when sending a failing selfie yml' do
context 'liveness check fails due to being determined to not be live' do
it 'returns a failure response' do
image = <<~YAML
portrait_match_results:
FaceMatchResult: Fail
FaceErrorMessage: 'Liveness: NotLive'
doc_auth_result: Passed
failed_alerts: []
YAML

post_images_response = client.post_images(
front_image: image,
back_image: image,
selfie_image: image,
liveness_checking_required: liveness_checking_required,
)

expect(post_images_response.success?).to eq(false)
expect(post_images_response.extra[:portrait_match_results]).to eq(
{
FaceMatchResult: 'Fail', FaceErrorMessage: 'Liveness: NotLive'
},
)

errors = post_images_response.errors
expect(errors.keys).to contain_exactly(:general, :hints, :selfie)
expect(errors[:selfie]).to contain_exactly(DocAuth::Errors::FALLBACK_FIELD_LEVEL)
end
end

context 'liveness check fails due to poor quality' do
it 'returns a failure response' do
image = <<~YAML
portrait_match_results:
FaceMatchResult: Fail
FaceErrorMessage: 'Liveness: PoorQuality'
doc_auth_result: Passed
failed_alerts: []
YAML

post_images_response = client.post_images(
front_image: image,
back_image: image,
selfie_image: image,
liveness_checking_required: liveness_checking_required,
)

expect(post_images_response.success?).to eq(false)
expect(post_images_response.extra[:portrait_match_results]).to eq(
{
FaceMatchResult: 'Fail', FaceErrorMessage: 'Liveness: PoorQuality'
},
)

errors = post_images_response.errors
expect(errors.keys).to contain_exactly(:general, :hints, :selfie)
expect(errors[:selfie]).to contain_exactly(DocAuth::Errors::FALLBACK_FIELD_LEVEL)
end
end

context 'assessed to be live but face match result fails' do
it 'returns a failure response' do
image = <<~YAML
portrait_match_results:
FaceMatchResult: Fail
FaceErrorMessage: 'Successful. Liveness: Live'
doc_auth_result: Passed
failed_alerts: []
YAML
post_images_response = client.post_images(
front_image: image,
back_image: image,
selfie_image: image,
liveness_checking_required: liveness_checking_required,
)

expect(post_images_response.success?).to eq(false)
expect(post_images_response.extra[:portrait_match_results]).to eq(
{
FaceMatchResult: 'Fail', FaceErrorMessage: 'Successful. Liveness: Live'
},
)

errors = post_images_response.errors
expect(errors.keys).to contain_exactly(:general, :hints, :selfie)
expect(errors[:selfie]).to contain_exactly(DocAuth::Errors::FALLBACK_FIELD_LEVEL)
end
end
end
end
end
56 changes: 51 additions & 5 deletions spec/services/doc_auth/mock/result_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -648,16 +648,62 @@
end

context 'when a selfie check is performed' do
let(:input) { DocAuthImageFixtures.document_front_image }
let(:selfie_check_performed) { true }

it { expect(response.selfie_check_performed?).to eq(true) }
describe 'and it is successful' do
let(:input) do
<<~YAML
portrait_match_results:
FaceMatchResult: Pass
FaceErrorMessage: 'Successful. Liveness: Live'
doc_auth_result: Passed
failed_alerts: []
YAML
end
let(:selfie_check_performed) { true }

it 'returns the expected values' do
selfie_results = {
FaceMatchResult: 'Pass',
FaceErrorMessage: 'Successful. Liveness: Live',
}

expect(response.selfie_check_performed?).to eq(true)
expect(response.success?).to eq(true)
expect(response.extra[:portrait_match_results]).to eq(selfie_results)
end
end

describe 'and it is not successful' do
let(:input) do
<<~YAML
portrait_match_results:
FaceMatchResult: Fail
FaceErrorMessage: 'Successful. Liveness: Live'
doc_auth_result: Passed
failed_alerts: []
YAML
end
let(:selfie_check_performed) { true }

it 'returns the expected values' do
selfie_results = {
FaceMatchResult: 'Fail',
FaceErrorMessage: 'Successful. Liveness: Live',
}

expect(response.selfie_check_performed?).to eq(true)
expect(response.success?).to eq(false)
expect(response.extra[:portrait_match_results]).to eq(selfie_results)
end
end
end

context 'when a selfie check is not performed' do
let(:input) { DocAuthImageFixtures.document_front_image }
let(:selfie_check_performed) { false }

it { expect(response.selfie_check_performed?).to eq(false) }
it 'returns the expected values' do
expect(response.selfie_check_performed?).to eq(false)
expect(response.extra).not_to have_key(:portrait_match_results)
end
end
end