From 22ce3e13a6e6760290d059ba3455863c4f94dea1 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Wed, 18 Oct 2023 10:11:29 -0400 Subject: [PATCH 1/9] LG-10889: Support field-specific Acuant image issue indicated by http status. --- app/services/doc_auth/acuant/request.rb | 25 ++++++----- .../acuant/requests/upload_image_request.rb | 20 +++++++++ app/services/doc_auth/errors.rb | 7 ++++ .../doc_auth/mock/doc_auth_mock_client.rb | 36 ++++++++++------ app/services/doc_auth_router.rb | 7 +++- config/locales/doc_auth/en.yml | 6 ++- config/locales/doc_auth/es.yml | 7 ++-- config/locales/doc_auth/fr.yml | 8 ++-- .../idv/image_uploads_controller_spec.rb | 41 +++++++++++++++++++ .../requests/upload_image_request_spec.rb | 30 ++++++++++++++ .../responses/get_results_response_spec.rb | 2 +- .../mock/doc_auth_mock_client_spec.rb | 5 ++- 12 files changed, 159 insertions(+), 35 deletions(-) diff --git a/app/services/doc_auth/acuant/request.rb b/app/services/doc_auth/acuant/request.rb index 0ca6f910f0d..c6fbe9ba8b8 100644 --- a/app/services/doc_auth/acuant/request.rb +++ b/app/services/doc_auth/acuant/request.rb @@ -127,16 +127,21 @@ def create_error_response(errors, exception) end def handle_expected_http_error(http_response) - error = case http_response.status - when 438 - Errors::IMAGE_LOAD_FAILURE - when 439 - Errors::PIXEL_DEPTH_FAILURE - when 440 - Errors::IMAGE_SIZE_FAILURE - end - - create_error_response({ general: [error] }, create_http_exception(http_response)) + errors = errors_from_http_status(http_response.status) + create_error_response(errors, create_http_exception(http_response)) + end + + def errors_from_http_status(status) + # todo: side specific message + error = case status + when 438 + Errors::IMAGE_LOAD_FAILURE + when 439 + Errors::PIXEL_DEPTH_FAILURE + when 440 + Errors::IMAGE_SIZE_FAILURE + end + { general: [error] } end def handle_invalid_response(http_response) diff --git a/app/services/doc_auth/acuant/requests/upload_image_request.rb b/app/services/doc_auth/acuant/requests/upload_image_request.rb index 83879cd5e40..cd597738e86 100644 --- a/app/services/doc_auth/acuant/requests/upload_image_request.rb +++ b/app/services/doc_auth/acuant/requests/upload_image_request.rb @@ -41,6 +41,26 @@ def metric_name def timeout IdentityConfig.store.acuant_upload_image_timeout end + + def errors_from_http_status(status) + case status + when 438 + { + general: [Errors::IMAGE_LOAD_FAILURE], + side.downcase.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD], + } + when 439 + { + general: [Errors::PIXEL_DEPTH_FAILURE], + side.downcase.to_sym => [Errors::PIXEL_DEPTH_FAILURE_FIELD], + } + when 440 + { + general: [Errors::IMAGE_SIZE_FAILURE], + side.downcase.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD], + } + end + end end end end diff --git a/app/services/doc_auth/errors.rb b/app/services/doc_auth/errors.rb index ba72fdc3e52..fbc15666cab 100644 --- a/app/services/doc_auth/errors.rb +++ b/app/services/doc_auth/errors.rb @@ -2,8 +2,11 @@ module DocAuth module Errors # HTTP Status Codes IMAGE_LOAD_FAILURE = 'image_load_failure' # 438 + IMAGE_LOAD_FAILURE_FIELD = 'image_load_failure_field' # 438 PIXEL_DEPTH_FAILURE = 'pixel_depth_failure' # 439 + PIXEL_DEPTH_FAILURE_FIELD = 'pixel_depth_failure_field' IMAGE_SIZE_FAILURE = 'image_size_failure' # 440 + IMAGE_SIZE_FAILURE_FIELD = 'image_size_failure_field' # 440 # Network NETWORK = 'network' # usually 500 or other unhandled error # Alerts @@ -80,6 +83,10 @@ module Errors # rubocop:disable Layout/LineLength USER_DISPLAY = { + # Http status + IMAGE_LOAD_FAILURE => { long_msg: IMAGE_LOAD_FAILURE, long_msg_plural: IMAGE_LOAD_FAILURE, field_msg: IMAGE_LOAD_FAILURE_FIELD }, + PIXEL_DEPTH_FAILURE => { long_msg: PIXEL_DEPTH_FAILURE, long_msg_plural: PIXEL_DEPTH_FAILURE, field_msg: PIXEL_DEPTH_FAILURE_FIELD }, + IMAGE_SIZE_FAILURE => { long_msg: IMAGE_SIZE_FAILURE, long_msg_plural: IMAGE_SIZE_FAILURE, field_msg: IMAGE_SIZE_FAILURE_FIELD }, # Image metrics DPI_LOW => { long_msg: DPI_LOW_ONE_SIDE, long_msg_plural: DPI_LOW_BOTH_SIDES, field_msg: DPI_LOW_FIELD }, SHARP_LOW => { long_msg: SHARP_LOW_ONE_SIDE, long_msg_plural: SHARP_LOW_BOTH_SIDES, field_msg: SHARP_LOW_FIELD }, diff --git a/app/services/doc_auth/mock/doc_auth_mock_client.rb b/app/services/doc_auth/mock/doc_auth_mock_client.rb index 449056998e6..105e54314af 100644 --- a/app/services/doc_auth/mock/doc_auth_mock_client.rb +++ b/app/services/doc_auth/mock/doc_auth_mock_client.rb @@ -105,25 +105,35 @@ def http_error_response(image, side) data = parse_yaml(image.to_s) status = data.dig('http_status', side) return nil unless [500, 440, 438, 439].include?(status) - error = case status - when 438 - Errors::IMAGE_LOAD_FAILURE - when 439 - Errors::PIXEL_DEPTH_FAILURE - when 440 - Errors::IMAGE_SIZE_FAILURE - when 500 - Errors::NETWORK - end - return nil unless error - errors = { general: [error] } + errors = case status + when 438 + { + general: [Errors::IMAGE_LOAD_FAILURE], + side.downcase.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD], + } + when 439 + { + general: [Errors::PIXEL_DEPTH_FAILURE], + side.downcase.to_sym => [Errors::PIXEL_DEPTH_FAILURE_FIELD], + } + when 440 + { + general: [Errors::IMAGE_SIZE_FAILURE], + side.downcase.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD], + } + when 500 + { + general: [Errors::NETWORK], + } + end + return nil unless errors message = [ self.class.name, 'Unexpected HTTP response', status, ].join(' ') exception = DocAuth::RequestError.new(message, status) - return DocAuth::Response.new( + DocAuth::Response.new( success: false, errors: errors, exception: exception, diff --git a/app/services/doc_auth_router.rb b/app/services/doc_auth_router.rb index 5ca18db56f3..1b0885b616a 100644 --- a/app/services/doc_auth_router.rb +++ b/app/services/doc_auth_router.rb @@ -80,10 +80,13 @@ module DocAuthRouter DocAuth::Errors::GLARE_LOW_FIELD => 'doc_auth.errors.glare.failed_short', # i18n-tasks-use t('doc_auth.errors.http.image_load') DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load', + DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD => 'doc_auth.errors.http.image_load', # i18n-tasks-use t('doc_auth.errors.http.pixel_depth') DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth', - # i18n-tasks-use t('doc_auth.errors.http.image_size') - DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size', + DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD => 'doc_auth.errors.http.pixel_depth', + # i18n-tasks-use t('doc_auth.errors.http.image_size.top_msg') + DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size.top_msg', + DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD => 'doc_auth.errors.http.image_size.failed_short', # i18n-tasks-use t('doc_auth.errors.general.fallback_field_level') DocAuth::Errors::FALLBACK_FIELD_LEVEL => 'doc_auth.errors.general.fallback_field_level', }.freeze diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index b5f01497c83..2c85fb25880 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -90,8 +90,10 @@ en: http: image_load: The image file that you added is not supported. Please take new photos of your ID and try again. - image_size: Your image size is too large or too small. Please add images of your - ID that are about 2025 x 1275 pixels. + image_size: + failed_short: Image file is not supported, please try again. + top_msg: Your image size is too large or too small. Please add images of your ID + that are about 2025 x 1275 pixels. pixel_depth: The pixel depth of your image file is not supported. Please take new photos of your ID and try again. Supported image pixel depth is 24-bit RGB. diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 96d3c2dad44..c204386bb7a 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -114,9 +114,10 @@ es: http: image_load: El archivo de imagen que ha añadido no es compatible. Por favor, tome nuevas fotos de su identificación y vuelva a intentarlo. - image_size: El tamaño de la imagen es demasiado grande o demasiado pequeño. - Añada imágenes de su documento de identidad de unos 2025 x 1275 - píxeles. + image_size: + failed_short: El archivo de imagen no es compatible, inténtalo de nuevo. + top_msg: El tamaño de la imagen es demasiado grande o demasiado pequeño. Añada + imágenes de su documento de identidad de unos 2025 x 1275 píxeles. pixel_depth: No es compatible con la profundidad de píxeles de su archivo de imagen. Tome nuevas fotos de su documento de identidad e inténtelo nuevamente. La profundidad de píxeles de la imagen admitida es de 24 diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index 3ec6678a271..d345dd32a24 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -120,9 +120,11 @@ fr: image_load: Le fichier image que vous avez ajouté n’est pas pris en charge. Veuillez prendre de nouvelles photos de votre pièce d’identité et réessayer. - image_size: La taille de votre image est trop grande ou trop petite. Veuillez - ajouter des images de votre pièce d’identité d’environ 2025 x 1275 - pixels. + image_size: + failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer. + top_msg: La taille de votre image est trop grande ou trop petite. Veuillez + ajouter des images de votre pièce d’identité d’environ 2025 x 1275 + pixels. pixel_depth: La profondeur de pixel de votre fichier image n’est pas supportée. Veuillez prendre de nouvelles photos de votre pièce d’identité et réessayer. La profondeur de pixel de l’image prise en charge est de 24 diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index fd5c8ea3ead..c19f70e1177 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -305,6 +305,47 @@ end end + context 'when image upload fails with 4xx status' do + before do + status = 440 + errors = { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], + front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD] } + message = [ + self.class.name, + 'Unexpected HTTP response', + status, + ].join(' ') + exception = DocAuth::RequestError.new(message, status) + response = DocAuth::Response.new( + success: false, + errors: errors, + exception: exception, + extra: { vendor: 'Mock' }, + ) + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_front_image, + response: response, + ) + end + + it 'returns error response' do + action + expect(response.status).to eq(400) + expect(json[:success]).to eq(false) + expect(json[:remaining_attempts]).to be_a_kind_of(Numeric) + expect(json[:errors]).to eq [ + { + field: 'general', + message: I18n.t('doc_auth.errors.http.image_size.top_msg'), + }, + { + field: 'front', + message: I18n.t('doc_auth.errors.http.image_size.failed_short'), + }, + ] + end + end + context 'when image upload succeeds' do it 'returns a successful response and modifies the session' do action diff --git a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb index 84df287a0cd..7c889f64490 100644 --- a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb +++ b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb @@ -29,6 +29,36 @@ expect(response.exception).to be_nil expect(request_stub).to have_been_requested end + + context 'when http status is 4xx' do + shared_examples 'http unexpected 4xx status' do |http_status, general_failure, side_failure| + it "generate errors for #{http_status}" do + request_stub = stub_request(:post, url).with( + query: { side: 0, light: 0 }, + body: DocAuthImageFixtures.document_front_image, + ).to_return(body: '', status: http_status) + + request = described_class.new( + config: config, + image_data: DocAuthImageFixtures.document_front_image, + instance_id: instance_id, + side: :front, + ) + response = request.fetch + expect(response.success?).to eq(false) + expect(response.errors).to eq({ front: [side_failure], general: [general_failure] }) + expect(response.exception).not_to be_nil + expect(request_stub).to have_been_requested + end + end + + it_should_behave_like 'http unexpected 4xx status', 440, 'image_size_failure', + 'image_size_failure_field' + it_should_behave_like 'http unexpected 4xx status', 438, 'image_load_failure', + 'image_load_failure_field' + it_should_behave_like 'http unexpected 4xx status', 439, 'pixel_depth_failure', + 'pixel_depth_failure_field' + end end context 'with a back image' do diff --git a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb index 69f6c27c37c..5795ba59d60 100644 --- a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb +++ b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb @@ -163,7 +163,7 @@ end end - context 'with a failed result of unknow document type' do + context 'with a failed result' do let(:http_response) do instance_double( Faraday::Response, 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 7a96f64a9a4..41b40f1fc8c 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 @@ -218,7 +218,10 @@ ) expect(response).to be_a(DocAuth::Response) expect(response.success?).to eq(false) - expect(response.errors).to eq(general: [DocAuth::Errors::IMAGE_SIZE_FAILURE]) + expect(response.errors).to eq( + { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], + front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD] }, + ) end end end From 8a3e2f2bf87b6d79d00f0d990b0d3f7298fbc396 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Thu, 19 Oct 2023 10:41:30 -0400 Subject: [PATCH 2/9] LG-11139: finish supporting other status codes. --- app/services/doc_auth/mock/result_response.rb | 20 +++++--- config/locales/doc_auth/en.yml | 14 ++++-- config/locales/doc_auth/es.yml | 17 ++++--- config/locales/doc_auth/fr.yml | 18 ++++--- .../doc_auth/redo_document_capture_spec.rb | 18 +++++++ .../doc_auth/acuant/acuant_client_spec.rb | 47 +++++++++++++++++++ spec/support/features/doc_auth_helper.rb | 2 +- 7 files changed, 111 insertions(+), 25 deletions(-) diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb index d206c354b5d..92c734940e7 100644 --- a/app/services/doc_auth/mock/result_response.rb +++ b/app/services/doc_auth/mock/result_response.rb @@ -72,16 +72,24 @@ def attention_with_barcode? parsed_alerts == [ATTENTION_WITH_BARCODE_ALERT] end - def self.create_image_error_response(status) - error = case status + def self.create_image_error_response(status, side) + errors = case status when 438 - Errors::IMAGE_LOAD_FAILURE + { + general: [Errors::IMAGE_LOAD_FAILURE], + side.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD], + } when 439 - Errors::PIXEL_DEPTH_FAILURE + { + general: [Errors::PIXEL_DEPTH_FAILURE], + side.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD], + } when 440 - Errors::IMAGE_SIZE_FAILURE + { + general: [Errors::IMAGE_SIZE_FAILURE], + side.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD], + } end - errors = { general: [error] } message = [ 'Unexpected HTTP response', status, diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index 2c85fb25880..f0a471d7b14 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -88,15 +88,19 @@ en: top_msg_plural: We couldn’t read your ID. Your photos may have glare. Make sure that the flash on your camera is off and try taking new pictures. http: - image_load: The image file that you added is not supported. Please take new - photos of your ID and try again. + image_load: + failed_short: The image file that you added is not supported. + top_msg: The image file that you added is not supported. Please take new photos + of your ID and try again. image_size: failed_short: Image file is not supported, please try again. top_msg: Your image size is too large or too small. Please add images of your ID that are about 2025 x 1275 pixels. - pixel_depth: The pixel depth of your image file is not supported. Please take - new photos of your ID and try again. Supported image pixel depth is - 24-bit RGB. + pixel_depth: + failed_short: The pixel depth of your image file is not supported. + top_msg: The pixel depth of your image file is not supported. Please take new + photos of your ID and try again. Supported image pixel depth is + 24-bit RGB. not_a_file: The selection was not a valid file. pii: birth_date_min_age: Your birthday does not meet the minimum age requirement. diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index c204386bb7a..5dc0c754a0a 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -112,16 +112,21 @@ es: tengan reflejos. Asegúrese de que el flash de su cámara esté desactivado e intente tomar nuevas fotos. http: - image_load: El archivo de imagen que ha añadido no es compatible. Por favor, - tome nuevas fotos de su identificación y vuelva a intentarlo. + image_load: + failed_short: El archivo de imagen que ha añadido no es compatible. + top_msg: El archivo de imagen que ha añadido no es compatible. Por favor, tome + nuevas fotos de su identificación y vuelva a intentarlo. image_size: failed_short: El archivo de imagen no es compatible, inténtalo de nuevo. top_msg: El tamaño de la imagen es demasiado grande o demasiado pequeño. Añada imágenes de su documento de identidad de unos 2025 x 1275 píxeles. - pixel_depth: No es compatible con la profundidad de píxeles de su archivo de - imagen. Tome nuevas fotos de su documento de identidad e inténtelo - nuevamente. La profundidad de píxeles de la imagen admitida es de 24 - bits RGB. + pixel_depth: + failed_short: No es compatible con la profundidad de píxeles de su archivo de + imagen. + top_msg: No es compatible con la profundidad de píxeles de su archivo de imagen. + Tome nuevas fotos de su documento de identidad e inténtelo + nuevamente. La profundidad de píxeles de la imagen admitida es de 24 + bits RGB. not_a_file: La selección no era un archivo válido. pii: birth_date_min_age: Tu cumpleaños no cumple con el requisito de edad mínima. diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index d345dd32a24..e89fa126b64 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -117,18 +117,22 @@ fr: peuvent avoir des reflets. Assurez-vous que le flash de votre appareil photo est désactivé puis essayez de prendre de nouvelles photos. http: - image_load: Le fichier image que vous avez ajouté n’est pas pris en charge. - Veuillez prendre de nouvelles photos de votre pièce d’identité et - réessayer. + image_load: + failed_short: Le fichier image que vous avez ajouté n’est pas pris en charge. + top_msg: Le fichier image que vous avez ajouté n’est pas pris en charge. + Veuillez prendre de nouvelles photos de votre pièce d’identité et + réessayer. image_size: failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer. top_msg: La taille de votre image est trop grande ou trop petite. Veuillez ajouter des images de votre pièce d’identité d’environ 2025 x 1275 pixels. - pixel_depth: La profondeur de pixel de votre fichier image n’est pas supportée. - Veuillez prendre de nouvelles photos de votre pièce d’identité et - réessayer. La profondeur de pixel de l’image prise en charge est de 24 - bits RGB. + pixel_depth: + failed_short: La profondeur de pixel de votre fichier image n’est pas supportée. + top_msg: La profondeur de pixel de votre fichier image n’est pas supportée. + Veuillez prendre de nouvelles photos de votre pièce d’identité et + réessayer. La profondeur de pixel de l’image prise en charge est de + 24 bits RGB. not_a_file: La sélection n’était pas un fichier valide. pii: birth_date_min_age: Votre anniversaire ne correspond pas à l’âge minimum requis. diff --git a/spec/features/idv/doc_auth/redo_document_capture_spec.rb b/spec/features/idv/doc_auth/redo_document_capture_spec.rb index 18431c01425..fabf4ce161c 100644 --- a/spec/features/idv/doc_auth/redo_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/redo_document_capture_spec.rb @@ -189,6 +189,23 @@ end end + shared_examples_for 'inline error for 4xx status shown' do |status| + it "shows inline error for status #{status}" do + error = case status + when 438 + t('doc_auth.errors.http.image_load.failed_short') + when 439 + t('doc_auth.errors.http.pixel_depth.failed_short') + when 440 + t('doc_auth.errors.http.image_size.failed_short') + end + expect(page).to have_css( + '.usa-error-message[role="alert"]', + text: error, + ) + end + end + context 'error due to data issue with 2xx status code', allow_browser_log: true do before do sign_in_and_2fa_user @@ -232,6 +249,7 @@ attach_and_submit_images click_try_again end + it_behaves_like 'inline error for 4xx status shown', 440 it_behaves_like 'image re-upload not allowed' end diff --git a/spec/services/doc_auth/acuant/acuant_client_spec.rb b/spec/services/doc_auth/acuant/acuant_client_spec.rb index ea74ae32ced..391d1bbe0a6 100644 --- a/spec/services/doc_auth/acuant/acuant_client_spec.rb +++ b/spec/services/doc_auth/acuant/acuant_client_spec.rb @@ -216,4 +216,51 @@ ) end end + + context 'when there is unexpected http status code' do + shared_examples 'with http status 4xx' do |status| + it "generate response for status #{status} " do + instance_id = 'this-is-a-test-instance-id' + url = URI.join( + assure_id_url, "/AssureIDService/Document/#{instance_id}/Image" + ) + stub_request(:post, url).with(query: { side: 0, light: 0 }).to_return( + body: '', + status: status, + ) + + result = subject.post_front_image( + instance_id: instance_id, + image: DocAuthImageFixtures.document_front_image, + ) + expect(result.exception.message).not_to be_nil + case status + when 440 + expect(result.errors).to eql( + { + general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], + front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], + }, + ) + when 438 + expect(result.errors).to eql( + { + general: [DocAuth::Errors::IMAGE_LOAD_FAILURE], + front: [DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD], + }, + ) + when 439 + expect(result.errors).to eql( + { + general: [DocAuth::Errors::PIXEL_DEPTH_FAILURE], + front: [DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD], + }, + ) + end + end + end + it_should_behave_like 'with http status 4xx', 440 + it_should_behave_like 'with http status 4xx', 439 + it_should_behave_like 'with http status 4xx', 438 + end end diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index 0fb2cd3e0a2..3e5072bc0ae 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -248,7 +248,7 @@ def mock_doc_auth_trueid_http_non2xx_status(status) def mock_doc_auth_acuant_http_4xx_status(status, method = :post_front_image) DocAuth::Mock::DocAuthMockClient.mock_response!( method: method, - response: DocAuth::Mock::ResultResponse.create_image_error_response(status), + response: DocAuth::Mock::ResultResponse.create_image_error_response(status, 'front'), ) end From 84a5a29f0be3fb8ea2605c45d64f35901ba97106 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Fri, 20 Oct 2023 09:08:38 -0400 Subject: [PATCH 3/9] LG-10889: translations. --- config/locales/doc_auth/en.yml | 4 ++-- config/locales/doc_auth/es.yml | 7 +++---- config/locales/doc_auth/fr.yml | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index f0a471d7b14..4fe40f8778d 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -89,7 +89,7 @@ en: that the flash on your camera is off and try taking new pictures. http: image_load: - failed_short: The image file that you added is not supported. + failed_short: Image file is not supported, please try again. top_msg: The image file that you added is not supported. Please take new photos of your ID and try again. image_size: @@ -97,7 +97,7 @@ en: top_msg: Your image size is too large or too small. Please add images of your ID that are about 2025 x 1275 pixels. pixel_depth: - failed_short: The pixel depth of your image file is not supported. + failed_short: Image file is not supported, please try again. top_msg: The pixel depth of your image file is not supported. Please take new photos of your ID and try again. Supported image pixel depth is 24-bit RGB. diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 5dc0c754a0a..915f7adcf8c 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -113,16 +113,15 @@ es: desactivado e intente tomar nuevas fotos. http: image_load: - failed_short: El archivo de imagen que ha añadido no es compatible. + failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo. top_msg: El archivo de imagen que ha añadido no es compatible. Por favor, tome nuevas fotos de su identificación y vuelva a intentarlo. image_size: - failed_short: El archivo de imagen no es compatible, inténtalo de nuevo. + failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo. top_msg: El tamaño de la imagen es demasiado grande o demasiado pequeño. Añada imágenes de su documento de identidad de unos 2025 x 1275 píxeles. pixel_depth: - failed_short: No es compatible con la profundidad de píxeles de su archivo de - imagen. + failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo. top_msg: No es compatible con la profundidad de píxeles de su archivo de imagen. Tome nuevas fotos de su documento de identidad e inténtelo nuevamente. La profundidad de píxeles de la imagen admitida es de 24 diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index e89fa126b64..440bc9341f9 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -118,7 +118,7 @@ fr: photo est désactivé puis essayez de prendre de nouvelles photos. http: image_load: - failed_short: Le fichier image que vous avez ajouté n’est pas pris en charge. + failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer. top_msg: Le fichier image que vous avez ajouté n’est pas pris en charge. Veuillez prendre de nouvelles photos de votre pièce d’identité et réessayer. @@ -128,7 +128,7 @@ fr: ajouter des images de votre pièce d’identité d’environ 2025 x 1275 pixels. pixel_depth: - failed_short: La profondeur de pixel de votre fichier image n’est pas supportée. + failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer. top_msg: La profondeur de pixel de votre fichier image n’est pas supportée. Veuillez prendre de nouvelles photos de votre pièce d’identité et réessayer. La profondeur de pixel de l’image prise en charge est de From df2d15ceced09a023f6478d024b2a25973a35b32 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Fri, 20 Oct 2023 15:42:35 -0400 Subject: [PATCH 4/9] LG-10889: set both sides to error if got 4xx on GetResult request, the Acuant document said it's a possible outcome. --- app/services/doc_auth/acuant/request.rb | 1 - .../acuant/requests/get_results_request.rb | 23 +++++++++++++++++++ .../doc_auth/mock/doc_auth_mock_client.rb | 7 ++++++ .../requests/get_results_request_spec.rb | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/services/doc_auth/acuant/request.rb b/app/services/doc_auth/acuant/request.rb index c6fbe9ba8b8..f625359e93e 100644 --- a/app/services/doc_auth/acuant/request.rb +++ b/app/services/doc_auth/acuant/request.rb @@ -132,7 +132,6 @@ def handle_expected_http_error(http_response) end def errors_from_http_status(status) - # todo: side specific message error = case status when 438 Errors::IMAGE_LOAD_FAILURE diff --git a/app/services/doc_auth/acuant/requests/get_results_request.rb b/app/services/doc_auth/acuant/requests/get_results_request.rb index df9419a14a9..a4d7bd7983a 100644 --- a/app/services/doc_auth/acuant/requests/get_results_request.rb +++ b/app/services/doc_auth/acuant/requests/get_results_request.rb @@ -32,6 +32,29 @@ def metric_name def timeout IdentityConfig.store.acuant_get_results_timeout end + + def errors_from_http_status(status) + case status + when 438 + { + general: [Errors::IMAGE_LOAD_FAILURE], + front: [Errors::IMAGE_LOAD_FAILURE_FIELD], + back: [Errors::IMAGE_LOAD_FAILURE_FIELD], + } + when 439 + { + general: [Errors::PIXEL_DEPTH_FAILURE], + front: [Errors::PIXEL_DEPTH_FAILURE_FIELD], + back: [Errors::PIXEL_DEPTH_FAILURE_FIELD], + } + when 440 + { + general: [Errors::IMAGE_SIZE_FAILURE], + front: [Errors::IMAGE_SIZE_FAILURE_FIELD], + back: [Errors::IMAGE_SIZE_FAILURE_FIELD], + } + end + end end end end diff --git a/app/services/doc_auth/mock/doc_auth_mock_client.rb b/app/services/doc_auth/mock/doc_auth_mock_client.rb index 105e54314af..141f8f986c7 100644 --- a/app/services/doc_auth/mock/doc_auth_mock_client.rb +++ b/app/services/doc_auth/mock/doc_auth_mock_client.rb @@ -127,6 +127,13 @@ def http_error_response(image, side) } end return nil unless errors + errors = errors.tap do |h| + if h.has_key?(:result) + h[:front] = h[:result] + h[:back] = h[:result] + h.delete(:result) + end + end message = [ self.class.name, 'Unexpected HTTP response', diff --git a/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb b/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb index 4dd103ff2a2..8865965b7c5 100644 --- a/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb +++ b/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb @@ -26,7 +26,7 @@ it 'get general error for 4xx' do stub_request(:get, url).to_return(status: 440) response = described_class.new(config: config, instance_id: instance_id).fetch - expect(response.errors).to have_key(:general) + expect(response.errors).to include(:general, :front, :back) expect(response.network_error?).to eq(false) end From c8c08eebc1597cd3890cc2025c1f10d40af53f85 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Sat, 21 Oct 2023 12:37:48 -0400 Subject: [PATCH 5/9] LG-10889: fix issue when both side's error message keys are the same, the translated message turns out to be default. Seems caused by some library bug due to value collision of the hash. --- app/services/doc_auth_router.rb | 13 ++++--- spec/services/doc_auth_router_spec.rb | 56 +++++++++++++++++++-------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/app/services/doc_auth_router.rb b/app/services/doc_auth_router.rb index 1b0885b616a..5049d4398ab 100644 --- a/app/services/doc_auth_router.rb +++ b/app/services/doc_auth_router.rb @@ -79,11 +79,11 @@ module DocAuthRouter # i18n-tasks-use t('doc_auth.errors.glare.failed_short') DocAuth::Errors::GLARE_LOW_FIELD => 'doc_auth.errors.glare.failed_short', # i18n-tasks-use t('doc_auth.errors.http.image_load') - DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load', - DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD => 'doc_auth.errors.http.image_load', + DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load.top_msg', + DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD => 'doc_auth.errors.http.image_load.failed_short', # i18n-tasks-use t('doc_auth.errors.http.pixel_depth') - DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth', - DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD => 'doc_auth.errors.http.pixel_depth', + DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth.top_msg', + DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD => 'doc_auth.errors.http.pixel_depth.failed_short', # i18n-tasks-use t('doc_auth.errors.http.image_size.top_msg') DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size.top_msg', DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD => 'doc_auth.errors.http.image_size.failed_short', @@ -125,7 +125,9 @@ def translate_doc_auth_errors!(response) error_keys = DocAuth::ErrorGenerator::ERROR_KEYS.dup error_keys.each do |category| - response.errors[category]&.map! do |plain_error| + cat_errors = response.errors[category] + next unless cat_errors + translated_cat_errors = cat_errors.map do |plain_error| error_key = ERROR_TRANSLATIONS[plain_error] if error_key I18n.t(error_key) @@ -134,6 +136,7 @@ def translate_doc_auth_errors!(response) I18n.t('doc_auth.errors.general.no_liveness') end end + response.errors[category] = translated_cat_errors end end diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb index f63c85f2675..672668418e8 100644 --- a/spec/services/doc_auth_router_spec.rb +++ b/spec/services/doc_auth_router_spec.rb @@ -230,22 +230,46 @@ def reload_ab_test_initializer! ) end - it 'translates http response errors and maintains exceptions' do - DocAuth::Mock::DocAuthMockClient.mock_response!( - method: :post_images, - response: DocAuth::Response.new( - success: false, - errors: { - general: [DocAuth::Errors::IMAGE_LOAD_FAILURE], - }, - exception: DocAuth::RequestError.new('Test 438 HTTP failure', 438), - ), - ) - - response = proxy.post_images(front_image: 'a', back_image: 'b') - - expect(response.errors).to eq(general: [I18n.t('doc_auth.errors.http.image_load')]) - expect(response.exception.message).to eq('Test 438 HTTP failure') + context 'translates http response errors and maintains exceptions' do + it 'translate general message' do + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_images, + response: DocAuth::Response.new( + success: false, + errors: { + general: [DocAuth::Errors::IMAGE_LOAD_FAILURE], + }, + exception: DocAuth::RequestError.new('Test 438 HTTP failure', 438), + ), + ) + + response = proxy.post_images(front_image: 'a', back_image: 'b') + expect(response.errors).to eq(general: [I18n.t('doc_auth.errors.http.image_load.top_msg')]) + expect(response.exception.message).to eq('Test 438 HTTP failure') + end + it 'translate general message with side related inline error message' do + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_images, + response: DocAuth::Response.new( + success: false, + errors: { + general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], + front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], + back: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], + }, + exception: DocAuth::RequestError.new('Test 440 HTTP failure', 440), + ), + ) + + response = proxy.post_images(front_image: 'a', back_image: 'b') + + expect(response.errors).to eq( + general: [I18n.t('doc_auth.errors.http.image_size.top_msg')], + front: [I18n.t('doc_auth.errors.http.image_size.failed_short')], + back: [I18n.t('doc_auth.errors.http.image_size.failed_short')], + ) + expect(response.exception.message).to eq('Test 440 HTTP failure') + end end it 'translates doc type error' do From d4d9a78e7bf73e0ca34a188f3988dc0fbc78e449 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Sat, 21 Oct 2023 16:25:19 -0400 Subject: [PATCH 6/9] LG-10889: use consistent wording. --- spec/services/doc_auth/acuant/acuant_client_spec.rb | 10 +++++----- .../acuant/requests/upload_image_request_spec.rb | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/services/doc_auth/acuant/acuant_client_spec.rb b/spec/services/doc_auth/acuant/acuant_client_spec.rb index 391d1bbe0a6..ab68738fce2 100644 --- a/spec/services/doc_auth/acuant/acuant_client_spec.rb +++ b/spec/services/doc_auth/acuant/acuant_client_spec.rb @@ -217,8 +217,8 @@ end end - context 'when there is unexpected http status code' do - shared_examples 'with http status 4xx' do |status| + context 'when there is expected rxx http status code' do + shared_examples 'with http status' do |status| it "generate response for status #{status} " do instance_id = 'this-is-a-test-instance-id' url = URI.join( @@ -259,8 +259,8 @@ end end end - it_should_behave_like 'with http status 4xx', 440 - it_should_behave_like 'with http status 4xx', 439 - it_should_behave_like 'with http status 4xx', 438 + it_should_behave_like 'with http status', 440 + it_should_behave_like 'with http status', 439 + it_should_behave_like 'with http status', 438 end end diff --git a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb index 7c889f64490..34cf0438e7d 100644 --- a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb +++ b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb @@ -31,7 +31,7 @@ end context 'when http status is 4xx' do - shared_examples 'http unexpected 4xx status' do |http_status, general_failure, side_failure| + shared_examples 'http expected 4xx status' do |http_status, general_failure, side_failure| it "generate errors for #{http_status}" do request_stub = stub_request(:post, url).with( query: { side: 0, light: 0 }, @@ -52,11 +52,11 @@ end end - it_should_behave_like 'http unexpected 4xx status', 440, 'image_size_failure', + it_should_behave_like 'http expected 4xx status', 440, 'image_size_failure', 'image_size_failure_field' - it_should_behave_like 'http unexpected 4xx status', 438, 'image_load_failure', + it_should_behave_like 'http expected 4xx status', 438, 'image_load_failure', 'image_load_failure_field' - it_should_behave_like 'http unexpected 4xx status', 439, 'pixel_depth_failure', + it_should_behave_like 'http expected 4xx status', 439, 'pixel_depth_failure', 'pixel_depth_failure_field' end end From 76d9886eade7870bcc71cc0fca9c4ce37ba6309e Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Sun, 22 Oct 2023 12:54:04 -0400 Subject: [PATCH 7/9] LG-10889: i18n tasks. --- app/services/doc_auth_router.rb | 7 +++++-- spec/services/doc_auth_router_spec.rb | 25 +++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/services/doc_auth_router.rb b/app/services/doc_auth_router.rb index 5049d4398ab..2545bbd859a 100644 --- a/app/services/doc_auth_router.rb +++ b/app/services/doc_auth_router.rb @@ -78,14 +78,17 @@ module DocAuthRouter DocAuth::Errors::GLARE_LOW_BOTH_SIDES => 'doc_auth.errors.glare.top_msg_plural', # i18n-tasks-use t('doc_auth.errors.glare.failed_short') DocAuth::Errors::GLARE_LOW_FIELD => 'doc_auth.errors.glare.failed_short', - # i18n-tasks-use t('doc_auth.errors.http.image_load') + # i18n-tasks-use t('doc_auth.errors.http.image_load.top_msg') DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load.top_msg', + # i18n-tasks-use t('doc_auth.errors.http.image_load.failed_short') DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD => 'doc_auth.errors.http.image_load.failed_short', - # i18n-tasks-use t('doc_auth.errors.http.pixel_depth') + # i18n-tasks-use t('doc_auth.errors.http.pixel_depth.top_msg') DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth.top_msg', + # i18n-tasks-use t('doc_auth.errors.http.pixel_depth.failed_short') DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD => 'doc_auth.errors.http.pixel_depth.failed_short', # i18n-tasks-use t('doc_auth.errors.http.image_size.top_msg') DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size.top_msg', + # i18n-tasks-use t('doc_auth.errors.http.image_size.failed_short') DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD => 'doc_auth.errors.http.image_size.failed_short', # i18n-tasks-use t('doc_auth.errors.general.fallback_field_level') DocAuth::Errors::FALLBACK_FIELD_LEVEL => 'doc_auth.errors.general.fallback_field_level', diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb index 672668418e8..061b405634c 100644 --- a/spec/services/doc_auth_router_spec.rb +++ b/spec/services/doc_auth_router_spec.rb @@ -247,7 +247,7 @@ def reload_ab_test_initializer! expect(response.errors).to eq(general: [I18n.t('doc_auth.errors.http.image_load.top_msg')]) expect(response.exception.message).to eq('Test 438 HTTP failure') end - it 'translate general message with side related inline error message' do + it 'translate related inline error messages for both sides' do DocAuth::Mock::DocAuthMockClient.mock_response!( method: :post_images, response: DocAuth::Response.new( @@ -255,7 +255,7 @@ def reload_ab_test_initializer! errors: { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], - back: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], + back: [DocAuth::Errors::IMAGE_SIZE_FAILURE], }, exception: DocAuth::RequestError.new('Test 440 HTTP failure', 440), ), @@ -270,6 +270,27 @@ def reload_ab_test_initializer! ) expect(response.exception.message).to eq('Test 440 HTTP failure') end + it 'translate related side specific inline error message' do + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_images, + response: DocAuth::Response.new( + success: false, + errors: { + general: [DocAuth::Errors::PIXEL_DEPTH_FAILURE], + front: [DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD], + }, + exception: DocAuth::RequestError.new('Test 439 HTTP failure', 439), + ), + ) + + response = proxy.post_images(front_image: 'a', back_image: 'b') + + expect(response.errors).to eq( + general: [I18n.t('doc_auth.errors.http.pixel_depth.top_msg')], + front: [I18n.t('doc_auth.errors.http.pixel_depth.failed_short')], + ) + expect(response.exception.message).to eq('Test 439 HTTP failure') + end end it 'translates doc type error' do From d02e2f36f312e7ea9959e659178468aba6a7cf35 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Sun, 22 Oct 2023 13:13:56 -0400 Subject: [PATCH 8/9] LG-10889: accidental change. changelog: User-Facing Improvements, Document Capturing, Improve image related inline error messages. --- spec/services/doc_auth_router_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb index 061b405634c..bbfcaec3c8c 100644 --- a/spec/services/doc_auth_router_spec.rb +++ b/spec/services/doc_auth_router_spec.rb @@ -255,7 +255,7 @@ def reload_ab_test_initializer! errors: { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE], front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], - back: [DocAuth::Errors::IMAGE_SIZE_FAILURE], + back: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD], }, exception: DocAuth::RequestError.new('Test 440 HTTP failure', 440), ), From f15129861996de446937ef49537604967775be44 Mon Sep 17 00:00:00 2001 From: Dawei Wang Date: Wed, 25 Oct 2023 10:47:54 -0400 Subject: [PATCH 9/9] LG-10889: code format. --- app/services/doc_auth/acuant/request.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/doc_auth/acuant/request.rb b/app/services/doc_auth/acuant/request.rb index f625359e93e..852dd66c05c 100644 --- a/app/services/doc_auth/acuant/request.rb +++ b/app/services/doc_auth/acuant/request.rb @@ -133,12 +133,12 @@ def handle_expected_http_error(http_response) def errors_from_http_status(status) error = case status - when 438 - Errors::IMAGE_LOAD_FAILURE - when 439 - Errors::PIXEL_DEPTH_FAILURE - when 440 - Errors::IMAGE_SIZE_FAILURE + when 438 + Errors::IMAGE_LOAD_FAILURE + when 439 + Errors::PIXEL_DEPTH_FAILURE + when 440 + Errors::IMAGE_SIZE_FAILURE end { general: [error] } end