Skip to content

Commit

Permalink
[10-10CG] Add ability to retry schema validation (#19571)
Browse files Browse the repository at this point in the history
* add ability to retry schema validation

* rename some spec for clarity

* Add log for successful validation call
  • Loading branch information
coope93 authored Nov 25, 2024
1 parent 0c6644b commit e4cdb22
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
42 changes: 42 additions & 0 deletions app/models/saved_claim/caregivers_assistance_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ def to_pdf(filename = nil, **)
raise
end

def form_matches_schema
super unless Flipper.enabled?(:caregiver_retry_form_validation)

return unless form_is_string

schema = VetsJsonSchema::SCHEMAS[self.class::FORM]
validation_errors = validate_form_with_retries(schema)

validation_errors.each do |e|
errors.add(e[:fragment], e[:message])
e[:errors]&.flatten(2)&.each { |nested| errors.add(nested[:fragment], nested[:message]) if nested.is_a? Hash }
end

unless validation_errors.empty?
Rails.logger.error('SavedClaim form did not pass validation', { guid:, errors: validation_errors })
end
end

# SavedClaims require regional_office to be defined, CaregiversAssistanceClaim has no purpose for it.
#
# CaregiversAssistanceClaims are not processed regional VA offices.
Expand Down Expand Up @@ -75,4 +93,28 @@ def destroy_attachment

Form1010cg::Attachment.find_by(guid: parsed_form['poaAttachmentId'])&.destroy!
end

def validate_form_with_retries(schema)
attempts = 0
max_attempts = 3

begin
attempts += 1
errors_array = JSON::Validator.fully_validate(schema, parsed_form, { errors_as_objects: true })
Rails.logger.info("Form validation succeeded on attempt #{attempts}/#{max_attempts}") if attempts > 1
errors_array
rescue => e
if attempts <= max_attempts
Rails.logger.warn("Retrying form validation due to error: #{e.message} (Attempt #{attempts}/#{max_attempts})")
sleep(1) # Delay 1 second in between attempts
retry
else
PersonalInformationLog.create(data: { schema:, parsed_form:, params: { errors_as_objects: true } },
error_class: 'SavedClaim FormValidationError')
Rails.logger.error('Error during form validation after maximimum retries',
{ error: e.message, backtrace: e.backtrace, schema: })
raise
end
end
end
end
3 changes: 3 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ features:
actor_type: user
description: Send 10-10CG submission failure email to Veteran using VANotify.
enable_in_development: true
caregiver_retry_form_validation:
actor_type: user
description: Enables 1010CG to retry schema validation
hca_browser_monitoring_enabled:
actor_type: user
description: Enables browser monitoring for the health care application.
Expand Down
135 changes: 135 additions & 0 deletions spec/models/saved_claim/caregivers_assistance_claim_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,141 @@
end
end

describe 'validations' do
let(:claim) { build(:caregivers_assistance_claim) }

before do
allow(Flipper).to receive(:enabled?).and_call_original
end

context 'caregiver_retry_form_validation disabled' do
before do
allow(Flipper).to receive(:enabled?).with(:caregiver_retry_form_validation).and_return(false)
end

context 'no validation errors' do
before do
allow(JSON::Validator).to receive(:fully_validate).and_return([])
end

it 'returns true' do
expect(claim.validate).to eq true
end
end

context 'validation errors' do
it 'calls the parent method when the toggle is off' do
allow(claim).to receive(:form_matches_schema).and_call_original

claim.validate

expect(claim).to have_received(:form_matches_schema)
end
end
end

context 'caregiver_retry_form_validation enabled' do
before do
allow(Flipper).to receive(:enabled?).with(:caregiver_retry_form_validation).and_return(true)
end

context 'no validation errors' do
before do
allow(JSON::Validator).to receive(:fully_validate).and_return([])
end

it 'returns true' do
expect(Rails.logger).not_to receive(:info)
.with('Form validation succeeded on attempt 1/3')

expect(claim.validate).to eq true
end
end

context 'validation errors' do
let(:schema_errors) { [{ fragment: 'error' }] }

context 'when JSON:Validator.fully_validate returns errors' do
before do
allow(JSON::Validator).to receive(:fully_validate).and_return(schema_errors)
end

it 'adds validation errors to the form' do
expect(JSON::Validator).not_to receive(:fully_validate_schema)

expect(Rails.logger).not_to receive(:info)
.with('Form validation succeeded on attempt 1/3')

claim.validate
expect(claim.errors.full_messages).not_to be_empty
end
end

context 'when JSON:Validator.fully_validate throws an exception' do
let(:exception_text) { 'Some exception' }
let(:exception) { StandardError.new(exception_text) }

context '3 times' do
let(:schema) { 'schema_content' }

before do
allow(VetsJsonSchema::SCHEMAS).to receive(:[]).and_return(schema)
allow(JSON::Validator).to receive(:fully_validate).and_raise(exception)
end

it 'logs exceptions and raises exception' do
expect(Rails.logger).to receive(:warn)
.with("Retrying form validation due to error: #{exception_text} (Attempt 1/3)").once
expect(Rails.logger).not_to receive(:info)
.with('Form validation succeeded on attempt 1/3')
expect(Rails.logger).to receive(:warn)
.with("Retrying form validation due to error: #{exception_text} (Attempt 2/3)").once
expect(Rails.logger).to receive(:warn)
.with("Retrying form validation due to error: #{exception_text} (Attempt 3/3)").once

expect(Rails.logger).to receive(:error)
.with('Error during form validation after maximimum retries', { error: exception.message,
backtrace: anything, schema: })

expect(PersonalInformationLog).to receive(:create).with(
data: { schema: schema,
parsed_form: claim.parsed_form,
params: { errors_as_objects: true } },
error_class: 'SavedClaim FormValidationError'
)

expect { claim.validate }.to raise_error(exception.class, exception.message)
end
end

context '1 time but succeeds after retrying' do
before do
# Throws exception the first time, returns empty array on subsequent calls
call_count = 0
allow(JSON::Validator).to receive(:fully_validate).and_wrap_original do
call_count += 1
if call_count == 1
raise exception
else
[]
end
end
end

it 'logs exception and validates succesfully after the retry' do
expect(Rails.logger).to receive(:warn)
.with("Retrying form validation due to error: #{exception_text} (Attempt 1/3)").once
expect(Rails.logger).to receive(:info)
.with('Form validation succeeded on attempt 2/3').once

expect(claim.validate).to eq true
end
end
end
end
end
end

describe '#process_attachments!' do
it 'raises a NotImplementedError' do
expect { subject.process_attachments! }.to raise_error(NotImplementedError)
Expand Down

0 comments on commit e4cdb22

Please sign in to comment.