diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb index e18566f5181..cbe64c22810 100644 --- a/app/services/doc_auth/mock/result_response.rb +++ b/app/services/doc_auth/mock/result_response.rb @@ -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, @@ -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 @@ -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) @@ -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 @@ -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' @@ -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 ) @@ -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 diff --git a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb index 19c23a1ecc3..b10f21671ac 100644 --- a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb +++ b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb @@ -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) end end end @@ -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 diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb index 5b893d82a30..586a87ef64d 100644 --- a/spec/services/doc_auth/mock/result_response_spec.rb +++ b/spec/services/doc_auth/mock/result_response_spec.rb @@ -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