diff --git a/app/models/saved_claim.rb b/app/models/saved_claim.rb index ea69a4f6d59..98509688106 100644 --- a/app/models/saved_claim.rb +++ b/app/models/saved_claim.rb @@ -99,6 +99,8 @@ def form_matches_schema unless validation_errors.empty? Rails.logger.error('SavedClaim form did not pass validation', { guid:, errors: validation_errors }) end + + schema_errors.empty? && validation_errors.empty? end def to_pdf(file_name = nil) @@ -147,14 +149,57 @@ def va_notification?(email_template_id) private + # Depending on the feature flipper, validate the *entire schema* + # via either the json_schema or json_schemer gem. + # This is tied to vets-api #19684 def validate_schema(schema) + if Flipper.enabled?(:validate_saved_claims_with_json_schemer) + validate_schema_with_json_schemer(schema) + else + validate_schema_with_json_schema(schema) + end + end + + # Depending on the feature flipper, validate the *parsed form* + # via either the json_schema or the json_schemer gem. + # This is tied to vets-api #19684 + def validate_form(schema, clear_cache) + if Flipper.enabled?(:validate_saved_claims_with_json_schemer) + validate_form_with_json_schemer(schema) + else + validate_form_with_json_schema(schema, clear_cache) + end + end + + # For json_schemer, the default behavior is not to raise an exception + # on validation, so we return an array of errors if they exist. + # This method validates the *entire schema*. + def validate_schema_with_json_schemer(schema) + errors = JSONSchemer.validate_schema(schema).to_a + return [] if errors.empty? + + reformatted_schemer_errors(errors) + end + + # For json_schema, validation errors raise an exception. + # This method validates the *entire schema*. + def validate_schema_with_json_schema(schema) JSON::Validator.fully_validate_schema(schema, { errors_as_objects: true }) rescue => e Rails.logger.error('Error during schema validation!', { error: e.message, backtrace: e.backtrace, schema: }) raise end - def validate_form(schema, clear_cache) + # This method validates the *parsed form* with json_schemer. + def validate_form_with_json_schemer(schema) + errors = JSONSchemer.schema(schema).validate(parsed_form).to_a + return [] if errors.empty? + + reformatted_schemer_errors(errors) + end + + # This method validates the *parsed form* with json_schema. + def validate_form_with_json_schema(schema, clear_cache) JSON::Validator.fully_validate(schema, parsed_form, { errors_as_objects: true, clear_cache: }) rescue => e PersonalInformationLog.create(data: { schema:, parsed_form:, params: { errors_as_objects: true, clear_cache: } }, @@ -164,6 +209,18 @@ def validate_form(schema, clear_cache) raise end + # This method exists to change the json_schemer errors + # to be formatted like json_schema errors, which keeps + # the error logging smooth and identical for both options. + def reformatted_schemer_errors(errors) + errors.map!(&:symbolize_keys) + errors.each do |error| + error[:fragment] = error[:data_pointer] + error[:message] = error[:error] + end + errors + end + def attachment_keys [] end diff --git a/config/features.yml b/config/features.yml index 9bdac7133d3..c4d42a7791a 100644 --- a/config/features.yml +++ b/config/features.yml @@ -1537,6 +1537,10 @@ features: actor_type: user description: When enabled, the VAProfile::V3::ContactInformation will be enabled enable_in_development: true + validate_saved_claims_with_json_schemer: + actor_type: user + description: When enabled, Saved Claims will be validated using the JSON Schemer gem rather than JSON Schema + enable_in_development: false veteran_onboarding_beta_flow: actor_type: user description: Conditionally display the new veteran onboarding flow to user diff --git a/modules/pensions/spec/models/pensions/saved_claim_spec.rb b/modules/pensions/spec/models/pensions/saved_claim_spec.rb index 1924a1a9faf..8ffb9f0748b 100644 --- a/modules/pensions/spec/models/pensions/saved_claim_spec.rb +++ b/modules/pensions/spec/models/pensions/saved_claim_spec.rb @@ -49,18 +49,45 @@ ) end - describe '#process_attachments!' do - it 'sets the attachments saved_claim_id' do - expect(Lighthouse::SubmitBenefitsIntakeClaim).not_to receive(:perform_async).with(claim.id) - claim.process_attachments! - expect(claim.persistent_attachments.size).to eq(2) + context 'using JSON Schema' do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) + end + + describe '#process_attachments!' do + it 'sets the attachments saved_claim_id' do + expect(Lighthouse::SubmitBenefitsIntakeClaim).not_to receive(:perform_async).with(claim.id) + claim.process_attachments! + expect(claim.persistent_attachments.size).to eq(2) + end + end + + describe '#destroy' do + it 'also destroys the persistent_attachments' do + claim.process_attachments! + expect { claim.destroy }.to change(PersistentAttachment, :count).by(-2) + end end end - describe '#destroy' do - it 'also destroys the persistent_attachments' do - claim.process_attachments! - expect { claim.destroy }.to change(PersistentAttachment, :count).by(-2) + context 'using JSON Schemer' do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(true) + end + + describe '#process_attachments!' do + it 'sets the attachments saved_claim_id' do + expect(Lighthouse::SubmitBenefitsIntakeClaim).not_to receive(:perform_async).with(claim.id) + claim.process_attachments! + expect(claim.persistent_attachments.size).to eq(2) + end + end + + describe '#destroy' do + it 'also destroys the persistent_attachments' do + claim.process_attachments! + expect { claim.destroy }.to change(PersistentAttachment, :count).by(-2) + end end end end diff --git a/spec/factories/gids_response.rb b/spec/factories/gids_response.rb index 7aeebbe5a5f..c0704b9c965 100644 --- a/spec/factories/gids_response.rb +++ b/spec/factories/gids_response.rb @@ -16,11 +16,11 @@ versioned_school_certifying_officials: [ { priority: 'Primary', - email: 'test@edu_sample.com' + email: 'user@school.edu' }, { priority: 'Secondary', - email: 'test@edu_sample.com' + email: 'user@school.edu' } ] } diff --git a/spec/factories/va10203.rb b/spec/factories/va10203.rb index c07374c7735..046f47e7610 100644 --- a/spec/factories/va10203.rb +++ b/spec/factories/va10203.rb @@ -18,7 +18,7 @@ schoolCity: 'Test', schoolState: 'TN', schoolCountry: 'USA', - schoolEmailAddress: 'test@edu_sample.com', + schoolEmailAddress: 'user@school.edu', schoolStudentId: '01010101', isActiveDuty: true, veteranAddress: { @@ -58,7 +58,7 @@ schoolCity: 'Test 2', schoolState: 'SC', schoolCountry: 'USA', - schoolEmailAddress: 'test@edu_sample.com', + schoolEmailAddress: 'user@school.edu', schoolStudentId: '01010101', isActiveDuty: true, veteranAddress: { @@ -98,7 +98,7 @@ schoolCity: 'Test 2', schoolState: 'SC', schoolCountry: 'USA', - schoolEmailAddress: 'test@edu_sample.com', + schoolEmailAddress: 'user@school.edu', schoolStudentId: '01010101', isActiveDuty: true, veteranAddress: { diff --git a/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb b/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb index 891794840ca..dc84bb6c218 100644 --- a/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb +++ b/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb @@ -12,6 +12,8 @@ before do Sidekiq::Job.clear_all Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) + allow(Flipper).to receive(:enabled?).with(:form526_send_backup_submission_exhaustion_email_notice).and_return(false) end let(:user) { FactoryBot.create(:user, :loa3) } @@ -56,7 +58,8 @@ context 'when form526_send_backup_submission_exhaustion_email_notice is enabled' do before do - Flipper.enable(:form526_send_backup_submission_exhaustion_email_notice) + allow(Flipper).to receive(:enabled?) + .with(:form526_send_backup_submission_exhaustion_email_notice).and_return(true) end it 'remediates the submission via an email notification' do @@ -103,111 +106,119 @@ end %w[single multi].each do |payload_method| - describe ".perform_async, enabled, #{payload_method} payload" do - before do - allow(Settings.form526_backup).to receive_messages(submission_method: payload_method, enabled: true) - end + [true, false].each do |flipper| + describe ".perform_async, enabled, #{payload_method} payload" do + before do + allow(Settings.form526_backup).to receive_messages(submission_method: payload_method, enabled: true) + end - let!(:submission) { create :form526_submission, :with_everything } - let!(:upload_data) { submission.form[Form526Submission::FORM_526_UPLOADS] } + let!(:submission) { create :form526_submission, :with_everything } + let!(:upload_data) { submission.form[Form526Submission::FORM_526_UPLOADS] } - context 'successfully' do - before do - upload_data.each do |ud| - file = Rack::Test::UploadedFile.new('spec/fixtures/files/doctors-note.pdf', 'application/pdf') - sea = SupportingEvidenceAttachment.find_or_create_by(guid: ud['confirmationCode']) - sea.set_file_data!(file) - sea.save! + context "when json_schemer flipper is #{flipper}" do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper) end - end - it 'creates a job for submission' do - expect { subject.perform_async(submission.id) }.to change(subject.jobs, :size).by(1) - end + context 'successfully' do + before do + upload_data.each do |ud| + file = Rack::Test::UploadedFile.new('spec/fixtures/files/doctors-note.pdf', 'application/pdf') + sea = SupportingEvidenceAttachment.find_or_create_by(guid: ud['confirmationCode']) + sea.set_file_data!(file) + sea.save! + end + end - it 'submits' do - new_form_data = submission.saved_claim.parsed_form - new_form_data['startedFormVersion'] = nil - submission.saved_claim.form = new_form_data.to_json - submission.saved_claim.save - VCR.use_cassette('lighthouse/benefits_intake/200_lighthouse_intake_upload_location') do - VCR.use_cassette('form526_backup/200_evss_get_pdf') do - VCR.use_cassette('lighthouse/benefits_intake/200_lighthouse_intake_upload') do - jid = subject.perform_async(submission.id) - last = subject.jobs.last - jid_from_jobs = last['jid'] - expect(jid).to eq(jid_from_jobs) - described_class.drain - expect(jid).not_to be_empty - - # The Backup Submission process gathers form 526 and any ancillary forms - # to send to Central Mail at the same time - - # Form 4142 Backup Submission Process - expect(submission.form['form4142']).not_to be(nil) - form4142_processor = DecisionReviewV1::Processor::Form4142Processor.new( - form_data: submission.form['form4142'], submission_id: submission.id - ) - request_body = form4142_processor.request_body - metadata_hash = JSON.parse(request_body['metadata']) - form4142_received_date = metadata_hash['receiveDt'].in_time_zone('Central Time (US & Canada)') - expect( - submission.created_at.in_time_zone('Central Time (US & Canada)') - ).to be_within(1.second).of(form4142_received_date) - - # Form 0781 Backup Submission Process - expect(submission.form['form0781']).not_to be(nil) - # not really a way to test the dates here - - job_status = Form526JobStatus.last - expect(job_status.form526_submission_id).to eq(submission.id) - expect(job_status.job_class).to eq('BackupSubmission') - expect(job_status.job_id).to eq(jid) - expect(job_status.status).to eq('success') - submission = Form526Submission.last - expect(submission.backup_submitted_claim_id).not_to be(nil) - expect(submission.submit_endpoint).to eq('benefits_intake_api') + it 'creates a job for submission' do + expect { subject.perform_async(submission.id) }.to change(subject.jobs, :size).by(1) + end + + it 'submits' do + new_form_data = submission.saved_claim.parsed_form + new_form_data['startedFormVersion'] = nil + submission.saved_claim.form = new_form_data.to_json + submission.saved_claim.save + VCR.use_cassette('lighthouse/benefits_intake/200_lighthouse_intake_upload_location') do + VCR.use_cassette('form526_backup/200_evss_get_pdf') do + VCR.use_cassette('lighthouse/benefits_intake/200_lighthouse_intake_upload') do + jid = subject.perform_async(submission.id) + last = subject.jobs.last + jid_from_jobs = last['jid'] + expect(jid).to eq(jid_from_jobs) + described_class.drain + expect(jid).not_to be_empty + + # The Backup Submission process gathers form 526 and any ancillary forms + # to send to Central Mail at the same time + + # Form 4142 Backup Submission Process + expect(submission.form['form4142']).not_to be(nil) + form4142_processor = DecisionReviewV1::Processor::Form4142Processor.new( + form_data: submission.form['form4142'], submission_id: submission.id + ) + request_body = form4142_processor.request_body + metadata_hash = JSON.parse(request_body['metadata']) + form4142_received_date = metadata_hash['receiveDt'].in_time_zone('Central Time (US & Canada)') + expect( + submission.created_at.in_time_zone('Central Time (US & Canada)') + ).to be_within(1.second).of(form4142_received_date) + + # Form 0781 Backup Submission Process + expect(submission.form['form0781']).not_to be(nil) + # not really a way to test the dates here + + job_status = Form526JobStatus.last + expect(job_status.form526_submission_id).to eq(submission.id) + expect(job_status.job_class).to eq('BackupSubmission') + expect(job_status.job_id).to eq(jid) + expect(job_status.status).to eq('success') + submission = Form526Submission.last + expect(submission.backup_submitted_claim_id).not_to be(nil) + expect(submission.submit_endpoint).to eq('benefits_intake_api') + end + end end end end - end - end - context 'with a submission timeout' do - before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError) - end + context 'with a submission timeout' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError) + end - it 'raises a gateway timeout error' do - jid = subject.perform_async(submission.id) - expect { described_class.drain }.to raise_error(Common::Exceptions::GatewayTimeout) - job_status = Form526JobStatus.find_by(job_id: jid) - expect(job_status.form526_submission_id).to eq(submission.id) - expect(job_status.job_class).to eq('BackupSubmission') - expect(job_status.job_id).to eq(jid) - expect(job_status.status).to eq('retryable_error') - error = job_status.bgjob_errors - expect(error.first.last['error_class']).to eq('Common::Exceptions::GatewayTimeout') - expect(error.first.last['error_message']).to eq('Gateway timeout') - end - end + it 'raises a gateway timeout error' do + jid = subject.perform_async(submission.id) + expect { described_class.drain }.to raise_error(Common::Exceptions::GatewayTimeout) + job_status = Form526JobStatus.find_by(job_id: jid) + expect(job_status.form526_submission_id).to eq(submission.id) + expect(job_status.job_class).to eq('BackupSubmission') + expect(job_status.job_id).to eq(jid) + expect(job_status.status).to eq('retryable_error') + error = job_status.bgjob_errors + expect(error.first.last['error_class']).to eq('Common::Exceptions::GatewayTimeout') + expect(error.first.last['error_message']).to eq('Gateway timeout') + end + end - context 'with an unexpected error' do - before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) - end + context 'with an unexpected error' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) + end - it 'raises a standard error' do - jid = subject.perform_async(submission.id) - expect { described_class.drain }.to raise_error(StandardError) - job_status = Form526JobStatus.find_by(job_id: jid) - expect(job_status.form526_submission_id).to eq(submission.id) - expect(job_status.job_class).to eq('BackupSubmission') - expect(job_status.job_id).to eq(jid) - expect(job_status.status).to eq('retryable_error') - error = job_status.bgjob_errors - expect(error.first.last['error_class']).to eq('StandardError') - expect(error.first.last['error_message']).to eq('foo') + it 'raises a standard error' do + jid = subject.perform_async(submission.id) + expect { described_class.drain }.to raise_error(StandardError) + job_status = Form526JobStatus.find_by(job_id: jid) + expect(job_status.form526_submission_id).to eq(submission.id) + expect(job_status.job_class).to eq('BackupSubmission') + expect(job_status.job_id).to eq(jid) + expect(job_status.status).to eq('retryable_error') + error = job_status.bgjob_errors + expect(error.first.last['error_class']).to eq('StandardError') + expect(error.first.last['error_message']).to eq('foo') + end + end end end end diff --git a/spec/lib/vre/monitor_spec.rb b/spec/lib/vre/monitor_spec.rb index 6b7754ef547..ff5fbdc64ac 100644 --- a/spec/lib/vre/monitor_spec.rb +++ b/spec/lib/vre/monitor_spec.rb @@ -28,34 +28,42 @@ let(:encrypted_user) { KmsEncrypted::Box.new.encrypt(user_struct.to_h.to_json) } describe '#track_submission_exhaustion' do - it 'logs sidekiq job exhaustion failure avoided' do - msg = { 'args' => [claim.id, encrypted_user], error_message: 'Error!' } + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) + end - log = "Failed all retries on VRE::Submit1900Job, last error: #{msg['error_message']}" - payload = { - message: msg - } + it 'logs sidekiq job exhaustion failure avoided' do + msg = { 'args' => [claim.id, encrypted_user], error_message: 'Error!' } - expect(monitor).to receive(:log_silent_failure_avoided).with(payload, nil, anything) - expect(StatsD).to receive(:increment).with("#{submission_stats_key}.exhausted") - expect(Rails.logger).to receive(:error).with(log) + log = "Failed all retries on VRE::Submit1900Job, last error: #{msg['error_message']}" + payload = { + message: msg + } - monitor.track_submission_exhaustion(msg, user_struct.va_profile_email) - end + expect(monitor).to receive(:log_silent_failure_avoided).with(payload, nil, anything) + expect(StatsD).to receive(:increment).with("#{submission_stats_key}.exhausted") + expect(Rails.logger).to receive(:error).with(log) + + monitor.track_submission_exhaustion(msg, user_struct.va_profile_email) + end - it 'logs sidekiq job exhaustion failure' do - msg = { 'args' => [claim.id, encrypted_user], error_message: 'Error!' } + it 'logs sidekiq job exhaustion failure' do + msg = { 'args' => [claim.id, encrypted_user], error_message: 'Error!' } - log = "Failed all retries on VRE::Submit1900Job, last error: #{msg['error_message']}" - payload = { - message: msg - } + log = "Failed all retries on VRE::Submit1900Job, last error: #{msg['error_message']}" + payload = { + message: msg + } - expect(monitor).to receive(:log_silent_failure).with(payload, nil, anything) - expect(StatsD).to receive(:increment).with("#{submission_stats_key}.exhausted") - expect(Rails.logger).to receive(:error).with(log) + expect(monitor).to receive(:log_silent_failure).with(payload, nil, anything) + expect(StatsD).to receive(:increment).with("#{submission_stats_key}.exhausted") + expect(Rails.logger).to receive(:error).with(log) - monitor.track_submission_exhaustion(msg, nil) + monitor.track_submission_exhaustion(msg, nil) + end + end end end end diff --git a/spec/models/form526_submission_spec.rb b/spec/models/form526_submission_spec.rb index cddc8715fe7..31fd6dae2fc 100644 --- a/spec/models/form526_submission_spec.rb +++ b/spec/models/form526_submission_spec.rb @@ -27,1647 +27,1668 @@ let(:submit_endpoint) { nil } let(:backup_submitted_claim_status) { nil } - before do - Flipper.disable(:disability_compensation_production_tester) - end - - describe 'associations' do - it { is_expected.to have_many(:form526_submission_remediations) } - end - - describe 'submit_endpoint enum' do - context 'when submit_endpoint is evss' do - let(:submit_endpoint) { 'evss' } - - it 'is valid' do - expect(subject).to be_valid - end - end - - context 'when submit_endpoint is claims_api' do - let(:submit_endpoint) { 'claims_api' } - - it 'is valid' do - expect(subject).to be_valid + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - end - - context 'when submit_endpoint is benefits_intake_api' do - let(:submit_endpoint) { 'benefits_intake_api' } - it 'is valid' do - expect(subject).to be_valid + describe 'associations' do + it { is_expected.to have_many(:form526_submission_remediations) } end - end - context 'when submit_endpoint is not evss, claims_api or benefits_intake_api' do - it 'is invalid' do - expect do - subject.submit_endpoint = 'other_value' - end.to raise_error(ArgumentError, "'other_value' is not a valid submit_endpoint") - end - end - end + describe 'submit_endpoint enum' do + context 'when submit_endpoint is evss' do + let(:submit_endpoint) { 'evss' } - describe 'backup_submitted_claim_status enum' do - context 'when backup_submitted_claim_status is nil' do - it 'is valid' do - expect(subject).to be_valid - end - end - - context 'when backup_submitted_claim_status is accepted' do - let(:backup_submitted_claim_status) { 'accepted' } + it 'is valid' do + expect(subject).to be_valid + end + end - it 'is valid' do - expect(subject).to be_valid - end - end + context 'when submit_endpoint is claims_api' do + let(:submit_endpoint) { 'claims_api' } - context 'when backup_submitted_claim_status is rejected' do - let(:backup_submitted_claim_status) { 'rejected' } + it 'is valid' do + expect(subject).to be_valid + end + end - it 'is valid' do - expect(subject).to be_valid - end - end + context 'when submit_endpoint is benefits_intake_api' do + let(:submit_endpoint) { 'benefits_intake_api' } - context 'when backup_submitted_claim_status is paranoid_success' do - let(:backup_submitted_claim_status) { 'paranoid_success' } + it 'is valid' do + expect(subject).to be_valid + end + end - it 'is valid' do - expect(subject).to be_valid + context 'when submit_endpoint is not evss, claims_api or benefits_intake_api' do + it 'is invalid' do + expect do + subject.submit_endpoint = 'other_value' + end.to raise_error(ArgumentError, "'other_value' is not a valid submit_endpoint") + end + end end - end - context 'when backup_submitted_claim_status is neither accepted, rejected nor nil' do - it 'is invalid' do - expect do - subject.backup_submitted_claim_status = 'other_value' - end.to raise_error(ArgumentError, "'other_value' is not a valid backup_submitted_claim_status") - end - end - end + describe 'backup_submitted_claim_status enum' do + context 'when backup_submitted_claim_status is nil' do + it 'is valid' do + expect(subject).to be_valid + end + end - describe 'scopes' do - let!(:in_process) { create(:form526_submission) } - let!(:expired) { create(:form526_submission, :created_more_than_3_weeks_ago) } - let!(:happy_path_success) { create(:form526_submission, :with_submitted_claim_id) } - let!(:happy_lighthouse_path_success) do - create(:form526_submission, :with_submitted_claim_id, submit_endpoint: 'claims_api') - end - let!(:pending_backup) { create(:form526_submission, :backup_path) } - let!(:accepted_backup) { create(:form526_submission, :backup_path, :backup_accepted) } - let!(:rejected_backup) { create(:form526_submission, :backup_path, :backup_rejected) } - let!(:remediated) { create(:form526_submission, :remediated) } - let!(:remediated_and_expired) { create(:form526_submission, :remediated, :created_more_than_3_weeks_ago) } - let!(:remediated_and_rejected) { create(:form526_submission, :remediated, :backup_path, :backup_rejected) } - let!(:new_no_longer_remediated) { create(:form526_submission, :no_longer_remediated) } - let!(:old_no_longer_remediated) do - Timecop.freeze(3.months.ago) do - create(:form526_submission, :no_longer_remediated) - end - end - let!(:paranoid_success) { create(:form526_submission, :backup_path, :paranoid_success) } - let!(:success_by_age) do - Timecop.freeze((1.year + 1.day).ago) do - create(:form526_submission, :backup_path, :paranoid_success) - end - end - let!(:failed_primary) { create(:form526_submission, :with_failed_primary_job) } - let!(:exhausted_primary) { create(:form526_submission, :with_exhausted_primary_job) } - let!(:failed_backup) { create(:form526_submission, :with_failed_backup_job) } - let!(:exhausted_backup) { create(:form526_submission, :with_exhausted_backup_job) } - - before do - happy_lighthouse_path_success.form526_job_statuses << Form526JobStatus.new( - job_class: 'PollForm526Pdf', - status: Form526JobStatus::STATUS[:success], - job_id: 1 - ) - end + context 'when backup_submitted_claim_status is accepted' do + let(:backup_submitted_claim_status) { 'accepted' } - describe 'with_exhausted_primary_jobs' do - it 'returns submissions associated to failed or exhausted primary jobs' do - expect(Form526Submission.with_exhausted_primary_jobs).to contain_exactly( - failed_primary, - exhausted_primary - ) - end - end + it 'is valid' do + expect(subject).to be_valid + end + end - describe 'with_exhausted_backup_jobs' do - it 'returns submissions associated to failed or exhausted backup jobs' do - expect(Form526Submission.with_exhausted_backup_jobs).to contain_exactly( - failed_backup, - exhausted_backup - ) - end - end + context 'when backup_submitted_claim_status is rejected' do + let(:backup_submitted_claim_status) { 'rejected' } - describe 'paranoid_success_type' do - it 'returns records less than a year old with paranoid_success backup status' do - expect(Form526Submission.paranoid_success_type).to contain_exactly( - paranoid_success - ) - end - end + it 'is valid' do + expect(subject).to be_valid + end + end - describe 'success_by_age' do - it 'returns records more than a year old with paranoid_success backup status' do - expect(Form526Submission.success_by_age).to contain_exactly( - success_by_age - ) - end - end + context 'when backup_submitted_claim_status is paranoid_success' do + let(:backup_submitted_claim_status) { 'paranoid_success' } - describe 'pending_backup' do - it 'returns records submitted to the backup path but lacking a decisive state' do - expect(Form526Submission.pending_backup).to contain_exactly( - pending_backup - ) - end - end + it 'is valid' do + expect(subject).to be_valid + end + end - describe 'in_process' do - it 'only returns submissions that are still in process' do - expect(Form526Submission.in_process).to contain_exactly( - in_process, - new_no_longer_remediated, - exhausted_primary, - failed_primary - ) + context 'when backup_submitted_claim_status is neither accepted, rejected nor nil' do + it 'is invalid' do + expect do + subject.backup_submitted_claim_status = 'other_value' + end.to raise_error(ArgumentError, "'other_value' is not a valid backup_submitted_claim_status") + end + end end - end - describe 'incomplete_type' do - it 'only returns submissions that are still in process' do - expect(Form526Submission.incomplete_type).to contain_exactly( - in_process, - pending_backup, - new_no_longer_remediated, - exhausted_primary, - failed_primary - ) - end - end + describe 'scopes' do + let!(:in_process) { create(:form526_submission) } + let!(:expired) { create(:form526_submission, :created_more_than_3_weeks_ago) } + let!(:happy_path_success) { create(:form526_submission, :with_submitted_claim_id) } + let!(:happy_lighthouse_path_success) do + create(:form526_submission, :with_submitted_claim_id, submit_endpoint: 'claims_api') + end + let!(:pending_backup) { create(:form526_submission, :backup_path) } + let!(:accepted_backup) { create(:form526_submission, :backup_path, :backup_accepted) } + let!(:rejected_backup) { create(:form526_submission, :backup_path, :backup_rejected) } + let!(:remediated) { create(:form526_submission, :remediated) } + let!(:remediated_and_expired) { create(:form526_submission, :remediated, :created_more_than_3_weeks_ago) } + let!(:remediated_and_rejected) { create(:form526_submission, :remediated, :backup_path, :backup_rejected) } + let!(:new_no_longer_remediated) { create(:form526_submission, :no_longer_remediated) } + let!(:old_no_longer_remediated) do + Timecop.freeze(3.months.ago) do + create(:form526_submission, :no_longer_remediated) + end + end + let!(:paranoid_success) { create(:form526_submission, :backup_path, :paranoid_success) } + let!(:success_by_age) do + Timecop.freeze((1.year + 1.day).ago) do + create(:form526_submission, :backup_path, :paranoid_success) + end + end + let!(:failed_primary) { create(:form526_submission, :with_failed_primary_job) } + let!(:exhausted_primary) { create(:form526_submission, :with_exhausted_primary_job) } + let!(:failed_backup) { create(:form526_submission, :with_failed_backup_job) } + let!(:exhausted_backup) { create(:form526_submission, :with_exhausted_backup_job) } + + before do + happy_lighthouse_path_success.form526_job_statuses << Form526JobStatus.new( + job_class: 'PollForm526Pdf', + status: Form526JobStatus::STATUS[:success], + job_id: 1 + ) + end - describe 'accepted_to_primary_path' do - it 'returns submissions with a submitted_claim_id' do - expect(Form526Submission.accepted_to_evss_primary_path).to contain_exactly( - happy_path_success - ) - end + describe 'with_exhausted_primary_jobs' do + it 'returns submissions associated to failed or exhausted primary jobs' do + expect(Form526Submission.with_exhausted_primary_jobs).to contain_exactly( + failed_primary, + exhausted_primary + ) + end + end - it 'returns Lighthouse submissions with a found PDF with a submitted_claim_id' do - expect(Form526Submission.accepted_to_lighthouse_primary_path).to contain_exactly(happy_lighthouse_path_success) - end + describe 'with_exhausted_backup_jobs' do + it 'returns submissions associated to failed or exhausted backup jobs' do + expect(Form526Submission.with_exhausted_backup_jobs).to contain_exactly( + failed_backup, + exhausted_backup + ) + end + end - it 'returns both an EVSS submission and a Lighthouse submission with a found PDF and a submitted_claim_id' do - expect(Form526Submission.accepted_to_primary_path).to contain_exactly( - happy_path_success, happy_lighthouse_path_success - ) - end + describe 'paranoid_success_type' do + it 'returns records less than a year old with paranoid_success backup status' do + expect(Form526Submission.paranoid_success_type).to contain_exactly( + paranoid_success + ) + end + end - it 'does not return the LH submission when the PDF is not found' do - happy_lighthouse_path_success.form526_job_statuses.last.update(status: Form526JobStatus::STATUS[:pdf_not_found]) + describe 'success_by_age' do + it 'returns records more than a year old with paranoid_success backup status' do + expect(Form526Submission.success_by_age).to contain_exactly( + success_by_age + ) + end + end - expect(Form526Submission.accepted_to_lighthouse_primary_path).to be_empty - end + describe 'pending_backup' do + it 'returns records submitted to the backup path but lacking a decisive state' do + expect(Form526Submission.pending_backup).to contain_exactly( + pending_backup + ) + end + end - it 'returns the EVSS submission when the Lighthouse submission is not found' do - happy_lighthouse_path_success.update(submitted_claim_id: nil) - expect(Form526Submission.accepted_to_primary_path).to contain_exactly( - happy_path_success - ) - end + describe 'in_process' do + it 'only returns submissions that are still in process' do + expect(Form526Submission.in_process).to contain_exactly( + in_process, + new_no_longer_remediated, + exhausted_primary, + failed_primary + ) + end + end - it 'returns the Lighthouse submission when the EVSS submission has no submitted claim id' do - happy_path_success.update(submitted_claim_id: nil) - expect(Form526Submission.accepted_to_primary_path).to contain_exactly( - happy_lighthouse_path_success - ) - end + describe 'incomplete_type' do + it 'only returns submissions that are still in process' do + expect(Form526Submission.incomplete_type).to contain_exactly( + in_process, + pending_backup, + new_no_longer_remediated, + exhausted_primary, + failed_primary + ) + end + end - it 'returns neither submission when neither EVSS nor Lighthouse submissions have submitted claim ids' do - happy_path_success.update(submitted_claim_id: nil) - happy_lighthouse_path_success.update(submitted_claim_id: nil) + describe 'accepted_to_primary_path' do + it 'returns submissions with a submitted_claim_id' do + expect(Form526Submission.accepted_to_evss_primary_path).to contain_exactly( + happy_path_success + ) + end - expect(Form526Submission.accepted_to_primary_path).to be_empty - end - end + it 'returns Lighthouse submissions with a found PDF with a submitted_claim_id' do + expect(Form526Submission.accepted_to_lighthouse_primary_path).to contain_exactly( + happy_lighthouse_path_success + ) + end - describe 'accepted_to_backup_path' do - it 'returns submissions with a backup_submitted_claim_id that have been explicitly accepted' do - expect(Form526Submission.accepted_to_backup_path).to contain_exactly( - accepted_backup, - paranoid_success, - success_by_age - ) - end - end + it 'returns both an EVSS submission and a Lighthouse submission with a found PDF and a submitted_claim_id' do + expect(Form526Submission.accepted_to_primary_path).to contain_exactly( + happy_path_success, happy_lighthouse_path_success + ) + end - describe 'rejected_from_backup_path' do - it 'returns submissions with a backup_submitted_claim_id that have been explicitly rejected' do - expect(Form526Submission.rejected_from_backup_path).to contain_exactly( - rejected_backup, - remediated_and_rejected - ) - end - end + it 'does not return the LH submission when the PDF is not found' do + happy_lighthouse_path_success.form526_job_statuses.last.update( + status: Form526JobStatus::STATUS[:pdf_not_found] + ) - describe 'remediated' do - it 'returns everything with a successful remediation' do - expect(Form526Submission.remediated).to contain_exactly( - remediated, - remediated_and_expired, - remediated_and_rejected - ) - end - end + expect(Form526Submission.accepted_to_lighthouse_primary_path).to be_empty + end - describe 'success_type' do - it 'returns all submissions on which no further action is required' do - expect(Form526Submission.success_type).to contain_exactly( - remediated, - remediated_and_expired, - remediated_and_rejected, - happy_path_success, - happy_lighthouse_path_success, - accepted_backup, - paranoid_success, - success_by_age - ) - end - end + it 'returns the EVSS submission when the Lighthouse submission is not found' do + happy_lighthouse_path_success.update(submitted_claim_id: nil) + expect(Form526Submission.accepted_to_primary_path).to contain_exactly( + happy_path_success + ) + end - describe 'failure_type' do - it 'returns anything not explicitly successful or still in process' do - expect(Form526Submission.failure_type).to contain_exactly( - rejected_backup, - expired, - old_no_longer_remediated, - failed_backup, - exhausted_backup - ) - end + it 'returns the Lighthouse submission when the EVSS submission has no submitted claim id' do + happy_path_success.update(submitted_claim_id: nil) + expect(Form526Submission.accepted_to_primary_path).to contain_exactly( + happy_lighthouse_path_success + ) + end - it 'handles the edge case where a submission succeeds during query building' do - expired.update!(submitted_claim_id: 'abc123') + it 'returns neither submission when neither EVSS nor Lighthouse submissions have submitted claim ids' do + happy_path_success.update(submitted_claim_id: nil) + happy_lighthouse_path_success.update(submitted_claim_id: nil) - expect(Form526Submission.failure_type).to contain_exactly( - rejected_backup, - old_no_longer_remediated, - failed_backup, - exhausted_backup - ) - end - end - end + expect(Form526Submission.accepted_to_primary_path).to be_empty + end + end - shared_examples '#start_evss_submission' do - context 'when it is all claims' do - it 'queues an all claims job' do - expect do - subject.start_evss_submission_job - end.to change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(1) - end - end - end + describe 'accepted_to_backup_path' do + it 'returns submissions with a backup_submitted_claim_id that have been explicitly accepted' do + expect(Form526Submission.accepted_to_backup_path).to contain_exactly( + accepted_backup, + paranoid_success, + success_by_age + ) + end + end - describe '#start' do - context 'the submission is for hypertension' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') - end + describe 'rejected_from_backup_path' do + it 'returns submissions with a backup_submitted_claim_id that have been explicitly rejected' do + expect(Form526Submission.rejected_from_backup_path).to contain_exactly( + rejected_backup, + remediated_and_rejected + ) + end + end - it_behaves_like '#start_evss_submission' - end + describe 'remediated' do + it 'returns everything with a successful remediation' do + expect(Form526Submission.remediated).to contain_exactly( + remediated, + remediated_and_expired, + remediated_and_rejected + ) + end + end - context 'the submission is NOT for hypertension' do - it_behaves_like '#start_evss_submission' - end + describe 'success_type' do + it 'returns all submissions on which no further action is required' do + expect(Form526Submission.success_type).to contain_exactly( + remediated, + remediated_and_expired, + remediated_and_rejected, + happy_path_success, + happy_lighthouse_path_success, + accepted_backup, + paranoid_success, + success_by_age + ) + end + end - context 'CFI metric logging' do - let!(:in_progress_form) do - ipf = create(:in_progress_526_form, user_uuid: user.uuid) - fd = ipf.form_data - fd = JSON.parse(fd) - fd['rated_disabilities'] = rated_disabilities - ipf.update!(form_data: fd) - ipf - end + describe 'failure_type' do + it 'returns anything not explicitly successful or still in process' do + expect(Form526Submission.failure_type).to contain_exactly( + rejected_backup, + expired, + old_no_longer_remediated, + failed_backup, + exhausted_backup + ) + end - before do - allow(StatsD).to receive(:increment) - allow(Rails.logger).to receive(:info) - end + it 'handles the edge case where a submission succeeds during query building' do + expired.update!(submitted_claim_id: 'abc123') - def expect_submit_log(num_max_rated, num_max_rated_cfi, total_cfi) - expect(Rails.logger).to have_received(:info).with( - 'Max CFI form526 submission', - { id: subject.id, - num_max_rated:, - num_max_rated_cfi:, - total_cfi:, - cfi_checkbox_was_selected: false } - ) + expect(Form526Submission.failure_type).to contain_exactly( + rejected_backup, + old_no_longer_remediated, + failed_backup, + exhausted_backup + ) + end + end end - def expect_max_cfi_logged(disability_claimed, diagnostic_code) - expect(StatsD).to have_received(:increment).with('api.max_cfi.submit', - tags: ["diagnostic_code:#{diagnostic_code}", - "claimed:#{disability_claimed}"]) + shared_examples '#start_evss_submission' do + context 'when it is all claims' do + it 'queues an all claims job' do + expect do + subject.start_evss_submission_job + end.to change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(1) + end + end end - def expect_no_max_cfi_logged(diagnostic_code) - expect(StatsD).not_to have_received(:increment).with('api.max_cfi.submit', - tags: ["diagnostic_code:#{diagnostic_code}", - anything]) - end + describe '#start' do + context 'the submission is for hypertension' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') + end - context 'the submission is for tinnitus' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/only_526_tinnitus.json') + it_behaves_like '#start_evss_submission' end - let(:rated_disabilities) do - [ - { name: 'Tinnitus', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, - rating_percentage:, - maximum_rating_percentage: 10 } - ] + + context 'the submission is NOT for hypertension' do + it_behaves_like '#start_evss_submission' end - let(:rating_percentage) { 0 } - context 'Rated Tinnitus is at maximum' do - let(:rating_percentage) { 10 } + context 'CFI metric logging' do + let!(:in_progress_form) do + ipf = create(:in_progress_526_form, user_uuid: user.uuid) + fd = ipf.form_data + fd = JSON.parse(fd) + fd['rated_disabilities'] = rated_disabilities + ipf.update!(form_data: fd) + ipf + end - it 'logs CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 6260) - expect_submit_log(1, 1, 1) + before do + allow(StatsD).to receive(:increment) + allow(Rails.logger).to receive(:info) end - end - context 'Rated Tinnitus is not at maximum' do - it 'logs CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:false', 'has_max_rated:false']) - expect_no_max_cfi_logged(6260) + def expect_submit_log(num_max_rated, num_max_rated_cfi, total_cfi) + expect(Rails.logger).to have_received(:info).with( + 'Max CFI form526 submission', + { id: subject.id, + num_max_rated:, + num_max_rated_cfi:, + total_cfi:, + cfi_checkbox_was_selected: false } + ) end - end - end - context 'the submission is for hypertension with no max rating percentage' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') - end - let(:rated_disabilities) do - [ - { name: 'Hypertension', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, - rating_percentage: 20 } - ] - end + def expect_max_cfi_logged(disability_claimed, diagnostic_code) + expect(StatsD).to have_received(:increment).with('api.max_cfi.submit', + tags: ["diagnostic_code:#{diagnostic_code}", + "claimed:#{disability_claimed}"]) + end - it 'logs CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:false', 'has_max_rated:false']) - expect_no_max_cfi_logged(7101) - expect_submit_log(0, 0, 1) - end - end + def expect_no_max_cfi_logged(diagnostic_code) + expect(StatsD).not_to have_received(:increment).with('api.max_cfi.submit', + tags: ["diagnostic_code:#{diagnostic_code}", + anything]) + end - context 'the submission for single cfi for a Veteran with multiple rated conditions' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') - end - let(:rated_disabilities) do - [ - { name: 'Tinnitus', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, - rating_percentage: rating_percentage_tinnitus, - maximum_rating_percentage: 10 }, - { name: 'Hypertension', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, - rating_percentage: rating_percentage_hypertension, - maximum_rating_percentage: 60 } - ] - end - let(:rating_percentage_tinnitus) { 0 } - let(:rating_percentage_hypertension) { 0 } + context 'the submission is for tinnitus' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/only_526_tinnitus.json') + end + let(:rated_disabilities) do + [ + { name: 'Tinnitus', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, + rating_percentage:, + maximum_rating_percentage: 10 } + ] + end + let(:rating_percentage) { 0 } + + context 'Rated Tinnitus is at maximum' do + let(:rating_percentage) { 10 } + + it 'logs CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 6260) + expect_submit_log(1, 1, 1) + end + end + + context 'Rated Tinnitus is not at maximum' do + it 'logs CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:false', 'has_max_rated:false']) + expect_no_max_cfi_logged(6260) + end + end + end - context 'Rated Disabilities are not at maximum' do - it 'logs CFI metric upon submission for only hypertension' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:false', 'has_max_rated:false']) - expect_no_max_cfi_logged(6260) - expect_no_max_cfi_logged(7101) - expect_submit_log(0, 0, 1) + context 'the submission is for hypertension with no max rating percentage' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') + end + let(:rated_disabilities) do + [ + { name: 'Hypertension', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, + rating_percentage: 20 } + ] + end + + it 'logs CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:false', 'has_max_rated:false']) + expect_no_max_cfi_logged(7101) + expect_submit_log(0, 0, 1) + end end - end - context 'Rated Disabilities of cfi is at maximum' do - let(:rating_percentage_hypertension) { 60 } + context 'the submission for single cfi for a Veteran with multiple rated conditions' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/only_526_hypertension.json') + end + let(:rated_disabilities) do + [ + { name: 'Tinnitus', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, + rating_percentage: rating_percentage_tinnitus, + maximum_rating_percentage: 10 }, + { name: 'Hypertension', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, + rating_percentage: rating_percentage_hypertension, + maximum_rating_percentage: 60 } + ] + end + let(:rating_percentage_tinnitus) { 0 } + let(:rating_percentage_hypertension) { 0 } + + context 'Rated Disabilities are not at maximum' do + it 'logs CFI metric upon submission for only hypertension' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:false', 'has_max_rated:false']) + expect_no_max_cfi_logged(6260) + expect_no_max_cfi_logged(7101) + expect_submit_log(0, 0, 1) + end + end + + context 'Rated Disabilities of cfi is at maximum' do + let(:rating_percentage_hypertension) { 60 } + + it 'logs CFI metric upon submission for only hypertension' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 7101) + expect_submit_log(1, 1, 1) + expect_no_max_cfi_logged(6260) + end + end + + context 'All Rated Disabilities at maximum' do + let(:rating_percentage_tinnitus) { 10 } + let(:rating_percentage_hypertension) { 60 } + + it 'logs CFI metric upon submission for only hypertension' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 7101) + expect_max_cfi_logged(false, 6260) + expect_submit_log(2, 1, 1) + end + end + end - it 'logs CFI metric upon submission for only hypertension' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 7101) - expect_submit_log(1, 1, 1) - expect_no_max_cfi_logged(6260) + context 'the submission for multiple cfi for a Veteran with multiple rated conditions' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/only_526_two_cfi_with_max_ratings.json') + end + let(:rated_disabilities) do + [ + { name: 'Tinnitus', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, + rating_percentage: rating_percentage_tinnitus, + maximum_rating_percentage: 10 }, + { name: 'Hypertension', + diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, + rating_percentage: rating_percentage_hypertension, + maximum_rating_percentage: 60 } + ] + end + let(:rating_percentage_tinnitus) { 0 } + let(:rating_percentage_hypertension) { 0 } + + context 'Rated Disabilities are not at maximum' do + it 'does not log CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:false', 'has_max_rated:false']) + expect_no_max_cfi_logged(6260) + expect_no_max_cfi_logged(7101) + expect_submit_log(0, 0, 2) + end + end + + context 'Rated Disabilities are at maximum' do + let(:rating_percentage_tinnitus) { 10 } + let(:rating_percentage_hypertension) { 60 } + + it 'logs CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 6260) + expect_max_cfi_logged(true, 7101) + expect_submit_log(2, 2, 2) + end + end + + context 'Only Tinnitus is rated at the maximum' do + let(:rating_percentage_tinnitus) { 10 } + + it 'logs CFI metric upon submission only for tinnitus' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 6260) + expect_no_max_cfi_logged(7101) + expect_submit_log(1, 1, 2) + end + end + + context 'Only Hypertension is rated at the maximum' do + let(:rating_percentage_hypertension) { 60 } + + it 'does not log CFI metric upon submission' do + subject.start + expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', + tags: ['claimed:true', 'has_max_rated:true']) + expect_max_cfi_logged(true, 7101) + expect_no_max_cfi_logged(6260) + expect_submit_log(1, 1, 2) + end + end end end + end - context 'All Rated Disabilities at maximum' do - let(:rating_percentage_tinnitus) { 10 } - let(:rating_percentage_hypertension) { 60 } - - it 'logs CFI metric upon submission for only hypertension' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 7101) - expect_max_cfi_logged(false, 6260) - expect_submit_log(2, 1, 1) + describe '#start_evss_submission_job' do + it_behaves_like '#start_evss_submission' + end + + describe '#submit_with_birls_id_that_hasnt_been_tried_yet!' do + context 'when it is all claims' do + it 'queues an all claims job' do + expect(subject.birls_id).to be_truthy + expect(subject.birls_ids.count).to eq 1 + subject.birls_ids_tried = { subject.birls_id => ['some timestamp'] }.to_json + subject.save! + expect { subject.submit_with_birls_id_that_hasnt_been_tried_yet! }.to( + change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(0) + ) + next_birls_id = "#{subject.birls_id}cat" + subject.add_birls_ids next_birls_id + expect { subject.submit_with_birls_id_that_hasnt_been_tried_yet! }.to( + change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(1) + ) + expect(subject.birls_id).to eq next_birls_id end end end - context 'the submission for multiple cfi for a Veteran with multiple rated conditions' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/only_526_two_cfi_with_max_ratings.json') - end - let(:rated_disabilities) do - [ - { name: 'Tinnitus', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::TINNITUS, - rating_percentage: rating_percentage_tinnitus, - maximum_rating_percentage: 10 }, - { name: 'Hypertension', - diagnostic_code: ClaimFastTracking::DiagnosticCodes::HYPERTENSION, - rating_percentage: rating_percentage_hypertension, - maximum_rating_percentage: 60 } - ] + describe '#form' do + it 'returns the form as a hash' do + expect(subject.form).to eq(JSON.parse(form_json)) end - let(:rating_percentage_tinnitus) { 0 } - let(:rating_percentage_hypertension) { 0 } + end - context 'Rated Disabilities are not at maximum' do - it 'does not log CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:false', 'has_max_rated:false']) - expect_no_max_cfi_logged(6260) - expect_no_max_cfi_logged(7101) - expect_submit_log(0, 0, 2) + describe '#form_to_json' do + context 'with form 526' do + it 'returns the sub form as json' do + expect(subject.form_to_json(Form526Submission::FORM_526)).to eq(JSON.parse(form_json)['form526'].to_json) end end - context 'Rated Disabilities are at maximum' do - let(:rating_percentage_tinnitus) { 10 } - let(:rating_percentage_hypertension) { 60 } + context 'with form 4142' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_4142.json') + end - it 'logs CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 6260) - expect_max_cfi_logged(true, 7101) - expect_submit_log(2, 2, 2) + it 'returns the sub form as json' do + expect(subject.form_to_json(Form526Submission::FORM_4142)).to eq(JSON.parse(form_json)['form4142'].to_json) end end - context 'Only Tinnitus is rated at the maximum' do - let(:rating_percentage_tinnitus) { 10 } + context 'with form 0781' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_0781.json') + end - it 'logs CFI metric upon submission only for tinnitus' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 6260) - expect_no_max_cfi_logged(7101) - expect_submit_log(1, 1, 2) + it 'returns the sub form as json' do + expect(subject.form_to_json(Form526Submission::FORM_0781)).to eq(JSON.parse(form_json)['form0781'].to_json) end end - context 'Only Hypertension is rated at the maximum' do - let(:rating_percentage_hypertension) { 60 } + context 'with form 8940' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_8940.json') + end - it 'does not log CFI metric upon submission' do - subject.start - expect(StatsD).to have_received(:increment).with('api.max_cfi.on_submit', - tags: ['claimed:true', 'has_max_rated:true']) - expect_max_cfi_logged(true, 7101) - expect_no_max_cfi_logged(6260) - expect_submit_log(1, 1, 2) + it 'returns the sub form as json' do + expect(subject.form_to_json(Form526Submission::FORM_8940)).to eq(JSON.parse(form_json)['form8940'].to_json) end end end - end - end - - describe '#start_evss_submission_job' do - it_behaves_like '#start_evss_submission' - end - describe '#submit_with_birls_id_that_hasnt_been_tried_yet!' do - context 'when it is all claims' do - it 'queues an all claims job' do - expect(subject.birls_id).to be_truthy - expect(subject.birls_ids.count).to eq 1 - subject.birls_ids_tried = { subject.birls_id => ['some timestamp'] }.to_json - subject.save! - expect { subject.submit_with_birls_id_that_hasnt_been_tried_yet! }.to( - change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(0) - ) - next_birls_id = "#{subject.birls_id}cat" - subject.add_birls_ids next_birls_id - expect { subject.submit_with_birls_id_that_hasnt_been_tried_yet! }.to( - change(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim.jobs, :size).by(1) - ) - expect(subject.birls_id).to eq next_birls_id + describe '#auth_headers' do + it 'returns the parsed auth headers' do + expect(subject.auth_headers).to eq(auth_headers) + end end - end - end - - describe '#form' do - it 'returns the form as a hash' do - expect(subject.form).to eq(JSON.parse(form_json)) - end - end - describe '#form_to_json' do - context 'with form 526' do - it 'returns the sub form as json' do - expect(subject.form_to_json(Form526Submission::FORM_526)).to eq(JSON.parse(form_json)['form526'].to_json) - end - end + describe '#add_birls_ids' do + subject do + headers = JSON.parse auth_headers.to_json + Form526Submission.new( + user_uuid: user.uuid, + saved_claim_id: saved_claim.id, + auth_headers_json: headers.to_json, + form_json:, + birls_ids_tried: birls_ids_tried.to_json + ) + end - context 'with form 4142' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_4142.json') - end + context 'birls_ids_tried nil' do + let(:birls_ids_tried) { nil } - it 'returns the sub form as json' do - expect(subject.form_to_json(Form526Submission::FORM_4142)).to eq(JSON.parse(form_json)['form4142'].to_json) - end - end + it 'has no default' do + expect(subject.birls_ids_tried).to eq 'null' + end - context 'with form 0781' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_0781.json') - end + context 'using nil as an id' do + it 'results in an empty hash' do + subject.add_birls_ids nil + expect(JSON.parse(subject.birls_ids_tried)).to be_a Hash + end + end - it 'returns the sub form as json' do - expect(subject.form_to_json(Form526Submission::FORM_0781)).to eq(JSON.parse(form_json)['form0781'].to_json) - end - end + context 'single id' do + it 'initializes with an empty array' do + subject.add_birls_ids 'a' + expect(subject.birls_ids_tried_hash).to eq 'a' => [] + end + end - context 'with form 8940' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_8940.json') - end + context 'an array of ids' do + it 'initializes with an empty arrays' do + subject.add_birls_ids(%w[a b c]) + expect(subject.birls_ids_tried_hash).to eq 'a' => [], 'b' => [], 'c' => [] + end + end + end - it 'returns the sub form as json' do - expect(subject.form_to_json(Form526Submission::FORM_8940)).to eq(JSON.parse(form_json)['form8940'].to_json) - end - end - end + context 'birls_ids_tried already has values' do + let(:birls_ids_tried) { { 'a' => ['2021-02-01T14:28:33Z'] } } - describe '#auth_headers' do - it 'returns the parsed auth headers' do - expect(subject.auth_headers).to eq(auth_headers) - end - end + context 'using nil as an id' do + it 'results in an empty hash' do + subject.add_birls_ids nil + expect(subject.birls_ids_tried_hash).to eq birls_ids_tried + end + end - describe '#add_birls_ids' do - subject do - headers = JSON.parse auth_headers.to_json - Form526Submission.new( - user_uuid: user.uuid, - saved_claim_id: saved_claim.id, - auth_headers_json: headers.to_json, - form_json:, - birls_ids_tried: birls_ids_tried.to_json - ) - end + context 'single id that is already present' do + it 'does nothing' do + subject.add_birls_ids 'a' + expect(subject.birls_ids_tried_hash).to eq birls_ids_tried + end + end - context 'birls_ids_tried nil' do - let(:birls_ids_tried) { nil } + context 'single id that is not already present' do + it 'does nothing' do + subject.add_birls_ids 'b' + expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => []) + end + end - it 'has no default' do - expect(subject.birls_ids_tried).to eq 'null' - end + context 'an array of ids' do + it 'initializes with an empty arrays, for ids that area not already present' do + subject.add_birls_ids(['a', :b, :c]) + expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => [], 'c' => []) + end + end - context 'using nil as an id' do - it 'results in an empty hash' do - subject.add_birls_ids nil - expect(JSON.parse(subject.birls_ids_tried)).to be_a Hash + context 'an array of ids persisted' do + it 'persists' do + subject.add_birls_ids(['a', :b, :c]) + subject.save + subject.reload + expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => [], 'c' => []) + end + end end end - context 'single id' do - it 'initializes with an empty array' do - subject.add_birls_ids 'a' - expect(subject.birls_ids_tried_hash).to eq 'a' => [] + describe '#birls_ids' do + subject do + headers = JSON.parse auth_headers.to_json + headers['va_eauth_birlsfilenumber'] = birls_id + Form526Submission.new( + user_uuid: user.uuid, + saved_claim_id: saved_claim.id, + auth_headers_json: headers.to_json, + form_json:, + birls_ids_tried: birls_ids_tried.to_json + ) end - end - context 'an array of ids' do - it 'initializes with an empty arrays' do - subject.add_birls_ids(%w[a b c]) - expect(subject.birls_ids_tried_hash).to eq 'a' => [], 'b' => [], 'c' => [] - end - end - end + let(:birls_id) { 'a' } + let(:birls_ids_tried) { { b: [], c: ['2021-02-01T14:28:33Z'] } } - context 'birls_ids_tried already has values' do - let(:birls_ids_tried) { { 'a' => ['2021-02-01T14:28:33Z'] } } + context 'birls_ids_tried present and auth_headers present' do + it 'lists all birls ids' do + expect(subject.birls_ids).to contain_exactly 'c', 'b', 'a' + end - context 'using nil as an id' do - it 'results in an empty hash' do - subject.add_birls_ids nil - expect(subject.birls_ids_tried_hash).to eq birls_ids_tried + it 'persists' do + subject.save + subject.reload + expect(subject.birls_ids).to contain_exactly 'b', 'c', 'a' + end end - end - context 'single id that is already present' do - it 'does nothing' do - subject.add_birls_ids 'a' - expect(subject.birls_ids_tried_hash).to eq birls_ids_tried - end - end + context 'only birls_ids_tried present' do + subject do + Form526Submission.new( + user_uuid: user.uuid, + saved_claim_id: saved_claim.id, + form_json:, + birls_ids_tried: birls_ids_tried.to_json + ) + end - context 'single id that is not already present' do - it 'does nothing' do - subject.add_birls_ids 'b' - expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => []) + it 'lists birls ids from birls_ids_tried only' do + expect(subject.birls_ids).to contain_exactly 'b', 'c' + end end - end - context 'an array of ids' do - it 'initializes with an empty arrays, for ids that area not already present' do - subject.add_birls_ids(['a', :b, :c]) - expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => [], 'c' => []) - end - end + context 'only auth_headers present' do + let(:birls_ids_tried) { nil } - context 'an array of ids persisted' do - it 'persists' do - subject.add_birls_ids(['a', :b, :c]) - subject.save - subject.reload - expect(subject.birls_ids_tried_hash).to eq birls_ids_tried.merge('b' => [], 'c' => []) + it 'lists birls ids from auth_headers only' do + expect(subject.birls_ids).to contain_exactly 'a' + end end end - end - end - - describe '#birls_ids' do - subject do - headers = JSON.parse auth_headers.to_json - headers['va_eauth_birlsfilenumber'] = birls_id - Form526Submission.new( - user_uuid: user.uuid, - saved_claim_id: saved_claim.id, - auth_headers_json: headers.to_json, - form_json:, - birls_ids_tried: birls_ids_tried.to_json - ) - end - - let(:birls_id) { 'a' } - let(:birls_ids_tried) { { b: [], c: ['2021-02-01T14:28:33Z'] } } - context 'birls_ids_tried present and auth_headers present' do - it 'lists all birls ids' do - expect(subject.birls_ids).to contain_exactly 'c', 'b', 'a' - end + describe '#mark_birls_id_as_tried' do + subject do + headers = JSON.parse auth_headers.to_json + headers['va_eauth_birlsfilenumber'] = birls_id + Form526Submission.new( + user_uuid: user.uuid, + saved_claim_id: saved_claim.id, + auth_headers_json: headers.to_json, + form_json:, + birls_ids_tried: birls_ids_tried.to_json + ) + end - it 'persists' do - subject.save - subject.reload - expect(subject.birls_ids).to contain_exactly 'b', 'c', 'a' - end - end + let(:birls_id) { 'a' } - context 'only birls_ids_tried present' do - subject do - Form526Submission.new( - user_uuid: user.uuid, - saved_claim_id: saved_claim.id, - form_json:, - birls_ids_tried: birls_ids_tried.to_json - ) - end + context 'nil birls_ids_tried' do + let(:birls_ids_tried) { nil } - it 'lists birls ids from birls_ids_tried only' do - expect(subject.birls_ids).to contain_exactly 'b', 'c' - end - end + it 'adds the current birls id to birls_ids_tried' do + expect(JSON.parse(subject.birls_ids_tried)).to eq birls_ids_tried + subject.mark_birls_id_as_tried + expect(subject.birls_ids_tried_hash.keys).to contain_exactly 'a' + subject.save + subject.reload + expect(subject.birls_ids_tried_hash.keys).to contain_exactly 'a' + end + end - context 'only auth_headers present' do - let(:birls_ids_tried) { nil } + context 'previous attempts' do + let(:birls_ids_tried) { { 'b' => ['2021-02-01T14:28:33Z'] } } - it 'lists birls ids from auth_headers only' do - expect(subject.birls_ids).to contain_exactly 'a' + it 'adds the current BIRLS ID to birls_ids_tried array (turns birls_ids_tried into an array if nil)' do + expect(JSON.parse(subject.birls_ids_tried)).to eq birls_ids_tried + subject.mark_birls_id_as_tried + expect(subject.birls_ids_tried_hash.keys).to contain_exactly(birls_id, *birls_ids_tried.keys) + subject.save + subject.reload + expect(subject.birls_ids_tried_hash.keys).to contain_exactly(birls_id, *birls_ids_tried.keys) + end + end end - end - end - - describe '#mark_birls_id_as_tried' do - subject do - headers = JSON.parse auth_headers.to_json - headers['va_eauth_birlsfilenumber'] = birls_id - Form526Submission.new( - user_uuid: user.uuid, - saved_claim_id: saved_claim.id, - auth_headers_json: headers.to_json, - form_json:, - birls_ids_tried: birls_ids_tried.to_json - ) - end - - let(:birls_id) { 'a' } - context 'nil birls_ids_tried' do - let(:birls_ids_tried) { nil } - - it 'adds the current birls id to birls_ids_tried' do - expect(JSON.parse(subject.birls_ids_tried)).to eq birls_ids_tried - subject.mark_birls_id_as_tried - expect(subject.birls_ids_tried_hash.keys).to contain_exactly 'a' - subject.save - subject.reload - expect(subject.birls_ids_tried_hash.keys).to contain_exactly 'a' - end - end + describe '#birls_ids_that_havent_been_tried_yet' do + subject do + headers = JSON.parse auth_headers.to_json + headers['va_eauth_birlsfilenumber'] = birls_id + Form526Submission.new( + user_uuid: user.uuid, + saved_claim_id: saved_claim.id, + auth_headers_json: headers.to_json, + form_json:, + birls_ids_tried: birls_ids_tried.to_json + ) + end - context 'previous attempts' do - let(:birls_ids_tried) { { 'b' => ['2021-02-01T14:28:33Z'] } } + let(:birls_id) { 'a' } + let(:birls_ids_tried) { { b: [], c: ['2021-02-01T14:28:33Z'], d: nil } } - it 'adds the current BIRLS ID to birls_ids_tried array (turns birls_ids_tried into an array if nil)' do - expect(JSON.parse(subject.birls_ids_tried)).to eq birls_ids_tried - subject.mark_birls_id_as_tried - expect(subject.birls_ids_tried_hash.keys).to contain_exactly(birls_id, *birls_ids_tried.keys) - subject.save - subject.reload - expect(subject.birls_ids_tried_hash.keys).to contain_exactly(birls_id, *birls_ids_tried.keys) + it 'does not include birls ids that have already been tried' do + expect(subject.birls_ids_that_havent_been_tried_yet).to contain_exactly('a', 'b', 'd') + end end - end - end - describe '#birls_ids_that_havent_been_tried_yet' do - subject do - headers = JSON.parse auth_headers.to_json - headers['va_eauth_birlsfilenumber'] = birls_id - Form526Submission.new( - user_uuid: user.uuid, - saved_claim_id: saved_claim.id, - auth_headers_json: headers.to_json, - form_json:, - birls_ids_tried: birls_ids_tried.to_json - ) - end - - let(:birls_id) { 'a' } - let(:birls_ids_tried) { { b: [], c: ['2021-02-01T14:28:33Z'], d: nil } } - - it 'does not include birls ids that have already been tried' do - expect(subject.birls_ids_that_havent_been_tried_yet).to contain_exactly('a', 'b', 'd') - end - end + describe '#birls_id!' do + it 'returns the BIRLS ID' do + expect(subject.birls_id!).to eq(auth_headers[described_class::BIRLS_KEY]) + end - describe '#birls_id!' do - it 'returns the BIRLS ID' do - expect(subject.birls_id!).to eq(auth_headers[described_class::BIRLS_KEY]) - end + context 'auth_headers is nil' do + it 'throws an exception' do + subject.auth_headers_json = nil + expect { subject.birls_id! }.to raise_error TypeError + end + end - context 'auth_headers is nil' do - it 'throws an exception' do - subject.auth_headers_json = nil - expect { subject.birls_id! }.to raise_error TypeError + context 'auth_headers is unparseable' do + it 'throws an exception' do + subject.auth_headers_json = 'hi!' + expect { subject.birls_id! }.to raise_error JSON::ParserError + end + end end - end - context 'auth_headers is unparseable' do - it 'throws an exception' do - subject.auth_headers_json = 'hi!' - expect { subject.birls_id! }.to raise_error JSON::ParserError - end - end - end + describe '#birls_id' do + it 'returns the BIRLS ID' do + expect(subject.birls_id).to eq(auth_headers[described_class::BIRLS_KEY]) + end - describe '#birls_id' do - it 'returns the BIRLS ID' do - expect(subject.birls_id).to eq(auth_headers[described_class::BIRLS_KEY]) - end + context 'auth_headers is nil' do + it 'returns nil' do + subject.auth_headers_json = nil + expect(subject.birls_id).to be_nil + end + end - context 'auth_headers is nil' do - it 'returns nil' do - subject.auth_headers_json = nil - expect(subject.birls_id).to be_nil + context 'auth_headers is unparseable' do + it 'throws an exception' do + subject.auth_headers_json = 'hi!' + expect { subject.birls_id }.to raise_error JSON::ParserError + end + end end - end - context 'auth_headers is unparseable' do - it 'throws an exception' do - subject.auth_headers_json = 'hi!' - expect { subject.birls_id }.to raise_error JSON::ParserError - end - end - end + describe '#birls_id=' do + let(:birls_id) { 1 } - describe '#birls_id=' do - let(:birls_id) { 1 } + it 'sets the BIRLS ID' do + subject.birls_id = birls_id + expect(subject.birls_id).to eq(birls_id) + end - it 'sets the BIRLS ID' do - subject.birls_id = birls_id - expect(subject.birls_id).to eq(birls_id) - end + context 'auth_headers is nil' do + it 'throws an exception' do + subject.auth_headers_json = nil + expect { subject.birls_id = birls_id }.to raise_error TypeError + end + end - context 'auth_headers is nil' do - it 'throws an exception' do - subject.auth_headers_json = nil - expect { subject.birls_id = birls_id }.to raise_error TypeError + context 'auth_headers is unparseable' do + it 'throws an exception' do + subject.auth_headers_json = 'hi!' + expect { subject.birls_id = birls_id }.to raise_error JSON::ParserError + end + end end - end - context 'auth_headers is unparseable' do - it 'throws an exception' do - subject.auth_headers_json = 'hi!' - expect { subject.birls_id = birls_id }.to raise_error JSON::ParserError - end - end - end + describe '#perform_ancillary_jobs_handler' do + let(:status) { OpenStruct.new(parent_bid: SecureRandom.hex(8)) } - describe '#perform_ancillary_jobs_handler' do - let(:status) { OpenStruct.new(parent_bid: SecureRandom.hex(8)) } + context 'with an ancillary job' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') + end - context 'with an ancillary job' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') - end + it 'queues 3 jobs' do + subject.form526_job_statuses << + Form526JobStatus.new(job_class: 'SubmitForm526AllClaim', status: 'success', job_id: 0) + expect do + subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) + end.to change(EVSS::DisabilityCompensationForm::SubmitUploads.jobs, :size).by(3) + end - it 'queues 3 jobs' do - subject.form526_job_statuses << - Form526JobStatus.new(job_class: 'SubmitForm526AllClaim', status: 'success', job_id: 0) - expect do - subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) - end.to change(EVSS::DisabilityCompensationForm::SubmitUploads.jobs, :size).by(3) - end + it 'warns when there are multiple successful submit526 jobs' do + 2.times do |index| + subject.form526_job_statuses << Form526JobStatus.new( + job_class: 'SubmitForm526AllClaim', + status: Form526JobStatus::STATUS[:success], + job_id: index + ) + end + expect(Form526JobStatus.all.count).to eq 2 + expect_any_instance_of(Form526Submission).to receive(:log_message_to_sentry).with( + 'There are multiple successful SubmitForm526 job statuses', + :warn, + { form_526_submission_id: subject.id } + ) + subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) + end - it 'warns when there are multiple successful submit526 jobs' do - 2.times do |index| - subject.form526_job_statuses << Form526JobStatus.new( - job_class: 'SubmitForm526AllClaim', - status: Form526JobStatus::STATUS[:success], - job_id: index - ) + it "warns when there's a successful submit526 job, but it's not the most recent submit526 job" do + %i[success retryable_error].each_with_index do |status, index| + subject.form526_job_statuses << Form526JobStatus.new( + job_class: 'SubmitForm526AllClaim', + status: Form526JobStatus::STATUS[status], + job_id: index, + updated_at: Time.zone.now + index.days + ) + end + expect(Form526JobStatus.all.count).to eq 2 + expect_any_instance_of(Form526Submission).to receive(:log_message_to_sentry).with( + "There is a successful SubmitForm526 job, but it's not the most recent SubmitForm526 job", + :warn, + { form_526_submission_id: subject.id } + ) + subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) + end end - expect(Form526JobStatus.all.count).to eq 2 - expect_any_instance_of(Form526Submission).to receive(:log_message_to_sentry).with( - 'There are multiple successful SubmitForm526 job statuses', - :warn, - { form_526_submission_id: subject.id } - ) - subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) end - it "warns when there's a successful submit526 job, but it's not the most recent submit526 job" do - %i[success retryable_error].each_with_index do |status, index| - subject.form526_job_statuses << Form526JobStatus.new( - job_class: 'SubmitForm526AllClaim', - status: Form526JobStatus::STATUS[status], - job_id: index, - updated_at: Time.zone.now + index.days - ) - end - expect(Form526JobStatus.all.count).to eq 2 - expect_any_instance_of(Form526Submission).to receive(:log_message_to_sentry).with( - "There is a successful SubmitForm526 job, but it's not the most recent SubmitForm526 job", - :warn, - { form_526_submission_id: subject.id } - ) - subject.perform_ancillary_jobs_handler(status, 'submission_id' => subject.id) - end - end - end + describe '#perform_ancillary_jobs' do + let(:first_name) { 'firstname' } - describe '#perform_ancillary_jobs' do - let(:first_name) { 'firstname' } + context 'with (3) uploads' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') + end - context 'with (3) uploads' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') - end + it 'queues 3 upload jobs' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(EVSS::DisabilityCompensationForm::SubmitUploads.jobs, :size).by(3) + end + end - it 'queues 3 upload jobs' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(EVSS::DisabilityCompensationForm::SubmitUploads.jobs, :size).by(3) - end - end + context 'with flashes' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') + end - context 'with flashes' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') - end + context 'when feature enabled' do + before { Flipper.enable(:disability_compensation_flashes) } + + it 'queues flashes job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(BGS::FlashUpdater.jobs, :size).by(1) + end + end - context 'when feature enabled' do - before { Flipper.enable(:disability_compensation_flashes) } + context 'when feature disabled' do + before { Flipper.disable(:disability_compensation_flashes) } - it 'queues flashes job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(BGS::FlashUpdater.jobs, :size).by(1) + it 'queues flashes job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(BGS::FlashUpdater.jobs, :size).by(0) + end + end end - end - context 'when feature disabled' do - before { Flipper.disable(:disability_compensation_flashes) } + context 'BDD' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/526_bdd.json') + end - it 'queues flashes job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(BGS::FlashUpdater.jobs, :size).by(0) + it 'queues 1 UploadBddInstructions job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(EVSS::DisabilityCompensationForm::UploadBddInstructions.jobs, :size).by(1) + end end - end - end - context 'BDD' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/526_bdd.json') - end + context 'with form 4142' do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) + allow(Flipper).to receive(:enabled?).with(:disability_compensation_production_tester).and_return(false) + allow(Flipper).to receive(:enabled?).with(:disability_526_toxic_exposure_document_upload_polling, + anything).and_return(false) + allow(Flipper).to receive(:enabled?).with(:disability_compensation_production_tester, + anything).and_return(false) + end - it 'queues 1 UploadBddInstructions job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(EVSS::DisabilityCompensationForm::UploadBddInstructions.jobs, :size).by(1) - end - end + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_4142.json') + end - context 'with form 4142' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_4142.json') - end + it 'queues a 4142 job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(CentralMail::SubmitForm4142Job.jobs, :size).by(1) + end + end - it 'queues a 4142 job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(CentralMail::SubmitForm4142Job.jobs, :size).by(1) - end - end + context 'with form 0781' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_0781.json') + end - context 'with form 0781' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_0781.json') - end + it 'queues a 0781 job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(EVSS::DisabilityCompensationForm::SubmitForm0781.jobs, :size).by(1) + end + end - it 'queues a 0781 job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(EVSS::DisabilityCompensationForm::SubmitForm0781.jobs, :size).by(1) - end - end + context 'with form 8940' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_8940.json') + end - context 'with form 8940' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_8940.json') - end + it 'queues a 8940 job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(EVSS::DisabilityCompensationForm::SubmitForm8940.jobs, :size).by(1) + end + end - it 'queues a 8940 job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(EVSS::DisabilityCompensationForm::SubmitForm8940.jobs, :size).by(1) - end - end + context 'with Lighthouse document upload polling' do + let(:form_json) do + File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') + end - context 'with Lighthouse document upload polling' do - let(:form_json) do - File.read('spec/support/disability_compensation_form/submissions/with_uploads.json') - end + context 'when feature enabled' do + before { Flipper.enable(:disability_526_toxic_exposure_document_upload_polling) } + + it 'queues polling job' do + expect do + form = subject.saved_claim.parsed_form + form['startedFormVersion'] = '2022' + subject.update(submitted_claim_id: 1) + subject.saved_claim.update(form: form.to_json) + subject.perform_ancillary_jobs(first_name) + end.to change(Lighthouse::PollForm526Pdf.jobs, :size).by(1) + end + end - context 'when feature enabled' do - before { Flipper.enable(:disability_526_toxic_exposure_document_upload_polling) } + context 'when feature disabled' do + before { Flipper.disable(:disability_526_toxic_exposure_document_upload_polling) } - it 'queues polling job' do - expect do - form = subject.saved_claim.parsed_form - form['startedFormVersion'] = '2022' - subject.update(submitted_claim_id: 1) - subject.saved_claim.update(form: form.to_json) - subject.perform_ancillary_jobs(first_name) - end.to change(Lighthouse::PollForm526Pdf.jobs, :size).by(1) + it 'does not queue polling job' do + expect do + subject.perform_ancillary_jobs(first_name) + end.to change(Lighthouse::PollForm526Pdf.jobs, :size).by(0) + end + end end end - context 'when feature disabled' do - before { Flipper.disable(:disability_526_toxic_exposure_document_upload_polling) } - - it 'does not queue polling job' do - expect do - subject.perform_ancillary_jobs(first_name) - end.to change(Lighthouse::PollForm526Pdf.jobs, :size).by(0) + describe '#get_first_name' do + [ + { + input: 'Joe', + expected: 'JOE' + }, + { + input: 'JOE', + expected: 'JOE' + }, { + input: 'joe mark', + expected: 'JOE MARK' + } + ].each do |test_param| + it 'gets correct first name' do + allow(User).to receive(:find).with(anything).and_return(user) + allow_any_instance_of(User).to receive(:first_name).and_return(test_param[:input]) + + expect(subject.get_first_name).to eql(test_param[:expected]) + end end - end - end - end - describe '#get_first_name' do - [ - { - input: 'Joe', - expected: 'JOE' - }, - { - input: 'JOE', - expected: 'JOE' - }, { - input: 'joe mark', - expected: 'JOE MARK' - } - ].each do |test_param| - it 'gets correct first name' do - allow(User).to receive(:find).with(anything).and_return(user) - allow_any_instance_of(User).to receive(:first_name).and_return(test_param[:input]) - - expect(subject.get_first_name).to eql(test_param[:expected]) - end - end + context 'when the first name is NOT populated on the User' do + before do + # Ensure `subject` is called before stubbing `first_name` so that the auth headers are populated correctly + subject + user_with_nil_first_name = User.create(user) + allow(user_with_nil_first_name).to receive(:first_name).and_return nil + allow(User).to receive(:find).with(subject.user_uuid).and_return user_with_nil_first_name + end - context 'when the first name is NOT populated on the User' do - before do - # Ensure `subject` is called before stubbing `first_name` so that the auth headers are populated correctly - subject - user_with_nil_first_name = User.create(user) - allow(user_with_nil_first_name).to receive(:first_name).and_return nil - allow(User).to receive(:find).with(subject.user_uuid).and_return user_with_nil_first_name - end + context 'when name attributes exist in the auth headers' do + it 'returns the first name of the user from the auth headers' do + expect(subject.get_first_name).to eql('BEYONCE') + end + end + + context 'when name attributes do NOT exist in the auth headers' do + subject { build(:form526_submission, :with_empty_auth_headers) } - context 'when name attributes exist in the auth headers' do - it 'returns the first name of the user from the auth headers' do - expect(subject.get_first_name).to eql('BEYONCE') + it 'returns nil' do + expect(subject.get_first_name).to be nil + end + end end - end - context 'when name attributes do NOT exist in the auth headers' do - subject { build(:form526_submission, :with_empty_auth_headers) } + context 'when the User is NOT found' do + before { allow(User).to receive(:find).and_return nil } - it 'returns nil' do - expect(subject.get_first_name).to be nil + it 'returns the first name of the user from the auth headers' do + expect(subject.get_first_name).to eql('BEYONCE') + end end end - end - context 'when the User is NOT found' do - before { allow(User).to receive(:find).and_return nil } + describe '#full_name' do + let(:full_name_hash) do + { + first: 'Beyonce', + middle: nil, + last: 'Knowles', + suffix: user.normalized_suffix + } + end - it 'returns the first name of the user from the auth headers' do - expect(subject.get_first_name).to eql('BEYONCE') - end - end - end + context 'when the full name exists on the User' do + it 'returns the full name of the user' do + expect(subject.full_name).to eql(full_name_hash) + end + end - describe '#full_name' do - let(:full_name_hash) do - { - first: 'Beyonce', - middle: nil, - last: 'Knowles', - suffix: user.normalized_suffix - } - end + context 'when the full name is NOT populated on the User but name attributes exist in the auth_headers' do + let(:nil_full_name_hash) do + { + first: nil, + middle: nil, + last: nil, + suffix: nil + } + end - context 'when the full name exists on the User' do - it 'returns the full name of the user' do - expect(subject.full_name).to eql(full_name_hash) - end - end + before do + allow_any_instance_of(User).to receive(:full_name_normalized).and_return nil_full_name_hash + end - context 'when the full name is NOT populated on the User but name attributes exist in the auth_headers' do - let(:nil_full_name_hash) do - { - first: nil, - middle: nil, - last: nil, - suffix: nil - } - end + context 'when name attributes exist in the auth headers' do + it 'returns the first and last name of the user from the auth headers' do + expect(subject.full_name).to eql(full_name_hash.merge(middle: nil, suffix: nil)) + end + end - before do - allow_any_instance_of(User).to receive(:full_name_normalized).and_return nil_full_name_hash - end + context 'when name attributes do NOT exist in the auth headers' do + subject { build(:form526_submission, :with_empty_auth_headers) } - context 'when name attributes exist in the auth headers' do - it 'returns the first and last name of the user from the auth headers' do - expect(subject.full_name).to eql(full_name_hash.merge(middle: nil, suffix: nil)) + it 'returns the hash with all nil values' do + expect(subject.full_name).to eql nil_full_name_hash + end + end end - end - context 'when name attributes do NOT exist in the auth headers' do - subject { build(:form526_submission, :with_empty_auth_headers) } + context 'when the User is NOT found' do + before { allow(User).to receive(:find).and_return nil } - it 'returns the hash with all nil values' do - expect(subject.full_name).to eql nil_full_name_hash + it 'returns the first and last name of the user from the auth headers' do + expect(subject.full_name).to eql(full_name_hash.merge(middle: nil, suffix: nil)) + end end end - end - - context 'when the User is NOT found' do - before { allow(User).to receive(:find).and_return nil } - it 'returns the first and last name of the user from the auth headers' do - expect(subject.full_name).to eql(full_name_hash.merge(middle: nil, suffix: nil)) - end - end - end + describe '#workflow_complete_handler' do + describe 'success' do + let(:options) do + { + 'submission_id' => subject.id, + 'first_name' => 'firstname' + } + end - describe '#workflow_complete_handler' do - describe 'success' do - let(:options) do - { - 'submission_id' => subject.id, - 'first_name' => 'firstname' - } - end + context 'with a single successful job' do + subject { create(:form526_submission, :with_one_succesful_job) } - context 'with a single successful job' do - subject { create(:form526_submission, :with_one_succesful_job) } + it 'sets the submission.complete to true' do + expect(subject.workflow_complete).to be_falsey + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + subject.reload + expect(subject.workflow_complete).to be_truthy + end + end - it 'sets the submission.complete to true' do - expect(subject.workflow_complete).to be_falsey - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - subject.reload - expect(subject.workflow_complete).to be_truthy - end - end + context 'with multiple successful jobs' do + subject { create(:form526_submission, :with_multiple_succesful_jobs) } - context 'with multiple successful jobs' do - subject { create(:form526_submission, :with_multiple_succesful_jobs) } + it 'sets the submission.complete to true' do + expect(subject.workflow_complete).to be_falsey + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + subject.reload + expect(subject.workflow_complete).to be_truthy + end + end - it 'sets the submission.complete to true' do - expect(subject.workflow_complete).to be_falsey - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - subject.reload - expect(subject.workflow_complete).to be_truthy - end - end + context 'with multiple successful jobs and email and submitted time in PM' do + subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } - context 'with multiple successful jobs and email and submitted time in PM' do - subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } + before { Timecop.freeze(Time.zone.parse('2012-07-20 14:15:00 UTC')) } - before { Timecop.freeze(Time.zone.parse('2012-07-20 14:15:00 UTC')) } + after { Timecop.return } - after { Timecop.return } + it 'calls confirmation email job with correct personalization' do + allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| + expect(args[0]['first_name']).to eql('firstname') + expect(args[0]['submitted_claim_id']).to be(123_654_879) + expect(args[0]['email']).to eql('test@email.com') + expect(args[0]['date_submitted']).to eql('July 20, 2012 2:15 p.m. UTC') + end - it 'calls confirmation email job with correct personalization' do - allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| - expect(args[0]['first_name']).to eql('firstname') - expect(args[0]['submitted_claim_id']).to be(123_654_879) - expect(args[0]['email']).to eql('test@email.com') - expect(args[0]['date_submitted']).to eql('July 20, 2012 2:15 p.m. UTC') + subject.workflow_complete_handler(nil, options) + end end - subject.workflow_complete_handler(nil, options) - end - end + context 'with multiple successful jobs and email and submitted time in PM with two digit hour' do + subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } - context 'with multiple successful jobs and email and submitted time in PM with two digit hour' do - subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } + before { Timecop.freeze(Time.zone.parse('2012-07-20 11:12:00 UTC')) } - before { Timecop.freeze(Time.zone.parse('2012-07-20 11:12:00 UTC')) } + after { Timecop.return } - after { Timecop.return } + it 'calls confirmation email job with correct personalization' do + allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| + expect(args[0]['first_name']).to eql('firstname') + expect(args[0]['submitted_claim_id']).to be(123_654_879) + expect(args[0]['email']).to eql('test@email.com') + expect(args[0]['date_submitted']).to eql('July 20, 2012 11:12 a.m. UTC') + end - it 'calls confirmation email job with correct personalization' do - allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| - expect(args[0]['first_name']).to eql('firstname') - expect(args[0]['submitted_claim_id']).to be(123_654_879) - expect(args[0]['email']).to eql('test@email.com') - expect(args[0]['date_submitted']).to eql('July 20, 2012 11:12 a.m. UTC') + subject.workflow_complete_handler(nil, options) + end end - subject.workflow_complete_handler(nil, options) - end - end + context 'with multiple successful jobs and email and submitted time in morning' do + subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } - context 'with multiple successful jobs and email and submitted time in morning' do - subject { create(:form526_submission, :with_multiple_succesful_jobs, submitted_claim_id: 123_654_879) } + before { Timecop.freeze(Time.zone.parse('2012-07-20 8:07:00 UTC')) } - before { Timecop.freeze(Time.zone.parse('2012-07-20 8:07:00 UTC')) } + after { Timecop.return } - after { Timecop.return } + it 'calls confirmation email job with correct personalization' do + allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| + expect(args[0]['first_name']).to eql('firstname') + expect(args[0]['submitted_claim_id']).to be(123_654_879) + expect(args[0]['email']).to eql('test@email.com') + expect(args[0]['date_submitted']).to eql('July 20, 2012 8:07 a.m. UTC') + end - it 'calls confirmation email job with correct personalization' do - allow(Form526ConfirmationEmailJob).to receive(:perform_async) do |*args| - expect(args[0]['first_name']).to eql('firstname') - expect(args[0]['submitted_claim_id']).to be(123_654_879) - expect(args[0]['email']).to eql('test@email.com') - expect(args[0]['date_submitted']).to eql('July 20, 2012 8:07 a.m. UTC') + subject.workflow_complete_handler(nil, options) + end end - subject.workflow_complete_handler(nil, options) + context 'with submission confirmation email when successful job statuses' do + subject { create(:form526_submission, :with_multiple_succesful_jobs) } + + it 'does not trigger job when disability_526_call_received_email_from_polling enabled' do + Flipper.enable(:disability_526_call_received_email_from_polling) + expect do + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + end.to change(Form526ConfirmationEmailJob.jobs, :size).by(0) + end + + it 'returns one job triggered when disability_526_call_received_email_from_polling disabled' do + Flipper.disable(:disability_526_call_received_email_from_polling) + expect do + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + end.to change(Form526ConfirmationEmailJob.jobs, :size).by(1) + end + end end - end - context 'with submission confirmation email when successful job statuses' do - subject { create(:form526_submission, :with_multiple_succesful_jobs) } + describe 'failure' do + context 'with mixed result jobs' do + subject { create(:form526_submission, :with_mixed_status) } - it 'does not trigger job when disability_526_call_received_email_from_polling enabled' do - allow(Flipper).to receive(:enabled?).with(:disability_526_call_received_email_from_polling, - anything).and_return(true) - expect do - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - end.to change(Form526ConfirmationEmailJob.jobs, :size).by(0) - end + it 'sets the submission.complete to true' do + expect(subject.workflow_complete).to be_falsey + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + subject.reload + expect(subject.workflow_complete).to be_falsey + end + end - it 'returns one job triggered when disability_526_call_received_email_from_polling disabled' do - allow(Flipper).to receive(:enabled?).with(:disability_526_call_received_email_from_polling, - anything).and_return(false) - expect do - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - end.to change(Form526ConfirmationEmailJob.jobs, :size).by(1) - end - end - end + context 'with a failing 526 form job' do + subject { create(:form526_submission, :with_one_failed_job) } - describe 'failure' do - context 'with mixed result jobs' do - subject { create(:form526_submission, :with_mixed_status) } + it 'sets the submission.complete to true' do + expect(subject.workflow_complete).to be_falsey + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + subject.reload + expect(subject.workflow_complete).to be_falsey + end + end - it 'sets the submission.complete to true' do - expect(subject.workflow_complete).to be_falsey - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - subject.reload - expect(subject.workflow_complete).to be_falsey - end - end + context 'with submission confirmation email when failed job statuses' do + subject { create(:form526_submission, :with_mixed_status) } - context 'with a failing 526 form job' do - subject { create(:form526_submission, :with_one_failed_job) } + it 'returns zero jobs triggered' do + expect do + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + end.to change(Form526ConfirmationEmailJob.jobs, :size).by(0) + end + end - it 'sets the submission.complete to true' do - expect(subject.workflow_complete).to be_falsey - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - subject.reload - expect(subject.workflow_complete).to be_falsey + it 'sends a submission failed email notification' do + expect do + subject.workflow_complete_handler(nil, 'submission_id' => subject.id) + end.to change(Form526SubmissionFailedEmailJob.jobs, :size).by(1) + end end end - context 'with submission confirmation email when failed job statuses' do - subject { create(:form526_submission, :with_mixed_status) } + describe '#disabilities_not_service_connected?' do + subject { form_526_submission.disabilities_not_service_connected? } + + before { create(:idme_user_verification, idme_uuid: user.idme_uuid, user_account:) } - it 'returns zero jobs triggered' do - expect do - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - end.to change(Form526ConfirmationEmailJob.jobs, :size).by(0) + let(:form_526_submission) do + Form526Submission.create( + user_uuid: user.uuid, + user_account: user.user_account, + saved_claim_id: saved_claim.id, + auth_headers_json: auth_headers.to_json, + form_json: File.read("spec/support/disability_compensation_form/submissions/#{form_json_filename}") + ) end - end - it 'sends a submission failed email notification' do - expect do - subject.workflow_complete_handler(nil, 'submission_id' => subject.id) - end.to change(Form526SubmissionFailedEmailJob.jobs, :size).by(1) - end - end - end + context 'evss provider' do + before do + VCR.insert_cassette('evss/disability_compensation_form/rated_disabilities_with_non_service_connected') + end - describe '#disabilities_not_service_connected?' do - subject { form_526_submission.disabilities_not_service_connected? } + after do + VCR.eject_cassette('evss/disability_compensation_form/rated_disabilities_with_non_service_connected') + end - before { create(:idme_user_verification, idme_uuid: user.idme_uuid, user_account:) } + context 'when all corresponding rated disabilities are not service-connected' do + let(:form_json_filename) { 'only_526_asthma.json' } - let(:form_526_submission) do - Form526Submission.create( - user_uuid: user.uuid, - user_account: user.user_account, - saved_claim_id: saved_claim.id, - auth_headers_json: auth_headers.to_json, - form_json: File.read("spec/support/disability_compensation_form/submissions/#{form_json_filename}") - ) - end + it 'returns true' do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + expect(subject).to be_truthy + end + end + + context 'when some but not all corresponding rated disabilities are not service-connected' do + let(:form_json_filename) { 'only_526_two_rated_disabilities.json' } - context 'evss provider' do - before { VCR.insert_cassette('evss/disability_compensation_form/rated_disabilities_with_non_service_connected') } - after { VCR.eject_cassette('evss/disability_compensation_form/rated_disabilities_with_non_service_connected') } + it 'returns false' do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + expect(subject).to be_falsey + end + end - context 'when all corresponding rated disabilities are not service-connected' do - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - let(:form_json_filename) { 'only_526_asthma.json' } + context 'when some disabilities do not have a ratedDisabilityId yet' do + let(:form_json_filename) { 'only_526_mixed_action_disabilities.json' } - it 'returns true' do - expect(subject).to be_truthy + it 'returns false' do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + expect(subject).to be_falsey + end + end end - end - context 'when some but not all corresponding rated disabilities are not service-connected' do - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - let(:form_json_filename) { 'only_526_two_rated_disabilities.json' } + context 'Lighthouse provider' do + before do + Flipper.enable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + VCR.insert_cassette('lighthouse/veteran_verification/disability_rating/200_Not_Connected_response') + allow_any_instance_of(Auth::ClientCredentials::Service).to receive(:get_token).and_return('blahblech') + end - it 'returns false' do - expect(subject).to be_falsey - end - end + after do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + VCR.eject_cassette('lighthouse/veteran_verification/disability_rating/200_Not_Connected_response') + end - context 'when some disabilities do not have a ratedDisabilityId yet' do - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - let(:form_json_filename) { 'only_526_mixed_action_disabilities.json' } + context 'when all corresponding rated disabilities are not service-connected' do + let(:form_json_filename) { 'only_526_asthma.json' } - it 'returns false' do - expect(subject).to be_falsey - end - end - end + it 'returns true' do + expect(subject).to be_truthy + end + end - context 'Lighthouse provider' do - before do - Flipper.enable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - VCR.insert_cassette('lighthouse/veteran_verification/disability_rating/200_Not_Connected_response') - allow_any_instance_of(Auth::ClientCredentials::Service).to receive(:get_token).and_return('blahblech') - end + context 'when some but not all corresponding rated disabilities are not service-connected' do + let(:form_json_filename) { 'only_526_two_rated_disabilities.json' } - after do - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - VCR.eject_cassette('lighthouse/veteran_verification/disability_rating/200_Not_Connected_response') - end + it 'returns false' do + expect(subject).to be_falsey + end + end - context 'when all corresponding rated disabilities are not service-connected' do - let(:form_json_filename) { 'only_526_asthma.json' } + context 'when some disabilities do not have a ratedDisabilityId yet' do + let(:form_json_filename) { 'only_526_mixed_action_disabilities.json' } - it 'returns true' do - expect(subject).to be_truthy + it 'returns false' do + expect(subject).to be_falsey + end + end end end - context 'when some but not all corresponding rated disabilities are not service-connected' do - let(:form_json_filename) { 'only_526_two_rated_disabilities.json' } + describe '#cfi_checkbox_was_selected?' do + subject { form_526_submission.cfi_checkbox_was_selected? } - it 'returns false' do - expect(subject).to be_falsey + let!(:in_progress_form) { create(:in_progress_526_form, user_uuid: user.uuid) } + let(:form_526_submission) do + Form526Submission.create( + user_uuid: user.uuid, + user_account: user.user_account, + saved_claim_id: saved_claim.id, + auth_headers_json: auth_headers.to_json, + form_json: File.read('spec/support/disability_compensation_form/submissions/only_526_tinnitus.json') + ) end - end - context 'when some disabilities do not have a ratedDisabilityId yet' do - let(:form_json_filename) { 'only_526_mixed_action_disabilities.json' } - - it 'returns false' do - expect(subject).to be_falsey + context 'when associated with a default InProgressForm' do + it 'returns false' do + expect(subject).to be_falsey + end end - end - end - end - describe '#cfi_checkbox_was_selected?' do - subject { form_526_submission.cfi_checkbox_was_selected? } - - let!(:in_progress_form) { create(:in_progress_526_form, user_uuid: user.uuid) } - let(:form_526_submission) do - Form526Submission.create( - user_uuid: user.uuid, - user_account: user.user_account, - saved_claim_id: saved_claim.id, - auth_headers_json: auth_headers.to_json, - form_json: File.read('spec/support/disability_compensation_form/submissions/only_526_tinnitus.json') - ) - end + context 'when associated with a InProgressForm that went through CFI being selected' do + let(:params) do + { form_data: { 'view:claim_type' => { 'view:claiming_increase' => true } } } + end - context 'when associated with a default InProgressForm' do - it 'returns false' do - expect(subject).to be_falsey + it 'returns true' do + ClaimFastTracking::MaxCfiMetrics.log_form_update(in_progress_form, params) + in_progress_form.update!(params) + expect(subject).to be_truthy + end + end end - end - context 'when associated with a InProgressForm that went through CFI being selected' do - let(:params) do - { form_data: { 'view:claim_type' => { 'view:claiming_increase' => true } } } - end + describe '#remediated?' do + context 'when there are no form526_submission_remediations' do + it 'returns false' do + expect(subject).not_to be_remediated + end + end - it 'returns true' do - ClaimFastTracking::MaxCfiMetrics.log_form_update(in_progress_form, params) - in_progress_form.update!(params) - expect(subject).to be_truthy - end - end - end + context 'when there are form526_submission_remediations' do + let(:remediation) do + FactoryBot.create(:form526_submission_remediation, form526_submission: subject) + end - describe '#remediated?' do - context 'when there are no form526_submission_remediations' do - it 'returns false' do - expect(subject).not_to be_remediated - end - end + it 'returns true if the most recent remediation was successful' do + remediation.update(success: true) + expect(subject).to be_remediated + end - context 'when there are form526_submission_remediations' do - let(:remediation) do - FactoryBot.create(:form526_submission_remediation, form526_submission: subject) + it 'returns false if the most recent remediation was not successful' do + remediation.update(success: false) + expect(subject).not_to be_remediated + end + end end - it 'returns true if the most recent remediation was successful' do - remediation.update(success: true) - expect(subject).to be_remediated - end + describe '#duplicate?' do + context 'when there are no form526_submission_remediations' do + it 'returns false' do + expect(subject).not_to be_duplicate + end + end - it 'returns false if the most recent remediation was not successful' do - remediation.update(success: false) - expect(subject).not_to be_remediated - end - end - end + context 'when there are form526_submission_remediations' do + let(:remediation) do + FactoryBot.create(:form526_submission_remediation, form526_submission: subject) + end - describe '#duplicate?' do - context 'when there are no form526_submission_remediations' do - it 'returns false' do - expect(subject).not_to be_duplicate - end - end + it 'returns true if the most recent remediation_type is ignored_as_duplicate' do + remediation.update(remediation_type: :ignored_as_duplicate) + expect(subject).to be_duplicate + end - context 'when there are form526_submission_remediations' do - let(:remediation) do - FactoryBot.create(:form526_submission_remediation, form526_submission: subject) + it 'returns false if the most recent remediation_type is not ignored_as_duplicate' do + remediation.update(remediation_type: :manual) + expect(subject).not_to be_duplicate + end + end end - it 'returns true if the most recent remediation_type is ignored_as_duplicate' do - remediation.update(remediation_type: :ignored_as_duplicate) - expect(subject).to be_duplicate - end + describe '#success_type?' do + let(:remediation) do + FactoryBot.create(:form526_submission_remediation, form526_submission: subject) + end - it 'returns false if the most recent remediation_type is not ignored_as_duplicate' do - remediation.update(remediation_type: :manual) - expect(subject).not_to be_duplicate - end - end - end + context 'when submitted_claim_id is present and backup_submitted_claim_status is nil' do + subject { create(:form526_submission, :with_submitted_claim_id) } - describe '#success_type?' do - let(:remediation) do - FactoryBot.create(:form526_submission_remediation, form526_submission: subject) - end + it 'returns true' do + expect(subject).to be_success_type + end + end - context 'when submitted_claim_id is present and backup_submitted_claim_status is nil' do - subject { create(:form526_submission, :with_submitted_claim_id) } + context 'when backup_submitted_claim_id is present and backup_submitted_claim_status is accepted' do + subject { create(:form526_submission, :backup_path, :backup_accepted) } - it 'returns true' do - expect(subject).to be_success_type - end - end + it 'returns true' do + expect(subject).to be_success_type + end + end - context 'when backup_submitted_claim_id is present and backup_submitted_claim_status is accepted' do - subject { create(:form526_submission, :backup_path, :backup_accepted) } + context 'when the most recent remediation is successful' do + it 'returns true' do + remediation.update(success: true) + expect(subject).to be_success_type + end + end - it 'returns true' do - expect(subject).to be_success_type + context 'when none of the success conditions are met' do + it 'returns false' do + remediation.update(success: false) + expect(subject).not_to be_success_type + end + end end - end - context 'when the most recent remediation is successful' do - it 'returns true' do - remediation.update(success: true) - expect(subject).to be_success_type - end - end + describe '#in_process?' do + context 'when submitted_claim_id and backup_submitted_claim_status are both nil' do + context 'and the record was created within the last 3 days' do + it 'returns true' do + expect(subject).to be_in_process + end + end - context 'when none of the success conditions are met' do - it 'returns false' do - remediation.update(success: false) - expect(subject).not_to be_success_type - end - end - end + context 'and the record was created more than 3 weeks ago' do + subject { create(:form526_submission, :created_more_than_3_weeks_ago) } - describe '#in_process?' do - context 'when submitted_claim_id and backup_submitted_claim_status are both nil' do - context 'and the record was created within the last 3 days' do - it 'returns true' do - expect(subject).to be_in_process + it 'returns false' do + expect(subject).not_to be_in_process + end + end end - end - context 'and the record was created more than 3 weeks ago' do - subject { create(:form526_submission, :created_more_than_3_weeks_ago) } + context 'when submitted_claim_id is not nil' do + subject { create(:form526_submission, :with_submitted_claim_id) } - it 'returns false' do - expect(subject).not_to be_in_process + it 'returns false' do + expect(subject).not_to be_in_process + end end - end - end - - context 'when submitted_claim_id is not nil' do - subject { create(:form526_submission, :with_submitted_claim_id) } - - it 'returns false' do - expect(subject).not_to be_in_process - end - end - context 'when backup_submitted_claim_status is not nil' do - subject { create(:form526_submission, :backup_path, :backup_accepted) } + context 'when backup_submitted_claim_status is not nil' do + subject { create(:form526_submission, :backup_path, :backup_accepted) } - it 'returns false' do - expect(subject).not_to be_in_process + it 'returns false' do + expect(subject).not_to be_in_process + end + end end - end - end - describe '#failure_type?' do - context 'when the submission is a success type' do - subject { create(:form526_submission, :with_submitted_claim_id) } + describe '#failure_type?' do + context 'when the submission is a success type' do + subject { create(:form526_submission, :with_submitted_claim_id) } - it 'returns true' do - expect(subject).not_to be_failure_type - end - end + it 'returns true' do + expect(subject).not_to be_failure_type + end + end - context 'when the submission is in process' do - it 'returns false' do - expect(subject).not_to be_failure_type - end - end + context 'when the submission is in process' do + it 'returns false' do + expect(subject).not_to be_failure_type + end + end - context 'when the submission is neither a success type nor in process' do - subject { create(:form526_submission, :created_more_than_3_weeks_ago) } + context 'when the submission is neither a success type nor in process' do + subject { create(:form526_submission, :created_more_than_3_weeks_ago) } - it 'returns true' do - expect(subject).to be_failure_type + it 'returns true' do + expect(subject).to be_failure_type + end + end end - end - end - describe 'ICN retrieval' do - context 'various ICN retrieval scenarios' do - let(:user) { FactoryBot.create(:user, :loa3) } - let(:auth_headers) do - EVSS::DisabilityCompensationAuthHeaders.new(user).add_headers(EVSS::AuthHeaders.new(user).to_h) - end - let(:submission) do - create(:form526_submission, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - let!(:form526_submission) { create(:form526_submission) } + describe 'ICN retrieval' do + context 'various ICN retrieval scenarios' do + let(:user) { FactoryBot.create(:user, :loa3) } + let(:auth_headers) do + EVSS::DisabilityCompensationAuthHeaders.new(user).add_headers(EVSS::AuthHeaders.new(user).to_h) + end + let(:submission) do + create(:form526_submission, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end + let!(:form526_submission) { create(:form526_submission) } - it 'submissions user account has an ICN, as expected' do - submission.user_account = UserAccount.new(icn: '123498767V222222') - account = submission.account - expect(account.icn).to eq('123498767V222222') - end + it 'submissions user account has an ICN, as expected' do + submission.user_account = UserAccount.new(icn: '123498767V222222') + account = submission.account + expect(account.icn).to eq('123498767V222222') + end - it 'submissions user account has no ICN, default to Account lookup' do - submission.user_account = UserAccount.new(icn: nil) - account = submission.account - expect(account.icn).to eq('123498767V234859') - end + it 'submissions user account has no ICN, default to Account lookup' do + submission.user_account = UserAccount.new(icn: nil) + account = submission.account + expect(account.icn).to eq('123498767V234859') + end - it 'submission has NO user account, default to Account lookup' do - account = submission.account - expect(account.icn).to eq('123498767V234859') - end + it 'submission has NO user account, default to Account lookup' do + account = submission.account + expect(account.icn).to eq('123498767V234859') + end - it 'submissions user account has no ICN, lookup from past submissions' do - user_account_with_icn = UserAccount.create!(icn: '123498767V111111') - create(:form526_submission, user_uuid: submission.user_uuid, user_account: user_account_with_icn) - submission.user_account = UserAccount.create!(icn: nil) - submission.save! - account = submission.account - expect(account.icn).to eq('123498767V111111') - end + it 'submissions user account has no ICN, lookup from past submissions' do + user_account_with_icn = UserAccount.create!(icn: '123498767V111111') + create(:form526_submission, user_uuid: submission.user_uuid, user_account: user_account_with_icn) + submission.user_account = UserAccount.create!(icn: nil) + submission.save! + account = submission.account + expect(account.icn).to eq('123498767V111111') + end - it 'lookup ICN from user verifications, idme_uuid defined' do - user_account_with_icn = UserAccount.create!(icn: '123498767V333333') - UserVerification.create!(idme_uuid: submission.user_uuid, user_account_id: user_account_with_icn.id) - submission.user_account = UserAccount.create!(icn: nil) - submission.save! - account = submission.account - expect(account.icn).to eq('123498767V333333') - end + it 'lookup ICN from user verifications, idme_uuid defined' do + user_account_with_icn = UserAccount.create!(icn: '123498767V333333') + UserVerification.create!(idme_uuid: submission.user_uuid, user_account_id: user_account_with_icn.id) + submission.user_account = UserAccount.create!(icn: nil) + submission.save! + account = submission.account + expect(account.icn).to eq('123498767V333333') + end - it 'lookup ICN from user verifications, backing_idme_uuid defined' do - user_account_with_icn = UserAccount.create!(icn: '123498767V444444') - UserVerification.create!(dslogon_uuid: Faker::Internet.uuid, backing_idme_uuid: submission.user_uuid, - user_account_id: user_account_with_icn.id) - submission.user_account = UserAccount.create!(icn: nil) - submission.save! - account = submission.account - expect(account.icn).to eq('123498767V444444') - end + it 'lookup ICN from user verifications, backing_idme_uuid defined' do + user_account_with_icn = UserAccount.create!(icn: '123498767V444444') + UserVerification.create!(dslogon_uuid: Faker::Internet.uuid, backing_idme_uuid: submission.user_uuid, + user_account_id: user_account_with_icn.id) + submission.user_account = UserAccount.create!(icn: nil) + submission.save! + account = submission.account + expect(account.icn).to eq('123498767V444444') + end - it 'lookup ICN from user verifications, alternate provider id defined' do - user_account_with_icn = UserAccount.create!(icn: '123498767V555555') - UserVerification.create!(dslogon_uuid: submission.user_uuid, backing_idme_uuid: Faker::Internet.uuid, - user_account_id: user_account_with_icn.id) - submission.user_account = UserAccount.create!(icn: nil) - submission.save! - account = submission.account - expect(account.icn).to eq('123498767V555555') + it 'lookup ICN from user verifications, alternate provider id defined' do + user_account_with_icn = UserAccount.create!(icn: '123498767V555555') + UserVerification.create!(dslogon_uuid: submission.user_uuid, backing_idme_uuid: Faker::Internet.uuid, + user_account_id: user_account_with_icn.id) + submission.user_account = UserAccount.create!(icn: nil) + submission.save! + account = submission.account + expect(account.icn).to eq('123498767V555555') + end + end end end end diff --git a/spec/models/saved_claim/education_benefits/va1995_spec.rb b/spec/models/saved_claim/education_benefits/va1995_spec.rb index e6a93c8fd4c..9afeed17acc 100644 --- a/spec/models/saved_claim/education_benefits/va1995_spec.rb +++ b/spec/models/saved_claim/education_benefits/va1995_spec.rb @@ -13,50 +13,64 @@ describe '#after_submit' do let(:user) { create(:user) } - describe 'sends confirmation email for the 1995' do - it 'with benefit selected' do - allow(VANotify::EmailJob).to receive(:perform_async) - - subject = create(:va1995_full_form) - confirmation_number = subject.education_benefits_claim.confirmation_number - - subject.after_submit(user) - - expect(VANotify::EmailJob).to have_received(:perform_async).with( - 'test@sample.com', - 'form1995_confirmation_email_template_id', - { - 'first_name' => 'FIRST', - 'benefit' => 'Transfer of Entitlement Program (TOE)', - 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), - 'confirmation_number' => confirmation_number, - 'regional_office_address' => "P.O. Box 4616\nBuffalo, NY 14240-4616" - } - ) - end + before do + allow(Flipper).to receive(:enabled?).with(:form1995_confirmation_email).and_return(true) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) + end + + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) + end + + describe 'sends confirmation email for the 1995' do + it 'with benefit selected' do + allow(VANotify::EmailJob).to receive(:perform_async) + + subject = create(:va1995_full_form) + confirmation_number = subject.education_benefits_claim.confirmation_number + + subject.after_submit(user) + + expect(VANotify::EmailJob).to have_received(:perform_async).with( + 'test@sample.com', + 'form1995_confirmation_email_template_id', + { + 'first_name' => 'FIRST', + 'benefit' => 'Transfer of Entitlement Program (TOE)', + 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), + 'confirmation_number' => confirmation_number, + 'regional_office_address' => "P.O. Box 4616\nBuffalo, NY 14240-4616" + } + ) + end + + it 'without benefit selected' do + allow(VANotify::EmailJob).to receive(:perform_async) + + subject = create(:va1995_full_form) + parsed_form_data = JSON.parse(subject.form) + parsed_form_data.delete('benefit') + subject.form = parsed_form_data.to_json + confirmation_number = subject.education_benefits_claim.confirmation_number + + subject.after_submit(user) - it 'without benefit selected' do - allow(VANotify::EmailJob).to receive(:perform_async) - - subject = create(:va1995_full_form) - parsed_form_data = JSON.parse(subject.form) - parsed_form_data.delete('benefit') - subject.form = parsed_form_data.to_json - confirmation_number = subject.education_benefits_claim.confirmation_number - - subject.after_submit(user) - - expect(VANotify::EmailJob).to have_received(:perform_async).with( - 'test@sample.com', - 'form1995_confirmation_email_template_id', - { - 'first_name' => 'FIRST', - 'benefit' => '', - 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), - 'confirmation_number' => confirmation_number, - 'regional_office_address' => "P.O. Box 4616\nBuffalo, NY 14240-4616" - } - ) + expect(VANotify::EmailJob).to have_received(:perform_async).with( + 'test@sample.com', + 'form1995_confirmation_email_template_id', + { + 'first_name' => 'FIRST', + 'benefit' => '', + 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), + 'confirmation_number' => confirmation_number, + 'regional_office_address' => "P.O. Box 4616\nBuffalo, NY 14240-4616" + } + ) + end + end end end end diff --git a/spec/models/saved_claim_spec.rb b/spec/models/saved_claim_spec.rb index 38141c39c64..d25308eb867 100644 --- a/spec/models/saved_claim_spec.rb +++ b/spec/models/saved_claim_spec.rb @@ -19,9 +19,10 @@ def attachment_keys subject(:saved_claim) { described_class.new(form: form_data) } let(:form_data) { { some_key: 'some_value' }.to_json } - let(:schema) { 'schema_content' } + let(:schema) { { some_key: 'some_value' }.to_json } before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) allow(VetsJsonSchema::SCHEMAS).to receive(:[]).and_return(schema) allow(JSON::Validator).to receive_messages(fully_validate_schema: [], fully_validate: []) end @@ -34,79 +35,128 @@ def attachment_keys describe 'validations' do context 'no validation errors' do - before do - allow(JSON::Validator).to receive(:fully_validate).and_return([]) + context 'using JSON Schema' do + before do + allow(JSON::Validator).to receive(:fully_validate).and_return([]) + end + + it 'returns true' do + expect(saved_claim.validate).to eq true + end end - it 'returns true' do - expect(saved_claim.validate).to eq true + context 'using JSON Schemer' do + before do + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(true) + end + + it 'returns true' do + expect(saved_claim.validate).to eq(true) + end end end context 'validation errors' do let(:schema_errors) { [{ fragment: 'error' }] } - context 'when fully_validate_schema returns errors' do - before do - allow(JSON::Validator).to receive_messages(fully_validate_schema: schema_errors, fully_validate: []) - end + context 'using JSON Schema' do + context 'when fully_validate_schema returns errors' do + before do + allow(Flipper).to receive(:enabled?).with(:saved_claim_schema_validation_disable).and_return(false) + allow(JSON::Validator).to receive_messages(fully_validate_schema: schema_errors, fully_validate: []) + end - it 'logs schema failed error and calls fully_validate' do - expect(Rails.logger).to receive(:error) - .with('SavedClaim schema failed validation! Attempting to clear cache.', { errors: schema_errors }) + it 'logs schema failed error and calls fully_validate' do + expect(Rails.logger).to receive(:error) + .with('SavedClaim schema failed validation! Attempting to clear cache.', { errors: schema_errors }) - expect(saved_claim.validate).to eq true + expect(saved_claim.validate).to eq true + end end - end - context 'when fully_validate returns errors' do - before do - allow(JSON::Validator).to receive(:fully_validate).and_return(schema_errors) - end + context 'when 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 - saved_claim.validate - expect(saved_claim.errors.full_messages).not_to be_empty + it 'adds validation errors to the form' do + saved_claim.validate + expect(saved_claim.errors.full_messages).not_to be_empty + end end - end - context 'when JSON:Validator.fully_validate_schema throws an exception' do - let(:exception) { StandardError.new('Some exception') } + context 'when JSON:Validator.fully_validate_schema throws an exception' do + let(:exception) { StandardError.new('Some exception') } - before do - allow(JSON::Validator).to receive(:fully_validate_schema).and_raise(exception) - allow(JSON::Validator).to receive(:fully_validate).and_return([]) + before do + allow(Flipper).to receive(:enabled?).with(:saved_claim_schema_validation_disable).and_return(true) + allow(JSON::Validator).to receive(:fully_validate_schema).and_raise(exception) + allow(JSON::Validator).to receive(:fully_validate).and_return([]) + end + + it 'logs exception and raises exception' do + expect(Rails.logger).to receive(:error) + .with('Error during schema validation!', { error: exception.message, backtrace: anything, schema: }) + + expect { saved_claim.validate }.to raise_error(exception.class, exception.message) + end end - it 'logs exception and raises exception' do - expect(Rails.logger).to receive(:error) - .with('Error during schema validation!', { error: exception.message, backtrace: anything, schema: }) + context 'when JSON:Validator.fully_validate throws an exception' do + let(:exception) { StandardError.new('Some exception') } - expect { saved_claim.validate }.to raise_error(exception.class, exception.message) + before do + allow(JSON::Validator).to receive(:fully_validate_schema).and_return([]) + allow(JSON::Validator).to receive(:fully_validate).and_raise(exception) + end + + it 'logs exception and raises exception' do + expect(Rails.logger).to receive(:error) + .with('Error during form validation!', { error: exception.message, backtrace: anything, schema:, + clear_cache: false }) + + expect(PersonalInformationLog).to receive(:create).with( + data: { schema: schema, + parsed_form: saved_claim.parsed_form, + params: { errors_as_objects: true, clear_cache: false } }, + error_class: 'SavedClaim FormValidationError' + ) + + expect { saved_claim.validate }.to raise_error(exception.class, exception.message) + end end end - context 'when JSON:Validator.fully_validate throws an exception' do - let(:exception) { StandardError.new('Some exception') } - + context 'using JSON Schemer' do before do - allow(JSON::Validator).to receive(:fully_validate_schema).and_return([]) - allow(JSON::Validator).to receive(:fully_validate).and_raise(exception) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(true) end - it 'logs exception and raises exception' do - expect(Rails.logger).to receive(:error) - .with('Error during form validation!', { error: exception.message, backtrace: anything, schema:, - clear_cache: false }) + context 'when validate_schema returns errors' do + before do + allow(Flipper).to receive(:enabled?).with(:saved_claim_schema_validation_disable).and_return(false) + allow(JSONSchemer).to receive_messages(validate_schema: schema_errors) + end - expect(PersonalInformationLog).to receive(:create).with( - data: { schema: schema, - parsed_form: saved_claim.parsed_form, - params: { errors_as_objects: true, clear_cache: false } }, - error_class: 'SavedClaim FormValidationError' - ) + it 'logs schema faild error' do + expect(Rails.logger).to receive(:error) + .with('SavedClaim schema failed validation! Attempting to clear cache.', { errors: schema_errors }) + + expect(saved_claim.validate).to eq(true) + end + end - expect { saved_claim.validate }.to raise_error(exception.class, exception.message) + context 'when form validation returns errors' do + before do + allow(JSONSchemer).to receive_messages(validate_schema: []) + allow(JSONSchemer).to receive(:schema).and_return(double(:fake_schema, + validate: [{ data_pointer: 'error' }])) + end + + it 'adds validation errors to the form' do + saved_claim.validate + expect(saved_claim.errors.full_messages).not_to be_empty + end end end end diff --git a/spec/requests/v0/form0969_spec.rb b/spec/requests/v0/form0969_spec.rb index 8870ddc7a00..81ee058e0d4 100644 --- a/spec/requests/v0/form0969_spec.rb +++ b/spec/requests/v0/form0969_spec.rb @@ -13,56 +13,75 @@ build(:income_and_assets_claim).parsed_form end - describe 'POST create' do - subject do - post('/v0/form0969', - params: params.to_json, - headers: { 'CONTENT_TYPE' => 'application/json', 'HTTP_X_KEY_INFLECTION' => 'camel' }) - end - - context 'with invalid params' do + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do before do - allow(Settings.sentry).to receive(:dsn).and_return('asdf') + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - let(:params) do - { - incomeAndAssetsClaim: { - form: full_claim.merge('veteranSocialSecurityNumber' => 'just a string').to_json - } - } - end + describe 'POST create' do + subject do + post('/v0/form0969', + params: params.to_json, + headers: { 'CONTENT_TYPE' => 'application/json', 'HTTP_X_KEY_INFLECTION' => 'camel' }) + end - it 'shows the validation errors' do - subject - expect(response).to have_http_status(:unprocessable_entity) - expect( - JSON.parse(response.body)['errors'][0]['detail'].include?( - "The property '#/veteranSocialSecurityNumber' value \"just a string\" did not match the regex" - ) - ).to eq(true) - end - end + context 'with invalid params' do + before do + allow(Settings.sentry).to receive(:dsn).and_return('asdf') + end - context 'with valid params' do - let(:params) do - { - incomeAndAssetsClaim: { - form: full_claim.to_json - } - } - end + let(:params) do + { + incomeAndAssetsClaim: { + form: full_claim.merge('veteranSocialSecurityNumber' => 'just a string').to_json + } + } + end - it 'renders success' do - subject - expect(JSON.parse(response.body)['data']['attributes'].keys.sort) - .to eq(%w[confirmationNumber form guid regionalOffice submittedAt]) - end + it 'shows the validation errors' do + subject + expect(response).to have_http_status(:unprocessable_entity) + + if flipper_value + expect( + JSON.parse(response.body)['errors'][0]['detail'].include?( + '/veteran-social-security-number - string at `/veteranSocialSecurityNumber` ' \ + 'does not match pattern: ^[0-9]{9}$' + ) + ).to eq(true) + else + expect( + JSON.parse(response.body)['errors'][0]['detail'].include?( + "The property '#/veteranSocialSecurityNumber' value \"just a string\" did not match the regex" + ) + ).to eq(true) + end + end + end + + context 'with valid params' do + let(:params) do + { + incomeAndAssetsClaim: { + form: full_claim.to_json + } + } + end + + it 'renders success' do + subject + expect(JSON.parse(response.body)['data']['attributes'].keys.sort) + .to eq(%w[confirmationNumber form guid regionalOffice submittedAt]) + end - it 'returns the expected regional office' do - subject - expect(JSON.parse(response.body)['data']['attributes']['regionalOffice'].join(', ')) - .to eq(reg_office) + it 'returns the expected regional office' do + subject + expect(JSON.parse(response.body)['data']['attributes']['regionalOffice'].join(', ')) + .to eq(reg_office) + end + end end end end diff --git a/spec/sidekiq/education_form/forms/va1995_spec.rb b/spec/sidekiq/education_form/forms/va1995_spec.rb index c6f1274126b..e89089e6e69 100644 --- a/spec/sidekiq/education_form/forms/va1995_spec.rb +++ b/spec/sidekiq/education_form/forms/va1995_spec.rb @@ -7,78 +7,88 @@ let(:education_benefits_claim) { build(:va1995).education_benefits_claim } - # For each sample application we have, format it and compare it against a 'known good' - # copy of that submission. This technically covers all the helper logic found in the - # `Form` specs, but are a good safety net for tracking how forms change over time. - %i[minimal kitchen_sink ch33_post911 ch33_fry ch30 ch1606].each do |application_name| - test_spool_file('1995', application_name) - end + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) + end - # run PROD_EMULATION=true rspec spec/sidekiq/education_form/forms/va1995_spec.rb to - # emulate production (e.g. when removing feature flags) - prod_emulation = true if ENV['PROD_EMULATION'].eql?('true') + # For each sample application we have, format it and compare it against a 'known good' + # copy of that submission. This technically covers all the helper logic found in the + # `Form` specs, but are a good safety net for tracking how forms change over time. + %i[minimal kitchen_sink ch33_post911 ch33_fry ch30 ch1606].each do |application_name| + test_spool_file('1995', application_name) + end - # :nocov: - context 'test 1995 - production emulation', if: prod_emulation do - before do - allow(Settings).to receive(:vsp_environment).and_return('vagov-production') - end + # run PROD_EMULATION=true rspec spec/sidekiq/education_form/forms/va1995_spec.rb to + # emulate production (e.g. when removing feature flags) + prod_emulation = true if ENV['PROD_EMULATION'].eql?('true') - %i[minimal kitchen_sink ch33_post911 ch33_fry ch30 ch1606].each do |application_name| - test_spool_file('1995', application_name) - end - end - # :nocov: + # :nocov: + context 'test 1995 - production emulation', if: prod_emulation do + before do + allow(Settings).to receive(:vsp_environment).and_return('vagov-production') + end - describe '#direct_deposit_type' do - let(:education_benefits_claim) { create(:va1995_full_form).education_benefits_claim } + %i[minimal kitchen_sink ch33_post911 ch33_fry ch30 ch1606].each do |application_name| + test_spool_file('1995', application_name) + end + end + # :nocov: - it 'converts internal keys to text' do - expect(subject.direct_deposit_type('startUpdate')).to eq('Start or Update') - expect(subject.direct_deposit_type('stop')).to eq('Stop') - expect(subject.direct_deposit_type('noChange')).to eq('Do Not Change') - end - end + describe '#direct_deposit_type' do + let(:education_benefits_claim) { create(:va1995_full_form).education_benefits_claim } - # :nocov: - describe '#direct_deposit_type - production emulation', if: prod_emulation do - before do - allow(Settings).to receive(:vsp_environment).and_return('vagov-production') - end + it 'converts internal keys to text' do + expect(subject.direct_deposit_type('startUpdate')).to eq('Start or Update') + expect(subject.direct_deposit_type('stop')).to eq('Stop') + expect(subject.direct_deposit_type('noChange')).to eq('Do Not Change') + end + end - let(:education_benefits_claim) { create(:va1995_full_form).education_benefits_claim } + # :nocov: + describe '#direct_deposit_type - production emulation', if: prod_emulation do + before do + allow(Settings).to receive(:vsp_environment).and_return('vagov-production') + end - it 'converts internal keys to text' do - expect(subject.direct_deposit_type('startUpdate')).to eq('Start or Update') - expect(subject.direct_deposit_type('stop')).to eq('Stop') - expect(subject.direct_deposit_type('noChange')).to eq('Do Not Change') - end - end - # :nocov: - - context 'spool_file tests with high school minors' do - %w[ - ch30_guardian_not_graduated - ch30_guardian_graduated_sponsor - ch30_guardian_graduated - ].each do |test_application| - test_spool_file('1995', test_application) - end - end + let(:education_benefits_claim) { create(:va1995_full_form).education_benefits_claim } - # :nocov: - context 'spool_file tests with high school minors - production emulation', if: prod_emulation do - before do - allow(Settings).to receive(:vsp_environment).and_return('vagov-production') - end + it 'converts internal keys to text' do + expect(subject.direct_deposit_type('startUpdate')).to eq('Start or Update') + expect(subject.direct_deposit_type('stop')).to eq('Stop') + expect(subject.direct_deposit_type('noChange')).to eq('Do Not Change') + end + end + # :nocov: + + context 'spool_file tests with high school minors' do + %w[ + ch30_guardian_not_graduated + ch30_guardian_graduated_sponsor + ch30_guardian_graduated + ].each do |test_application| + test_spool_file('1995', test_application) + end + end + + # :nocov: + context 'spool_file tests with high school minors - production emulation', if: prod_emulation do + before do + allow(Settings).to receive(:vsp_environment).and_return('vagov-production') + end - %w[ - ch30_guardian_not_graduated - ch30_guardian_graduated_sponsor - ch30_guardian_graduated - ].each do |test_application| - test_spool_file('1995', test_application) + %w[ + ch30_guardian_not_graduated + ch30_guardian_graduated_sponsor + ch30_guardian_graduated + ].each do |test_application| + test_spool_file('1995', test_application) + end + end + # :nocov: end end - # :nocov: end diff --git a/spec/sidekiq/education_form/send_school_certifying_officials_email_spec.rb b/spec/sidekiq/education_form/send_school_certifying_officials_email_spec.rb index ca86670dbf0..23bd53b201f 100644 --- a/spec/sidekiq/education_form/send_school_certifying_officials_email_spec.rb +++ b/spec/sidekiq/education_form/send_school_certifying_officials_email_spec.rb @@ -103,7 +103,7 @@ def sco_email_sent_false(less_than_six_months, facility_code) # Find the SCO email sco_email = ActionMailer::Base.deliveries.find do |email| - email.to.include?('test@edu_sample.com') + email.to.include?('user@school.edu') end expect(sco_email).not_to be_nil diff --git a/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb b/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb index 2b0bd480ef7..5c7977fef01 100644 --- a/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb +++ b/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb @@ -13,6 +13,8 @@ allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', instance_of(User)).and_return(false) allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(false) + allow(Flipper).to receive(:enabled?).with(:saved_claim_schema_validation_disable).and_return(false) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(false) end let(:user) { FactoryBot.create(:user, :loa3) } @@ -40,587 +42,599 @@ end end - describe '.perform_async' do - let(:submission) do - Form526Submission.create(user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id, - form_json: form0781, - submitted_claim_id: evss_claim_id) - end - - context 'with a successful submission job' do - it 'queues a job for submit' do - expect do - subject.perform_async(submission.id) - end.to change(subject.jobs, :size).by(1) - end - - it 'submits successfully' do - VCR.use_cassette('evss/disability_compensation_form/submit_0781') do - subject.perform_async(submission.id) - jid = subject.jobs.last['jid'] - described_class.drain - expect(jid).not_to be_empty - end - end - end - - context 'with a submission timeout' do - before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError) - end - - it 'raises a gateway timeout error' do - subject.perform_async(submission.id) - expect { described_class.drain }.to raise_error(StandardError) - end - end - - context 'with an unexpected error' do + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - it 'raises a standard error' do - subject.perform_async(submission.id) - expect { described_class.drain }.to raise_error(StandardError) - end - end - end - - context 'catastrophic failure state' do - describe 'when all retries are exhausted' do - let!(:form526_submission) { create(:form526_submission) } - let!(:form526_job_status) { create(:form526_job_status, :retryable_error, form526_submission:, job_id: 1) } - - it 'updates a StatsD counter and updates the status on an exhaustion event' do - subject.within_sidekiq_retries_exhausted_block({ 'jid' => form526_job_status.job_id }) do - # Will receieve increment for failure mailer metric - allow(StatsD).to receive(:increment).with( - 'shared.sidekiq.default.EVSS_DisabilityCompensationForm_Form0781DocumentUploadFailureEmail.enqueue' - ) - - expect(StatsD).to receive(:increment).with("#{subject::STATSD_KEY_PREFIX}.exhausted") - expect(Rails).to receive(:logger).and_call_original + describe '.perform_async' do + let(:submission) do + Form526Submission.create(user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id, + form_json: form0781, + submitted_claim_id: evss_claim_id) end - form526_job_status.reload - expect(form526_job_status.status).to eq(Form526JobStatus::STATUS[:exhausted]) - end - - context 'when an error occurs during exhaustion handling and FailureEmail fails to enqueue' do - let!(:failure_email) { EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail } - let!(:zsf_tag) { Form526Submission::ZSF_DD_TAG_SERVICE } - let!(:zsf_monitor) { ZeroSilentFailures::Monitor.new(zsf_tag) } - before do - allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(true) - allow(ZeroSilentFailures::Monitor).to receive(:new).with(zsf_tag).and_return(zsf_monitor) - end + context 'with a successful submission job' do + it 'queues a job for submit' do + expect do + subject.perform_async(submission.id) + end.to change(subject.jobs, :size).by(1) + end - it 'logs a silent failure' do - expect(zsf_monitor).to receive(:log_silent_failure).with( - { - job_id: form526_job_status.job_id, - error_class: nil, - error_message: 'An error occured', - timestamp: instance_of(Time), - form526_submission_id: form526_submission.id - }, - nil, - call_location: instance_of(Logging::CallLocation) - ) - - args = { 'jid' => form526_job_status.job_id, 'args' => [form526_submission.id] } - - expect do - subject.within_sidekiq_retries_exhausted_block(args) do - allow(failure_email).to receive(:perform_async).and_raise(StandardError, 'Simulated error') + it 'submits successfully' do + VCR.use_cassette('evss/disability_compensation_form/submit_0781') do + subject.perform_async(submission.id) + jid = subject.jobs.last['jid'] + described_class.drain + expect(jid).not_to be_empty end - end.to raise_error(StandardError, 'Simulated error') + end end - end - context 'when the form526_send_0781_failure_notification Flipper is enabled' do - before do - allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(true) - end + context 'with a submission timeout' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError) + end - it 'enqueues a failure notification mailer to send to the veteran' do - subject.within_sidekiq_retries_exhausted_block( - { - 'jid' => form526_job_status.job_id, - 'args' => [form526_submission.id] - } - ) do - expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail) - .to receive(:perform_async).with(form526_submission.id) + it 'raises a gateway timeout error' do + subject.perform_async(submission.id) + expect { described_class.drain }.to raise_error(StandardError) end end - end - context 'when the form526_send_0781_failure_notification Flipper is disabled' do - before do - allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(false) - end + context 'with an unexpected error' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) + end - it 'does not enqueue a failure notification mailer to send to the veteran' do - subject.within_sidekiq_retries_exhausted_block( - { - 'jid' => form526_job_status.job_id, - 'args' => [form526_submission.id] - } - ) do - expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail) - .not_to receive(:perform_async) + it 'raises a standard error' do + subject.perform_async(submission.id) + expect { described_class.drain }.to raise_error(StandardError) end end end - context 'when the API Provider uploads are enabled' do - before do - allow(Flipper).to receive(:enabled?) - .with(:disability_compensation_use_api_provider_for_0781_uploads).and_return(true) - end + context 'catastrophic failure state' do + describe 'when all retries are exhausted' do + let!(:form526_submission) { create(:form526_submission) } + let!(:form526_job_status) { create(:form526_job_status, :retryable_error, form526_submission:, job_id: 1) } - let(:sidekiq_job_exhaustion_errors) do - { - 'jid' => form526_job_status.job_id, - 'error_class' => 'Broken Job Error', - 'error_message' => 'Your Job Broke', - 'args' => [form526_submission.id] - } - end + it 'updates a StatsD counter and updates the status on an exhaustion event' do + subject.within_sidekiq_retries_exhausted_block({ 'jid' => form526_job_status.job_id }) do + # Will receieve increment for failure mailer metric + allow(StatsD).to receive(:increment).with( + 'shared.sidekiq.default.EVSS_DisabilityCompensationForm_Form0781DocumentUploadFailureEmail.enqueue' + ) - context 'for a Lighthouse upload' do - it 'logs the job failure' do - allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', - instance_of(User)).and_return(true) - - subject.within_sidekiq_retries_exhausted_block(sidekiq_job_exhaustion_errors) do - expect_any_instance_of(LighthouseSupplementalDocumentUploadProvider) - .to receive(:log_uploading_job_failure) - .with(EVSS::DisabilityCompensationForm::SubmitForm0781, 'Broken Job Error', 'Your Job Broke') + expect(StatsD).to receive(:increment).with("#{subject::STATSD_KEY_PREFIX}.exhausted") + expect(Rails).to receive(:logger).and_call_original end + form526_job_status.reload + expect(form526_job_status.status).to eq(Form526JobStatus::STATUS[:exhausted]) end - end - context 'for an EVSS Upload' do - it 'logs the job failure' do - allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', - instance_of(User)).and_return(false) + context 'when an error occurs during exhaustion handling and FailureEmail fails to enqueue' do + let!(:failure_email) { EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail } + let!(:zsf_tag) { Form526Submission::ZSF_DD_TAG_SERVICE } + let!(:zsf_monitor) { ZeroSilentFailures::Monitor.new(zsf_tag) } - subject.within_sidekiq_retries_exhausted_block(sidekiq_job_exhaustion_errors) do - expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_uploading_job_failure) - .with(EVSS::DisabilityCompensationForm::SubmitForm0781, 'Broken Job Error', 'Your Job Broke') + before do + allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(true) + allow(ZeroSilentFailures::Monitor).to receive(:new).with(zsf_tag).and_return(zsf_monitor) end - end - end - end - end - end - - context 'When an ApiProvider is used for uploads' do - before do - allow(Flipper).to receive(:enabled?) - .with(:disability_compensation_use_api_provider_for_0781_uploads).and_return(true) - - # StatsD metrics are incremented in several callbacks we're not testing here so we need to allow them - allow(StatsD).to receive(:increment) - # There is an ensure block in the upload_to_vbms method that deletes the generated PDF - allow(File).to receive(:delete).and_return(nil) - end - - let(:path_to_0781_fixture) { 'spec/fixtures/pdf_fill/21-0781/simple.pdf' } - let(:parsed_0781_form) { JSON.parse(submission.form_to_json(Form526Submission::FORM_0781))['form0781'] } - let(:form0781_only) do - original = JSON.parse(form0781) - original['form0781'].delete('form0781a') - original.to_json - end - - let(:path_to_0781a_fixture) { 'spec/fixtures/pdf_fill/21-0781a/kitchen_sink.pdf' } - let(:parsed_0781a_form) { JSON.parse(submission.form_to_json(Form526Submission::FORM_0781))['form0781a'] } - let(:form0781a_only) do - original = JSON.parse(form0781) - original['form0781'].delete('form0781') - original.to_json - end - - let(:submission) do - Form526Submission.create(user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id, - form_json: form0781, # contains 0781 and 0781a - submitted_claim_id: evss_claim_id) - end - - let(:perform_upload) do - subject.perform_async(submission.id) - described_class.drain - end - - context 'when the disability_compensation_upload_0781_to_lighthouse flipper is enabled' do - let(:faraday_response) { instance_double(Faraday::Response) } - let(:lighthouse_request_id) { Faker::Number.number(digits: 8) } - let(:lighthouse_0781_document) do - LighthouseDocument.new( - claim_id: submission.submitted_claim_id, - participant_id: submission.auth_headers['va_eauth_pid'], - document_type: 'L228' - ) - end - let(:lighthouse_0781a_document) do - LighthouseDocument.new( - claim_id: submission.submitted_claim_id, - participant_id: submission.auth_headers['va_eauth_pid'], - document_type: 'L229' - ) - end - let(:expected_statsd_metrics_prefix) do - 'worker.evss.submit_form0781.lighthouse_supplemental_document_upload_provider' - end - - before do - allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', - instance_of(User)).and_return(true) - - allow(BenefitsDocuments::Form526::UploadSupplementalDocumentService).to receive(:call) - .and_return(faraday_response) - - allow(faraday_response).to receive(:body).and_return( - { - 'data' => { - 'success' => true, - 'requestId' => lighthouse_request_id - } - } - ) - end - - context 'when a submission has both 0781 and 0781a' do - context 'when the request is successful' do - it 'uploads both documents to Lighthouse' do - # 0781 - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') - .and_return(path_to_0781_fixture) - allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .and_return(lighthouse_0781_document) - - expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) - .to receive(:call) - .with(File.read(path_to_0781_fixture), lighthouse_0781a_document) - - # 0781a - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') - .and_return(path_to_0781a_fixture) - - allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .and_return(lighthouse_0781a_document) + it 'logs a silent failure' do + expect(zsf_monitor).to receive(:log_silent_failure).with( + { + job_id: form526_job_status.job_id, + error_class: nil, + error_message: 'An error occured', + timestamp: instance_of(Time), + form526_submission_id: form526_submission.id + }, + nil, + call_location: instance_of(Logging::CallLocation) + ) + + args = { 'jid' => form526_job_status.job_id, 'args' => [form526_submission.id] } + + expect do + subject.within_sidekiq_retries_exhausted_block(args) do + allow(failure_email).to receive(:perform_async).and_raise(StandardError, 'Simulated error') + end + end.to raise_error(StandardError, 'Simulated error') + end + end - expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) - .to receive(:call) - .with(File.read(path_to_0781a_fixture), lighthouse_0781a_document) + context 'when the form526_send_0781_failure_notification Flipper is enabled' do + before do + allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(true) + end - perform_upload + it 'enqueues a failure notification mailer to send to the veteran' do + subject.within_sidekiq_retries_exhausted_block( + { + 'jid' => form526_job_status.job_id, + 'args' => [form526_submission.id] + } + ) do + expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail) + .to receive(:perform_async).with(form526_submission.id) + end + end end - it 'logs the upload attempt with the correct job prefix' do - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_attempt" - ).twice # For 0781 and 0781a - perform_upload + context 'when the form526_send_0781_failure_notification Flipper is disabled' do + before do + allow(Flipper).to receive(:enabled?).with(:form526_send_0781_failure_notification).and_return(false) + end + + it 'does not enqueue a failure notification mailer to send to the veteran' do + subject.within_sidekiq_retries_exhausted_block( + { + 'jid' => form526_job_status.job_id, + 'args' => [form526_submission.id] + } + ) do + expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail) + .not_to receive(:perform_async) + end + end end - it 'increments the correct StatsD success metric' do - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_success" - ).twice # For 0781 and 0781a + context 'when the API Provider uploads are enabled' do + before do + allow(Flipper).to receive(:enabled?) + .with(:disability_compensation_use_api_provider_for_0781_uploads).and_return(true) + end - perform_upload - end + let(:sidekiq_job_exhaustion_errors) do + { + 'jid' => form526_job_status.job_id, + 'error_class' => 'Broken Job Error', + 'error_message' => 'Your Job Broke', + 'args' => [form526_submission.id] + } + end - it 'creates a pending Lighthouse526DocumentUpload record so we can poll Lighthouse later' do - upload_attributes = { - aasm_state: 'pending', - form526_submission_id: submission.id, - lighthouse_document_request_id: lighthouse_request_id - } + context 'for a Lighthouse upload' do + it 'logs the job failure' do + allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', + instance_of(User)).and_return(true) + + subject.within_sidekiq_retries_exhausted_block(sidekiq_job_exhaustion_errors) do + expect_any_instance_of(LighthouseSupplementalDocumentUploadProvider) + .to receive(:log_uploading_job_failure) + .with(EVSS::DisabilityCompensationForm::SubmitForm0781, 'Broken Job Error', 'Your Job Broke') + end + end + end - expect(Lighthouse526DocumentUpload.where(**upload_attributes).count).to eq(0) + context 'for an EVSS Upload' do + it 'logs the job failure' do + allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', + instance_of(User)).and_return(false) - perform_upload - expect(Lighthouse526DocumentUpload.where(**upload_attributes) - .where(document_type: 'Form 0781').count).to eq(1) - expect(Lighthouse526DocumentUpload.where(**upload_attributes) - .where(document_type: 'Form 0781a').count).to eq(1) + subject.within_sidekiq_retries_exhausted_block(sidekiq_job_exhaustion_errors) do + expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_uploading_job_failure) + .with(EVSS::DisabilityCompensationForm::SubmitForm0781, 'Broken Job Error', 'Your Job Broke') + end + end + end end end end - context 'when a submission has 0781 only' do + context 'When an ApiProvider is used for uploads' do before do - submission.update(form_json: form0781_only) + allow(Flipper).to receive(:enabled?) + .with(:disability_compensation_use_api_provider_for_0781_uploads).and_return(true) + + # StatsD metrics are incremented in several callbacks we're not testing here so we need to allow them + allow(StatsD).to receive(:increment) + # There is an ensure block in the upload_to_vbms method that deletes the generated PDF + allow(File).to receive(:delete).and_return(nil) end - context 'when the request is successful' do - it 'uploads to Lighthouse' do - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') - .and_return(path_to_0781_fixture) + let(:path_to_0781_fixture) { 'spec/fixtures/pdf_fill/21-0781/simple.pdf' } + let(:parsed_0781_form) { JSON.parse(submission.form_to_json(Form526Submission::FORM_0781))['form0781'] } + let(:form0781_only) do + original = JSON.parse(form0781) + original['form0781'].delete('form0781a') + original.to_json + end - allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .and_return(lighthouse_0781_document) + let(:path_to_0781a_fixture) { 'spec/fixtures/pdf_fill/21-0781a/kitchen_sink.pdf' } + let(:parsed_0781a_form) { JSON.parse(submission.form_to_json(Form526Submission::FORM_0781))['form0781a'] } + let(:form0781a_only) do + original = JSON.parse(form0781) + original['form0781'].delete('form0781') + original.to_json + end - expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) - .to receive(:call) - .with(File.read(path_to_0781_fixture), lighthouse_0781_document) + let(:submission) do + Form526Submission.create(user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id, + form_json: form0781, # contains 0781 and 0781a + submitted_claim_id: evss_claim_id) + end - perform_upload - end + let(:perform_upload) do + subject.perform_async(submission.id) + described_class.drain end - context 'when Lighthouse returns an error response' do - let(:exception_errors) { [{ detail: 'Something Broke' }] } + context 'when the disability_compensation_upload_0781_to_lighthouse flipper is enabled' do + let(:faraday_response) { instance_double(Faraday::Response) } + let(:lighthouse_request_id) { Faker::Number.number(digits: 8) } + let(:lighthouse_0781_document) do + LighthouseDocument.new( + claim_id: submission.submitted_claim_id, + participant_id: submission.auth_headers['va_eauth_pid'], + document_type: 'L228' + ) + end + let(:lighthouse_0781a_document) do + LighthouseDocument.new( + claim_id: submission.submitted_claim_id, + participant_id: submission.auth_headers['va_eauth_pid'], + document_type: 'L229' + ) + end + let(:expected_statsd_metrics_prefix) do + 'worker.evss.submit_form0781.lighthouse_supplemental_document_upload_provider' + end before do - # Skip additional logging that occurs in Lighthouse::ServiceException handling - allow(Rails.logger).to receive(:error) + allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', + instance_of(User)).and_return(true) allow(BenefitsDocuments::Form526::UploadSupplementalDocumentService).to receive(:call) - .and_raise(Common::Exceptions::BadRequest.new(errors: exception_errors)) - end + .and_return(faraday_response) - it 'logs the Lighthouse error response and re-raises the exception' do - expect(Rails.logger).to receive(:error).with( - 'LighthouseSupplementalDocumentUploadProvider upload failed', + allow(faraday_response).to receive(:body).and_return( { - class: 'LighthouseSupplementalDocumentUploadProvider', - submission_id: submission.id, - submitted_claim_id: submission.submitted_claim_id, - user_uuid: submission.user_uuid, - va_document_type_code: 'L228', - primary_form: 'Form526', - error_info: exception_errors + 'data' => { + 'success' => true, + 'requestId' => lighthouse_request_id + } } ) - - expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) end - it 'increments the correct status failure metric' do - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_failure" - ) - - expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + context 'when a submission has both 0781 and 0781a' do + context 'when the request is successful' do + it 'uploads both documents to Lighthouse' do + # 0781 + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') + .and_return(path_to_0781_fixture) + + allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .and_return(lighthouse_0781_document) + + expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) + .to receive(:call) + .with(File.read(path_to_0781_fixture), lighthouse_0781a_document) + + # 0781a + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') + .and_return(path_to_0781a_fixture) + + allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .and_return(lighthouse_0781a_document) + + expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) + .to receive(:call) + .with(File.read(path_to_0781a_fixture), lighthouse_0781a_document) + + perform_upload + end + + it 'logs the upload attempt with the correct job prefix' do + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_attempt" + ).twice # For 0781 and 0781a + perform_upload + end + + it 'increments the correct StatsD success metric' do + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_success" + ).twice # For 0781 and 0781a + + perform_upload + end + + it 'creates a pending Lighthouse526DocumentUpload record so we can poll Lighthouse later' do + upload_attributes = { + aasm_state: 'pending', + form526_submission_id: submission.id, + lighthouse_document_request_id: lighthouse_request_id + } + + expect(Lighthouse526DocumentUpload.where(**upload_attributes).count).to eq(0) + + perform_upload + expect(Lighthouse526DocumentUpload.where(**upload_attributes) + .where(document_type: 'Form 0781').count).to eq(1) + expect(Lighthouse526DocumentUpload.where(**upload_attributes) + .where(document_type: 'Form 0781a').count).to eq(1) + end + end end - end - end - context 'when a submission has 0781a only' do - before do - submission.update(form_json: form0781a_only) - end + context 'when a submission has 0781 only' do + before do + submission.update(form_json: form0781_only) + end - context 'when a request is successful' do - it 'uploads to Lighthouse' do - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') - .and_return(path_to_0781a_fixture) + context 'when the request is successful' do + it 'uploads to Lighthouse' do + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') + .and_return(path_to_0781_fixture) - allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .and_return(lighthouse_0781a_document) + allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .and_return(lighthouse_0781_document) + + expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) + .to receive(:call) + .with(File.read(path_to_0781_fixture), lighthouse_0781_document) - expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) - .to receive(:call) - .with(File.read(path_to_0781a_fixture), lighthouse_0781a_document) + perform_upload + end + end - perform_upload + context 'when Lighthouse returns an error response' do + let(:exception_errors) { [{ detail: 'Something Broke' }] } + + before do + # Skip additional logging that occurs in Lighthouse::ServiceException handling + allow(Rails.logger).to receive(:error) + + allow(BenefitsDocuments::Form526::UploadSupplementalDocumentService).to receive(:call) + .and_raise(Common::Exceptions::BadRequest.new(errors: exception_errors)) + end + + it 'logs the Lighthouse error response and re-raises the exception' do + expect(Rails.logger).to receive(:error).with( + 'LighthouseSupplementalDocumentUploadProvider upload failed', + { + class: 'LighthouseSupplementalDocumentUploadProvider', + submission_id: submission.id, + submitted_claim_id: submission.submitted_claim_id, + user_uuid: submission.user_uuid, + va_document_type_code: 'L228', + primary_form: 'Form526', + error_info: exception_errors + } + ) + + expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + end + + it 'increments the correct status failure metric' do + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_failure" + ) + + expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + end + end end - end - context 'when Lighthouse returns an error response' do - let(:exception_errors) { [{ detail: 'Something Broke' }] } + context 'when a submission has 0781a only' do + before do + submission.update(form_json: form0781a_only) + end - before do - # Skip additional logging that occurs in Lighthouse::ServiceException handling - allow(Rails.logger).to receive(:error) + context 'when a request is successful' do + it 'uploads to Lighthouse' do + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') + .and_return(path_to_0781a_fixture) - allow(BenefitsDocuments::Form526::UploadSupplementalDocumentService).to receive(:call) - .and_raise(Common::Exceptions::BadRequest.new(errors: exception_errors)) - end + allow_any_instance_of(LighthouseSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .and_return(lighthouse_0781a_document) - it 'logs the Lighthouse error response and re-raises the exception' do - expect(Rails.logger).to receive(:error).with( - 'LighthouseSupplementalDocumentUploadProvider upload failed', - { - class: 'LighthouseSupplementalDocumentUploadProvider', - submission_id: submission.id, - submitted_claim_id: submission.submitted_claim_id, - user_uuid: submission.user_uuid, - va_document_type_code: 'L229', - primary_form: 'Form526', - error_info: exception_errors - } - ) + expect(BenefitsDocuments::Form526::UploadSupplementalDocumentService) + .to receive(:call) + .with(File.read(path_to_0781a_fixture), lighthouse_0781a_document) + + perform_upload + end + end - expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + context 'when Lighthouse returns an error response' do + let(:exception_errors) { [{ detail: 'Something Broke' }] } + + before do + # Skip additional logging that occurs in Lighthouse::ServiceException handling + allow(Rails.logger).to receive(:error) + + allow(BenefitsDocuments::Form526::UploadSupplementalDocumentService).to receive(:call) + .and_raise(Common::Exceptions::BadRequest.new(errors: exception_errors)) + end + + it 'logs the Lighthouse error response and re-raises the exception' do + expect(Rails.logger).to receive(:error).with( + 'LighthouseSupplementalDocumentUploadProvider upload failed', + { + class: 'LighthouseSupplementalDocumentUploadProvider', + submission_id: submission.id, + submitted_claim_id: submission.submitted_claim_id, + user_uuid: submission.user_uuid, + va_document_type_code: 'L229', + primary_form: 'Form526', + error_info: exception_errors + } + ) + + expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + end + + it 'increments the correct status failure metric' do + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_failure" + ) + + expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) + end + end end + end - it 'increments the correct status failure metric' do - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_failure" + context 'when the disability_compensation_upload_0781_to_lighthouse flipper is disabled' do + let(:evss_claim_0781_document) do + EVSSClaimDocument.new( + evss_claim_id: submission.submitted_claim_id, + document_type: 'L228' ) - - expect { perform_upload }.to raise_error(Common::Exceptions::BadRequest) end - end - end - end + let(:evss_claim_0781a_document) do + EVSSClaimDocument.new( + evss_claim_id: submission.submitted_claim_id, + document_type: 'L229' + ) + end + let(:client_stub) { instance_double(EVSS::DocumentsService) } + let(:expected_statsd_metrics_prefix) do + 'worker.evss.submit_form0781.evss_supplemental_document_upload_provider' + end - context 'when the disability_compensation_upload_0781_to_lighthouse flipper is disabled' do - let(:evss_claim_0781_document) do - EVSSClaimDocument.new( - evss_claim_id: submission.submitted_claim_id, - document_type: 'L228' - ) - end - let(:evss_claim_0781a_document) do - EVSSClaimDocument.new( - evss_claim_id: submission.submitted_claim_id, - document_type: 'L229' - ) - end - let(:client_stub) { instance_double(EVSS::DocumentsService) } - let(:expected_statsd_metrics_prefix) { 'worker.evss.submit_form0781.evss_supplemental_document_upload_provider' } + before do + allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', + instance_of(User)).and_return(false) - before do - allow(Flipper).to receive(:enabled?).with('disability_compensation_upload_0781_to_lighthouse', - instance_of(User)).and_return(false) - - allow(EVSS::DocumentsService).to receive(:new) { client_stub } - allow(client_stub).to receive(:upload) - # 0781 - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') - .and_return(path_to_0781_fixture) - allow_any_instance_of(EVSSSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .with('simple.pdf') - .and_return(evss_claim_0781_document) - - # 0781a - allow_any_instance_of(described_class) - .to receive(:generate_stamp_pdf) - .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') - .and_return(path_to_0781a_fixture) - allow_any_instance_of(EVSSSupplementalDocumentUploadProvider) - .to receive(:generate_upload_document) - .with('kitchen_sink.pdf') - .and_return(evss_claim_0781a_document) - end + allow(EVSS::DocumentsService).to receive(:new) { client_stub } + allow(client_stub).to receive(:upload) + # 0781 + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781_form, submission.submitted_claim_id, '21-0781') + .and_return(path_to_0781_fixture) + allow_any_instance_of(EVSSSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .with('simple.pdf') + .and_return(evss_claim_0781_document) - context 'when a submission has both 0781 and 0781a' do - context 'when the request is successful' do - it 'uploads both documents to EVSS' do - expect(client_stub).to receive(:upload).with(File.read(path_to_0781_fixture), evss_claim_0781_document) + # 0781a + allow_any_instance_of(described_class) + .to receive(:generate_stamp_pdf) + .with(parsed_0781a_form, submission.submitted_claim_id, '21-0781a') + .and_return(path_to_0781a_fixture) + allow_any_instance_of(EVSSSupplementalDocumentUploadProvider) + .to receive(:generate_upload_document) + .with('kitchen_sink.pdf') + .and_return(evss_claim_0781a_document) + end - expect(client_stub).to receive(:upload).with(File.read(path_to_0781a_fixture), evss_claim_0781a_document) + context 'when a submission has both 0781 and 0781a' do + context 'when the request is successful' do + it 'uploads both documents to EVSS' do + expect(client_stub).to receive(:upload).with(File.read(path_to_0781_fixture), evss_claim_0781_document) - perform_upload - end + expect(client_stub).to receive(:upload).with(File.read(path_to_0781a_fixture), + evss_claim_0781a_document) - it 'logs the upload attempt with the correct job prefix' do - allow(client_stub).to receive(:upload) - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_attempt" - ).twice # For 0781 and 0781a + perform_upload + end - perform_upload - end + it 'logs the upload attempt with the correct job prefix' do + allow(client_stub).to receive(:upload) + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_attempt" + ).twice # For 0781 and 0781a - it 'increments the correct StatsD success metric' do - allow(client_stub).to receive(:upload) - expect(StatsD).to receive(:increment).with( - "#{expected_statsd_metrics_prefix}.upload_success" - ).twice # For 0781 and 0781a + perform_upload + end - perform_upload - end - end + it 'increments the correct StatsD success metric' do + allow(client_stub).to receive(:upload) + expect(StatsD).to receive(:increment).with( + "#{expected_statsd_metrics_prefix}.upload_success" + ).twice # For 0781 and 0781a - context 'when an upload raises an EVSS response error' do - it 'logs an upload error and re-raises the error' do - allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) - expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) + perform_upload + end + end - expect do - subject.perform_async(submission.id) - described_class.drain - end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + context 'when an upload raises an EVSS response error' do + it 'logs an upload error and re-raises the error' do + allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) + expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) + + expect do + subject.perform_async(submission.id) + described_class.drain + end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + end + end end - end - end - context 'when a submission has only a 0781 form' do - before do - submission.update(form_json: form0781_only) - end + context 'when a submission has only a 0781 form' do + before do + submission.update(form_json: form0781_only) + end - context 'when the request is successful' do - it 'uploads to EVSS' do - submission.update(form_json: form0781_only) - expect(client_stub).to receive(:upload).with(File.read(path_to_0781_fixture), evss_claim_0781_document) + context 'when the request is successful' do + it 'uploads to EVSS' do + submission.update(form_json: form0781_only) + expect(client_stub).to receive(:upload).with(File.read(path_to_0781_fixture), evss_claim_0781_document) - perform_upload - end - end + perform_upload + end + end - context 'when an upload raises an EVSS response error' do - it 'logs an upload error and re-raises the error' do - allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) - expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) + context 'when an upload raises an EVSS response error' do + it 'logs an upload error and re-raises the error' do + allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) + expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) - expect do - subject.perform_async(submission.id) - described_class.drain - end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + expect do + subject.perform_async(submission.id) + described_class.drain + end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + end + end end - end - end - context 'when a submission has only a 0781a form' do - context 'when the request is successful' do - it 'uploads the 0781a document to EVSS' do - submission.update(form_json: form0781a_only) - expect(client_stub).to receive(:upload).with(File.read(path_to_0781a_fixture), evss_claim_0781a_document) + context 'when a submission has only a 0781a form' do + context 'when the request is successful' do + it 'uploads the 0781a document to EVSS' do + submission.update(form_json: form0781a_only) + expect(client_stub).to receive(:upload).with(File.read(path_to_0781a_fixture), + evss_claim_0781a_document) - perform_upload - end - end + perform_upload + end + end - context 'when an upload raises an EVSS response error' do - it 'logs an upload error and re-raises the error' do - allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) - expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) + context 'when an upload raises an EVSS response error' do + it 'logs an upload error and re-raises the error' do + allow(client_stub).to receive(:upload).and_raise(EVSS::ErrorMiddleware::EVSSError) + expect_any_instance_of(EVSSSupplementalDocumentUploadProvider).to receive(:log_upload_failure) - expect do - subject.perform_async(submission.id) - described_class.drain - end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + expect do + subject.perform_async(submission.id) + described_class.drain + end.to raise_error(EVSS::ErrorMiddleware::EVSSError) + end + end end end end diff --git a/spec/sidekiq/evss/disability_compensation_form/submit_form526_all_claim_spec.rb b/spec/sidekiq/evss/disability_compensation_form/submit_form526_all_claim_spec.rb index 312beb99873..88943b34496 100644 --- a/spec/sidekiq/evss/disability_compensation_form/submit_form526_all_claim_spec.rb +++ b/spec/sidekiq/evss/disability_compensation_form/submit_form526_all_claim_spec.rb @@ -10,12 +10,14 @@ RSpec.describe EVSS::DisabilityCompensationForm::SubmitForm526AllClaim, type: :job do subject { described_class } + # This needs to be modernized (using allow) before do Sidekiq::Job.clear_all + Flipper.disable(:validate_saved_claims_with_json_schemer) + Flipper.disable(:disability_526_expanded_contention_classification) Flipper.disable(:disability_compensation_lighthouse_claims_service_provider) Flipper.disable(:disability_compensation_production_tester) Flipper.disable(:disability_compensation_fail_submission) - Flipper.disable(:disability_526_expanded_contention_classification) end let(:user) { FactoryBot.create(:user, :loa3) } @@ -23,569 +25,729 @@ EVSS::DisabilityCompensationAuthHeaders.new(user).add_headers(EVSS::AuthHeaders.new(user).to_h) end - describe '.perform_async' do - define_negated_matcher :not_change, :change - - let(:saved_claim) { FactoryBot.create(:va526ez) } - let(:submitted_claim_id) { 600_130_094 } - let(:user_account) { create(:user_account, icn: '123498767V234859') } - let(:submission) do - create(:form526_submission, - user_account_id: user_account.id, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - let(:open_claims_cassette) { 'evss/claims/claims_without_open_compensation_claims' } - let(:caseflow_cassette) { 'caseflow/appeals' } - let(:rated_disabilities_cassette) { 'evss/disability_compensation_form/rated_disabilities' } - let(:submit_form_cassette) { 'evss/disability_compensation_form/submit_form_v2' } - let(:lh_upload) { 'lighthouse/benefits_intake/200_lighthouse_intake_upload_location' } - let(:evss_get_pdf) { 'form526_backup/200_evss_get_pdf' } - let(:lh_intake_upload) { 'lighthouse/benefits_intake/200_lighthouse_intake_upload' } - let(:lh_submission) { 'lighthouse/benefits_claims/submit526/200_synchronous_response' } - let(:cassettes) do - [open_claims_cassette, caseflow_cassette, rated_disabilities_cassette, - submit_form_cassette, lh_upload, evss_get_pdf, - lh_intake_upload, lh_submission] - end - let(:backup_klass) { Sidekiq::Form526BackupSubmissionProcess::Submit } - - before do - cassettes.each { |cassette| VCR.insert_cassette(cassette) } - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) - Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_FOREGROUND) - end - - after do - cassettes.each { |cassette| VCR.eject_cassette(cassette) } - end - - def expect_retryable_error(error_class) - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once - expect(Form526JobStatus).to receive(:upsert).twice - expect do - described_class.drain - end.to raise_error(error_class).and not_change(backup_klass.jobs, :size) - end - - def expect_non_retryable_error - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_non_retryable).once - expect(Form526JobStatus).to receive(:upsert).thrice - expect_any_instance_of(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim).to( - receive(:non_retryable_error_handler).and_call_original - ) - described_class.drain - end - - context 'Submission inspection for flashes' do + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do before do - allow(Rails.logger).to receive(:info) - allow(StatsD).to receive(:increment) - end - - def submit_it - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/contention_classification_null_response') do - described_class.drain - end - submission.reload - expect(Form526JobStatus.last.status).to eq 'success' + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - context 'without any flashes' do - let(:submission) do - create(:form526_submission, - :asthma_claim_for_increase, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - - it 'does not log or push metrics' do - submit_it + describe '.perform_async' do + define_negated_matcher :not_change, :change - expect(Rails.logger).not_to have_received(:info).with('Flash Prototype Added', anything) - expect(StatsD).not_to have_received(:increment).with('worker.flashes', anything) - end - end - - context 'with flash but without prototype' do + let(:saved_claim) { FactoryBot.create(:va526ez) } + let(:submitted_claim_id) { 600_130_094 } + let(:user_account) { create(:user_account, icn: '123498767V234859') } let(:submission) do create(:form526_submission, - :without_diagnostic_code, + user_account_id: user_account.id, user_uuid: user.uuid, auth_headers_json: auth_headers.to_json, saved_claim_id: saved_claim.id) end - - it 'does not log prototype statement but pushes metrics' do - submit_it - - expect(Rails.logger).not_to have_received(:info).with('Flash Prototype Added', anything) - expect(StatsD).to have_received(:increment).with( - 'worker.flashes', - tags: ['flash:Priority Processing - Veteran over age 85', 'prototype:false'] - ).once + let(:open_claims_cassette) { 'evss/claims/claims_without_open_compensation_claims' } + let(:caseflow_cassette) { 'caseflow/appeals' } + let(:rated_disabilities_cassette) { 'evss/disability_compensation_form/rated_disabilities' } + let(:submit_form_cassette) { 'evss/disability_compensation_form/submit_form_v2' } + let(:lh_upload) { 'lighthouse/benefits_intake/200_lighthouse_intake_upload_location' } + let(:evss_get_pdf) { 'form526_backup/200_evss_get_pdf' } + let(:lh_intake_upload) { 'lighthouse/benefits_intake/200_lighthouse_intake_upload' } + let(:lh_submission) { 'lighthouse/benefits_claims/submit526/200_synchronous_response' } + let(:cassettes) do + [open_claims_cassette, caseflow_cassette, rated_disabilities_cassette, + submit_form_cassette, lh_upload, evss_get_pdf, + lh_intake_upload, lh_submission] end - end + let(:backup_klass) { Sidekiq::Form526BackupSubmissionProcess::Submit } - context 'with ALS flash' do - let(:submission) do - create(:form526_submission, - :als_claim_for_increase, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) + before do + cassettes.each { |cassette| VCR.insert_cassette(cassette) } + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_BACKGROUND) + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_RATED_DISABILITIES_FOREGROUND) end - it 'logs prototype statement and pushes metrics' do - submit_it - - expect(Rails.logger).to have_received(:info).with( - 'Flash Prototype Added', - { submitted_claim_id:, flashes: ['Amyotrophic Lateral Sclerosis'] } - ).once - expect(StatsD).to have_received(:increment).with( - 'worker.flashes', - tags: ['flash:Amyotrophic Lateral Sclerosis', 'prototype:true'] - ).once + after do + cassettes.each { |cassette| VCR.eject_cassette(cassette) } end - end - context 'with multiple flashes' do - let(:submission) do - create(:form526_submission, - :als_claim_for_increase_terminally_ill, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) + def expect_retryable_error(error_class) + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once + expect(Form526JobStatus).to receive(:upsert).twice + expect do + described_class.drain + end.to raise_error(error_class).and not_change(backup_klass.jobs, :size) end - it 'logs prototype statement and pushes metrics' do - submit_it - - expect(Rails.logger).to have_received(:info).with( - 'Flash Prototype Added', - { submitted_claim_id:, flashes: ['Amyotrophic Lateral Sclerosis', 'Terminally Ill'] } - ).once - expect(StatsD).to have_received(:increment).with( - 'worker.flashes', - tags: ['flash:Amyotrophic Lateral Sclerosis', 'prototype:true'] - ).once - expect(StatsD).to have_received(:increment).with( - 'worker.flashes', - tags: ['flash:Terminally Ill', 'prototype:false'] - ).once + def expect_non_retryable_error + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_non_retryable).once + expect(Form526JobStatus).to receive(:upsert).thrice + expect_any_instance_of(EVSS::DisabilityCompensationForm::SubmitForm526AllClaim).to( + receive(:non_retryable_error_handler).and_call_original + ) + described_class.drain end - end - end - context 'with contention classification enabled' do - context 'when diagnostic code is not set' do - let(:submission) do - create(:form526_submission, - :without_diagnostic_code, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - end + context 'Submission inspection for flashes' do + before do + allow(Rails.logger).to receive(:info) + allow(StatsD).to receive(:increment) + end - context 'when diagnostic code is set' do - it 'still completes form 526 submission when CC fails' do - subject.perform_async(submission.id) - expect do - VCR.use_cassette('virtual_regional_office/contention_classification_failure') do + def submit_it + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/contention_classification_null_response') do described_class.drain end - end.not_to change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq 'success' - end + submission.reload + expect(Form526JobStatus.last.status).to eq 'success' + end - it 'handles null response gracefully' do - subject.perform_async(submission.id) - expect do - VCR.use_cassette('virtual_regional_office/contention_classification_null_response') do - described_class.drain - submission.reload + context 'without any flashes' do + let(:submission) do + create(:form526_submission, + :asthma_claim_for_increase, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - final_classification_code = submission.form['form526']['form526']['disabilities'][0]['classificationCode'] - expect(final_classification_code).to eq(ONLY_526_JSON_CLASSIFICATION_CODE) + it 'does not log or push metrics' do + submit_it + + expect(Rails.logger).not_to have_received(:info).with('Flash Prototype Added', anything) + expect(StatsD).not_to have_received(:increment).with('worker.flashes', anything) end - end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) - expect(Form526JobStatus.last.status).to eq 'success' - end + end - it 'updates Form526Submission form with id' do - expect(described_class).to be < EVSS::DisabilityCompensationForm::SubmitForm526 - subject.perform_async(submission.id) + context 'with flash but without prototype' do + let(:submission) do + create(:form526_submission, + :without_diagnostic_code, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - expect do - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain - submission.reload + it 'does not log prototype statement but pushes metrics' do + submit_it - final_classification_code = submission.form['form526']['form526']['disabilities'][0]['classificationCode'] - expect(final_classification_code).to eq(9012) + expect(Rails.logger).not_to have_received(:info).with('Flash Prototype Added', anything) + expect(StatsD).to have_received(:increment).with( + 'worker.flashes', + tags: ['flash:Priority Processing - Veteran over age 85', 'prototype:false'] + ).once end - end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) - end + end - context 'when veteran has open claims' do - let(:open_claims_cassette) { 'evss/claims/claims' } + context 'with ALS flash' do + let(:submission) do + create(:form526_submission, + :als_claim_for_increase, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - before do - allow(Rails.logger).to receive(:info) - Timecop.freeze('2018-09-28T13:00:00ZZ') + it 'logs prototype statement and pushes metrics' do + submit_it + + expect(Rails.logger).to have_received(:info).with( + 'Flash Prototype Added', + { submitted_claim_id:, flashes: ['Amyotrophic Lateral Sclerosis'] } + ).once + expect(StatsD).to have_received(:increment).with( + 'worker.flashes', + tags: ['flash:Amyotrophic Lateral Sclerosis', 'prototype:true'] + ).once + end end - after { Timecop.return } + context 'with multiple flashes' do + let(:submission) do + create(:form526_submission, + :als_claim_for_increase_terminally_ill, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - it 'logs the expected data for EP 400 merge eligibility' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain + it 'logs prototype statement and pushes metrics' do + submit_it + + expect(Rails.logger).to have_received(:info).with( + 'Flash Prototype Added', + { submitted_claim_id:, flashes: ['Amyotrophic Lateral Sclerosis', 'Terminally Ill'] } + ).once + expect(StatsD).to have_received(:increment).with( + 'worker.flashes', + tags: ['flash:Amyotrophic Lateral Sclerosis', 'prototype:true'] + ).once + expect(StatsD).to have_received(:increment).with( + 'worker.flashes', + tags: ['flash:Terminally Ill', 'prototype:false'] + ).once end - expect(Rails.logger).to have_received(:info).with('EP Merge total open EPs', id: submission.id, count: 1) - expect(Rails.logger).to have_received(:info).with( - 'EP Merge open EP eligibility', - { id: submission.id, feature_enabled: true, open_claim_review: false, - pending_ep_age: 365, pending_ep_status: 'UNDER REVIEW' } - ) end + end - context 'when the claim is not fully classified' do - it 'does not log EP 400 merge eligibility' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/multi_contention_classification') do - described_class.drain - end - expect(Rails.logger).not_to have_received(:info).with( - 'EP Merge total open EPs', id: submission.id, count: 1 - ) - expect(Rails.logger).not_to have_received(:info).with( - 'EP Merge open EP eligibility', - { id: submission.id, feature_enabled: true, open_claim_review: false, - pending_ep_age: 365, pending_ep_status: 'UNDER REVIEW' } - ) + context 'with contention classification enabled' do + context 'when diagnostic code is not set' do + let(:submission) do + create(:form526_submission, + :without_diagnostic_code, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) end end - context 'when using LH Benefits Claims API instead of EVSS' do - before do - Flipper.enable(:disability_compensation_lighthouse_claims_service_provider) - allow_any_instance_of(BenefitsClaims::Configuration).to receive(:access_token).and_return('access_token') + context 'when diagnostic code is set' do + it 'still completes form 526 submission when CC fails' do + subject.perform_async(submission.id) + expect do + VCR.use_cassette('virtual_regional_office/contention_classification_failure') do + described_class.drain + end + end.not_to change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq 'success' end - after { Flipper.disable(:disability_compensation_lighthouse_claims_service_provider) } + it 'handles null response gracefully' do + subject.perform_async(submission.id) + expect do + VCR.use_cassette('virtual_regional_office/contention_classification_null_response') do + described_class.drain + submission.reload - let(:open_claims_cassette) { 'lighthouse/benefits_claims/index/claims_with_single_open_disability_claim' } + final_code = submission.form['form526']['form526']['disabilities'][0]['classificationCode'] + expect(final_code).to eq(ONLY_526_JSON_CLASSIFICATION_CODE) + end + end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) + expect(Form526JobStatus.last.status).to eq 'success' + end - it 'logs the expected data for EP 400 merge eligibility' do + it 'updates Form526Submission form with id' do + expect(described_class).to be < EVSS::DisabilityCompensationForm::SubmitForm526 subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain - end - expect(Rails.logger).to have_received(:info).with('EP Merge total open EPs', id: submission.id, count: 1) - expect(Rails.logger).to have_received(:info).with( - 'EP Merge open EP eligibility', - { id: submission.id, feature_enabled: true, open_claim_review: false, - pending_ep_age: 365, pending_ep_status: 'INITIAL_REVIEW' } - ) + + expect do + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + submission.reload + + final_code = submission.form['form526']['form526']['disabilities'][0]['classificationCode'] + expect(final_code).to eq(9012) + end + end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) end - context 'when the claim is not fully classified' do - it 'does not log EP 400 merge eligibility' do + context 'when veteran has open claims' do + let(:open_claims_cassette) { 'evss/claims/claims' } + + before do + allow(Rails.logger).to receive(:info) + Timecop.freeze('2018-09-28T13:00:00ZZ') + end + + after { Timecop.return } + + it 'logs the expected data for EP 400 merge eligibility' do subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/multi_contention_classification') do + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do described_class.drain end - expect(Rails.logger).not_to have_received(:info).with( - 'EP Merge total open EPs', id: submission.id, count: 1 - ) - expect(Rails.logger).not_to have_received(:info).with( + expect(Rails.logger).to have_received(:info).with('EP Merge total open EPs', id: submission.id, + count: 1) + expect(Rails.logger).to have_received(:info).with( 'EP Merge open EP eligibility', { id: submission.id, feature_enabled: true, open_claim_review: false, - pending_ep_age: 365, pending_ep_status: 'INITIAL_REVIEW' } + pending_ep_age: 365, pending_ep_status: 'UNDER REVIEW' } ) end - end - end - context 'when EP400 merge API call is enabled' do - before do - Flipper.enable(:disability_526_ep_merge_api, user) - allow(Flipper).to receive(:enabled?).and_call_original - end + context 'when the claim is not fully classified' do + it 'does not log EP 400 merge eligibility' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/multi_contention_classification') do + described_class.drain + end + expect(Rails.logger).not_to have_received(:info).with( + 'EP Merge total open EPs', id: submission.id, count: 1 + ) + expect(Rails.logger).not_to have_received(:info).with( + 'EP Merge open EP eligibility', + { id: submission.id, feature_enabled: true, open_claim_review: false, + pending_ep_age: 365, pending_ep_status: 'UNDER REVIEW' } + ) + end + end - it 'records the eligible claim ID and adds the EP400 special issue to the submission' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain + context 'when using LH Benefits Claims API instead of EVSS' do + before do + Flipper.enable(:disability_compensation_lighthouse_claims_service_provider) + allow_any_instance_of(BenefitsClaims::Configuration).to receive(:access_token) + .and_return('access_token') + end + + after { Flipper.disable(:disability_compensation_lighthouse_claims_service_provider) } + + let(:open_claims_cassette) do + 'lighthouse/benefits_claims/index/claims_with_single_open_disability_claim' + end + + it 'logs the expected data for EP 400 merge eligibility' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + expect(Rails.logger).to have_received(:info).with('EP Merge total open EPs', id: submission.id, + count: 1) + expect(Rails.logger).to have_received(:info).with( + 'EP Merge open EP eligibility', + { id: submission.id, feature_enabled: true, open_claim_review: false, + pending_ep_age: 365, pending_ep_status: 'INITIAL_REVIEW' } + ) + end + + context 'when the claim is not fully classified' do + it 'does not log EP 400 merge eligibility' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/multi_contention_classification') do + described_class.drain + end + expect(Rails.logger).not_to have_received(:info).with( + 'EP Merge total open EPs', id: submission.id, count: 1 + ) + expect(Rails.logger).not_to have_received(:info).with( + 'EP Merge open EP eligibility', + { id: submission.id, feature_enabled: true, open_claim_review: false, + pending_ep_age: 365, pending_ep_status: 'INITIAL_REVIEW' } + ) + end + end end - submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to eq('600114692') # from claims.yml - expect(submission.disabilities.first).to include('specialIssues' => ['EMP']) - actor = OpenStruct.new({ flipper_id: submission.user_uuid }) - expect(Flipper).to have_received(:enabled?).with(:disability_526_ep_merge_api, actor).once - end - context 'when the claim is not fully classified' do - it 'does not record an eligible claim id' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/multi_contention_classification') do - described_class.drain + context 'when EP400 merge API call is enabled' do + before do + Flipper.enable(:disability_526_ep_merge_api, user) + allow(Flipper).to receive(:enabled?).and_call_original + end + + it 'records the eligible claim ID and adds the EP400 special issue to the submission' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to eq('600114692') # from claims.yml + expect(submission.disabilities.first).to include('specialIssues' => ['EMP']) + actor = OpenStruct.new({ flipper_id: submission.user_uuid }) + expect(Flipper).to have_received(:enabled?).with(:disability_526_ep_merge_api, actor).once + end + + context 'when the claim is not fully classified' do + it 'does not record an eligible claim id' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/multi_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + expect(submission.disabilities.first['specialIssues']).to be_nil + end end - submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil - expect(submission.disabilities.first['specialIssues']).to be_nil end - end - end - context 'when pending claim has lifecycle status not considered open for EP400 merge' do - let(:open_claims_cassette) { 'evss/claims/claims_pending_decision_approval' } + context 'when pending claim has lifecycle status not considered open for EP400 merge' do + let(:open_claims_cassette) { 'evss/claims/claims_pending_decision_approval' } - it 'does not save any claim ID for EP400 merge' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain + it 'does not save any claim ID for EP400 merge' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + end + end + + context 'when an EP 030 or 040 is included in the list of open claims' do + let(:open_claims_cassette) { 'evss/claims/claims_with_open_040' } + + it 'does not save any claim ID for EP400 merge' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + end + end + + context 'when Caseflow appeals status API returns an open claim review' do + let(:caseflow_cassette) { 'caseflow/appeals_with_hlr_only' } + + it 'does not save any claim ID for EP400 merge' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + end + end + + context 'when EP400 merge API call is disabled' do + before { Flipper.disable(:disability_526_ep_merge_api) } + + it 'does not record any eligible claim ID or add an EP400 special issue to the submission' do + subject.perform_async(submission.id) + VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + described_class.drain + end + submission.reload + expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + expect(submission.disabilities.first['specialIssues']).to be_nil + end end - submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil end end + end + + context 'with multi-contention classification enabled' do + let(:submission) do + create(:form526_submission, + :with_mixed_action_disabilities_and_free_text, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - context 'when an EP 030 or 040 is included in the list of open claims' do - let(:open_claims_cassette) { 'evss/claims/claims_with_open_040' } + it 'does something when multi-contention api endpoint is hit' do + subject.perform_async(submission.id) - it 'does not save any claim ID for EP400 merge' do - subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do + expect do + VCR.use_cassette('virtual_regional_office/multi_contention_classification') do described_class.drain end - submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil - end + end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) + submission.reload + + classification_codes = submission.form['form526']['form526']['disabilities'].pluck('classificationCode') + expect(classification_codes).to eq([9012, 8994, nil, nil]) end - context 'when Caseflow appeals status API returns an open claim review' do - let(:caseflow_cassette) { 'caseflow/appeals_with_hlr_only' } + it 'calls va-gov-claim-classifier as default' do + vro_client_mock = instance_double(VirtualRegionalOffice::Client) + allow(VirtualRegionalOffice::Client).to receive(:new).and_return(vro_client_mock) + allow(vro_client_mock).to receive_messages( + classify_vagov_contentions_expanded: OpenStruct.new(body: 'expanded classification'), + classify_vagov_contentions: OpenStruct.new(body: 'regular response') + ) + + expect_any_instance_of(Form526Submission).to receive(:classify_vagov_contentions).and_call_original + expect(vro_client_mock).to receive(:classify_vagov_contentions) + subject.perform_async(submission.id) + described_class.drain + end + + context 'when the expanded classification endpoint is enabled' do + before do + user = OpenStruct.new({ flipper_id: submission.user_uuid }) + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:disability_526_expanded_contention_classification, + user).and_return(true) + end + + it 'calls the expanded classification endpoint' do + vro_client_mock = instance_double(VirtualRegionalOffice::Client) + allow(VirtualRegionalOffice::Client).to receive(:new).and_return(vro_client_mock) + allow(vro_client_mock).to receive_messages( + classify_vagov_contentions_expanded: OpenStruct.new(body: 'expanded classification'), + classify_vagov_contentions: OpenStruct.new(body: 'regular response') + ) - it 'does not save any claim ID for EP400 merge' do + expect_any_instance_of(Form526Submission).to receive(:classify_vagov_contentions).and_call_original + expect(vro_client_mock).to receive(:classify_vagov_contentions_expanded) subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain - end + described_class.drain + end + + it 'uses expanded classification to classify contentions' do + subject.perform_async(submission.id) + expect do + VCR.use_cassette('virtual_regional_office/expanded_classification') do + described_class.drain + end + end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil + + classification_codes = submission.form['form526']['form526']['disabilities'].pluck('classificationCode') + expect(classification_codes).to eq([9012, 8994, nil, 8997]) end end - context 'when EP400 merge API call is disabled' do - before { Flipper.disable(:disability_526_ep_merge_api) } + context 'when the disabilities array is empty' do + before do + allow(Rails.logger).to receive(:info) + end - it 'does not record any eligible claim ID or add an EP400 special issue to the submission' do + let(:submission) do + create(:form526_submission, + :with_empty_disabilities, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end + + it 'returns false to skip classification and continue other jobs' do subject.perform_async(submission.id) - VCR.use_cassette('virtual_regional_office/fully_classified_contention_classification') do - described_class.drain - end - submission.reload - expect(submission.read_metadata(:ep_merge_pending_claim_id)).to be_nil - expect(submission.disabilities.first['specialIssues']).to be_nil + expect(submission.update_contention_classification_all!).to eq false + expect(Rails.logger).to have_received(:info).with( + "No disabilities found for classification on claim #{submission.id}" + ) + end + + it 'does not call va-gov-claim-classifier' do + subject.perform_async(submission.id) + described_class.drain + expect(submission).not_to receive(:classify_vagov_contentions) end end end - end - end - - context 'with multi-contention classification enabled' do - let(:submission) do - create(:form526_submission, - :with_mixed_action_disabilities_and_free_text, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - it 'does something when multi-contention api endpoint is hit' do - subject.perform_async(submission.id) + context 'with a successful submission job' do + it 'queues a job for submit' do + expect do + subject.perform_async(submission.id) + end.to change(subject.jobs, :size).by(1) + end - expect do - VCR.use_cassette('virtual_regional_office/multi_contention_classification') do - described_class.drain + it 'submits successfully' do + subject.perform_async(submission.id) + expect { described_class.drain }.not_to change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq 'success' end - end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) - submission.reload - classification_codes = submission.form['form526']['form526']['disabilities'].pluck('classificationCode') - expect(classification_codes).to eq([9012, 8994, nil, nil]) - end + it 'submits successfully without calling classification service' do + subject.perform_async(submission.id) + expect do + VCR.use_cassette('virtual_regional_office/multi_contention_classification') do + described_class.drain + end + end.not_to change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq 'success' + end - it 'calls va-gov-claim-classifier as default' do - vro_client_mock = instance_double(VirtualRegionalOffice::Client) - allow(VirtualRegionalOffice::Client).to receive(:new).and_return(vro_client_mock) - allow(vro_client_mock).to receive_messages( - classify_vagov_contentions_expanded: OpenStruct.new(body: 'expanded classification'), - classify_vagov_contentions: OpenStruct.new(body: 'regular response') - ) - - expect_any_instance_of(Form526Submission).to receive(:classify_vagov_contentions).and_call_original - expect(vro_client_mock).to receive(:classify_vagov_contentions) - subject.perform_async(submission.id) - described_class.drain - end + it 'does not call contention classification endpoint' do + subject.perform_async(submission.id) + expect(submission).not_to receive(:classify_vagov_contentions) + described_class.drain + end - context 'when the expanded classification endpoint is enabled' do - before do - user = OpenStruct.new({ flipper_id: submission.user_uuid }) - allow(Flipper).to receive(:enabled?).and_call_original - allow(Flipper).to receive(:enabled?).with(:disability_526_expanded_contention_classification, - user).and_return(true) - end + context 'with an MAS-related diagnostic code' do + let(:submission) do + create(:form526_submission, + :non_rrd_with_mas_diagnostic_code, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end + let(:mas_cassette) { 'mail_automation/mas_initiate_apcas_request' } + let(:cassettes) do + [open_claims_cassette, rated_disabilities_cassette, submit_form_cassette, mas_cassette] + end - it 'calls the expanded classification endpoint' do - vro_client_mock = instance_double(VirtualRegionalOffice::Client) - allow(VirtualRegionalOffice::Client).to receive(:new).and_return(vro_client_mock) - allow(vro_client_mock).to receive_messages( - classify_vagov_contentions_expanded: OpenStruct.new(body: 'expanded classification'), - classify_vagov_contentions: OpenStruct.new(body: 'regular response') - ) + before do + allow(StatsD).to receive(:increment) + end - expect_any_instance_of(Form526Submission).to receive(:classify_vagov_contentions).and_call_original - expect(vro_client_mock).to receive(:classify_vagov_contentions_expanded) - subject.perform_async(submission.id) - described_class.drain - end + it 'sends form526 to the MAS endpoint successfully' do + subject.perform_async(submission.id) + described_class.drain + expect(Form526JobStatus.last.status).to eq 'success' + rrd_submission = Form526Submission.find(Form526JobStatus.last.form526_submission_id) + expect(rrd_submission.form.dig('rrd_metadata', 'mas_packetId')).to eq '12345' + expect(StatsD).to have_received(:increment) + .with('worker.rapid_ready_for_decision.notify_mas.success').once + end - it 'uses expanded classification to classify contentions' do - subject.perform_async(submission.id) - expect do - VCR.use_cassette('virtual_regional_office/expanded_classification') do + it 'sends an email for tracking purposes' do + subject.perform_async(submission.id) described_class.drain + expect(ActionMailer::Base.deliveries.last.subject).to eq 'MA claim - 6847' end - end.not_to change(Sidekiq::Form526BackupSubmissionProcess::Submit.jobs, :size) - submission.reload - classification_codes = submission.form['form526']['form526']['disabilities'].pluck('classificationCode') - expect(classification_codes).to eq([9012, 8994, nil, 8997]) - end - end + context 'when MAS endpoint handshake fails' do + let(:mas_cassette) { 'mail_automation/mas_initiate_apcas_request_failure' } - context 'when the disabilities array is empty' do - before do - allow(Rails.logger).to receive(:info) - end + it 'handles MAS endpoint handshake failure by sending failure notification' do + subject.perform_async(submission.id) + described_class.drain + expect(ActionMailer::Base.deliveries.last.subject).to eq "Failure: MA claim - #{submitted_claim_id}" + expect(StatsD).to have_received(:increment) + .with('worker.rapid_ready_for_decision.notify_mas.failure').once + end + end - let(:submission) do - create(:form526_submission, - :with_empty_disabilities, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end + context 'MAS-related claim that already includes classification code' do + let(:submission) do + create(:form526_submission, + :mas_diagnostic_code_with_classification, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - it 'returns false to skip classification and continue other jobs' do - subject.perform_async(submission.id) - expect(submission.update_contention_classification_all!).to eq false - expect(Rails.logger).to have_received(:info).with( - "No disabilities found for classification on claim #{submission.id}" - ) - end + it 'already includes classification code and does not modify' do + subject.perform_async(submission.id) + described_class.drain + mas_submission = Form526Submission.find(Form526JobStatus.last.form526_submission_id) + expect(mas_submission.form.dig('form526', 'form526', + 'disabilities').first['classificationCode']).to eq '8935' + end + end - it 'does not call va-gov-claim-classifier' do - subject.perform_async(submission.id) - described_class.drain - expect(submission).not_to receive(:classify_vagov_contentions) - end - end - end + context 'when the rated disability has decision code NOTSVCCON in EVSS' do + let(:rated_disabilities_cassette) do + 'evss/disability_compensation_form/rated_disabilities_with_non_service_connected' + end - context 'with a successful submission job' do - it 'queues a job for submit' do - expect do - subject.perform_async(submission.id) - end.to change(subject.jobs, :size).by(1) - end + it 'skips forwarding to MAS' do + subject.perform_async(submission.id) + described_class.drain + expect(Form526JobStatus.last.status).to eq 'success' + rrd_submission = Form526Submission.find(submission.id) + expect(rrd_submission.form.dig('rrd_metadata', 'mas_packetId')).to be_nil + end + end + end - it 'submits successfully' do - subject.perform_async(submission.id) - expect { described_class.drain }.not_to change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq 'success' - end + context 'with multiple MAS-related diagnostic codes' do + let(:submission) do + create(:form526_submission, + :with_multiple_mas_diagnostic_code, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id) + end - it 'submits successfully without calling classification service' do - subject.perform_async(submission.id) - expect do - VCR.use_cassette('virtual_regional_office/multi_contention_classification') do - described_class.drain + context 'when tracking and APCAS notification are enabled for all claims' do + it 'calls APCAS and sends two emails' do + VCR.use_cassette('mail_automation/mas_initiate_apcas_request') do + subject.perform_async(submission.id) + end + described_class.drain + expect(ActionMailer::Base.deliveries.length).to eq 2 + end + end end - end.not_to change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq 'success' - end - it 'does not call contention classification endpoint' do - subject.perform_async(submission.id) - expect(submission).not_to receive(:classify_vagov_contentions) - described_class.drain - end + context 'with Lighthouse as submission provider' do + let(:submission) do + create(:form526_submission, + :with_everything, + user_uuid: user.uuid, + auth_headers_json: auth_headers.to_json, + saved_claim_id: saved_claim.id, + submit_endpoint: 'claims_api') + end - context 'with an MAS-related diagnostic code' do - let(:submission) do - create(:form526_submission, - :non_rrd_with_mas_diagnostic_code, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - let(:mas_cassette) { 'mail_automation/mas_initiate_apcas_request' } - let(:cassettes) do - [open_claims_cassette, rated_disabilities_cassette, submit_form_cassette, mas_cassette] - end + let(:headers) { { 'content-type' => 'application/json' } } - before do - allow(StatsD).to receive(:increment) - end + before do + allow_any_instance_of(Auth::ClientCredentials::Service).to receive(:get_token) + .and_return('fake_access_token') + end - it 'sends form526 to the MAS endpoint successfully' do - subject.perform_async(submission.id) - described_class.drain - expect(Form526JobStatus.last.status).to eq 'success' - rrd_submission = Form526Submission.find(Form526JobStatus.last.form526_submission_id) - expect(rrd_submission.form.dig('rrd_metadata', 'mas_packetId')).to eq '12345' - expect(StatsD).to have_received(:increment).with('worker.rapid_ready_for_decision.notify_mas.success').once - end + it 'performs a successful submission' do + subject.perform_async(submission.id) + expect { described_class.drain }.not_to change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:success] + submission.reload + expect(submission.submitted_claim_id).to eq(Form526JobStatus.last.submission.submitted_claim_id) + end - it 'sends an email for tracking purposes' do - subject.perform_async(submission.id) - described_class.drain - expect(ActionMailer::Base.deliveries.last.subject).to eq 'MA claim - 6847' - end + it 'retries UpstreamUnprocessableEntity errors' do + body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', + 'detail' => 'The claim failed to establish' }] } + + allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) + .and_raise(Faraday::UnprocessableEntityError.new( + body:, + status: 422, + headers: + )) + expect_retryable_error(Common::Exceptions::UpstreamUnprocessableEntity) + end - context 'when MAS endpoint handshake fails' do - let(:mas_cassette) { 'mail_automation/mas_initiate_apcas_request_failure' } + it 'does not retry UnprocessableEntity errors with "pointer" defined' do + body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', + 'detail' => 'The claim failed to establish', + 'source' => { 'pointer' => 'data/attributes/' } }] } + allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) + .and_raise(Faraday::UnprocessableEntityError.new( + body:, status: 422, headers: + )) + expect_non_retryable_error + end - it 'handles MAS endpoint handshake failure by sending failure notification' do - subject.perform_async(submission.id) - described_class.drain - expect(ActionMailer::Base.deliveries.last.subject).to eq "Failure: MA claim - #{submitted_claim_id}" - expect(StatsD).to have_received(:increment).with('worker.rapid_ready_for_decision.notify_mas.failure').once + it 'does not retry UnprocessableEntity errors with "retries will fail" in detail message' do + body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', + 'detail' => 'The claim failed to establish. rEtries WilL fAiL.' }] } + allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) + .and_raise(Faraday::UnprocessableEntityError.new( + body:, status: 422, headers: + )) + expect_non_retryable_error + end + + Lighthouse::ServiceException::ERROR_MAP.slice(429, 499, 500, 501, 502, 503).each do |status, error_class| + it "throws a #{status} error if Lighthouse sends it back" do + allow_any_instance_of(Form526Submission).to receive(:prepare_for_evss!).and_return(nil) + allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) + .and_raise(error_class.new(status:)) + expect_retryable_error(error_class) + end + + it "throws a #{status} error if Lighthouse sends it back for rated disabilities" do + allow_any_instance_of(Flipper) + .to(receive(:enabled?)) + .with('disability_compensation_lighthouse_rated_disabilities_provider_background', anything) + .and_return(true) + allow_any_instance_of(Flipper) + .to(receive(:enabled?)) + .with(:validate_saved_claims_with_json_schemer) + .and_return(false) + allow_any_instance_of(EVSS::DisabilityCompensationForm::SubmitForm526) + .to(receive(:fail_submission_feature_enabled?)) + .and_return(false) + allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:prepare_for_ep_merge!) + .and_return(nil) + allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:pending_eps?) + .and_return(false) + allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:classify_vagov_contentions) + .and_return(nil) + allow_any_instance_of(VeteranVerification::Service).to receive(:get_rated_disabilities) + .and_raise(error_class.new(status:)) + expect_retryable_error(error_class) + end + end end end - context 'MAS-related claim that already includes classification code' do + context 'with non-MAS-related diagnostic code' do let(:submission) do create(:form526_submission, - :mas_diagnostic_code_with_classification, + :with_uploads, user_uuid: user.uuid, auth_headers_json: auth_headers.to_json, saved_claim_id: saved_claim.id) end - it 'already includes classification code and does not modify' do + it 'does not set a classification code for irrelevant claims' do subject.perform_async(submission.id) described_class.drain mas_submission = Form526Submission.find(Form526JobStatus.last.form526_submission_id) @@ -594,275 +756,141 @@ def submit_it end end - context 'when the rated disability has decision code NOTSVCCON in EVSS' do - let(:rated_disabilities_cassette) do - 'evss/disability_compensation_form/rated_disabilities_with_non_service_connected' - end - - it 'skips forwarding to MAS' do + context 'when retrying a job' do + it 'doesnt recreate the job status' do subject.perform_async(submission.id) + + jid = subject.jobs.last['jid'] + values = { + form526_submission_id: submission.id, + job_id: jid, + job_class: subject.class, + status: Form526JobStatus::STATUS[:try], + updated_at: Time.now.utc + } + Form526JobStatus.upsert(values, unique_by: :job_id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to( + receive(:increment_success).with(false, 'evss').once + ) described_class.drain - expect(Form526JobStatus.last.status).to eq 'success' - rrd_submission = Form526Submission.find(submission.id) - expect(rrd_submission.form.dig('rrd_metadata', 'mas_packetId')).to be_nil + job_status = Form526JobStatus.where(job_id: values[:job_id]).first + expect(job_status.status).to eq 'success' + expect(job_status.error_class).to eq nil + expect(job_status.job_class).to eq 'SubmitForm526AllClaim' + expect(Form526JobStatus.count).to eq 1 end end - end - - context 'with multiple MAS-related diagnostic codes' do - let(:submission) do - create(:form526_submission, - :with_multiple_mas_diagnostic_code, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - context 'when tracking and APCAS notification are enabled for all claims' do - it 'calls APCAS and sends two emails' do - VCR.use_cassette('mail_automation/mas_initiate_apcas_request') do + context 'with an upstream service error for EP code not valid' do + it 'sets the transaction to "non_retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_200_with_ep_not_valid') do subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics) + .to receive(:increment_non_retryable).once + expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) end - described_class.drain - expect(ActionMailer::Base.deliveries.length).to eq 2 end end - end - - context 'with Lighthouse as submission provider' do - let(:submission) do - create(:form526_submission, - :with_everything, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id, - submit_endpoint: 'claims_api') - end - - let(:headers) { { 'content-type' => 'application/json' } } - - before do - allow_any_instance_of(Auth::ClientCredentials::Service).to receive(:get_token).and_return('fake_access_token') - end - - it 'performs a successful submission' do - subject.perform_async(submission.id) - expect { described_class.drain }.not_to change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:success] - submission.reload - expect(submission.submitted_claim_id).to eq(Form526JobStatus.last.submission.submitted_claim_id) - end - it 'retries UpstreamUnprocessableEntity errors' do - body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', - 'detail' => 'The claim failed to establish' }] } - - allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) - .and_raise(Faraday::UnprocessableEntityError.new( - body:, - status: 422, - headers: - )) - expect_retryable_error(Common::Exceptions::UpstreamUnprocessableEntity) - end - - it 'does not retry UnprocessableEntity errors with "pointer" defined' do - body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', - 'detail' => 'The claim failed to establish', - 'source' => { 'pointer' => 'data/attributes/' } }] } - allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) - .and_raise(Faraday::UnprocessableEntityError.new( - body:, status: 422, headers: - )) - expect_non_retryable_error - end - - it 'does not retry UnprocessableEntity errors with "retries will fail" in detail message' do - body = { 'errors' => [{ 'status' => '422', 'title' => 'Backend Service Exception', - 'detail' => 'The claim failed to establish. rEtries WilL fAiL.' }] } - allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) - .and_raise(Faraday::UnprocessableEntityError.new( - body:, status: 422, headers: - )) - expect_non_retryable_error - end - - Lighthouse::ServiceException::ERROR_MAP.slice(429, 499, 500, 501, 502, 503).each do |status, error_class| - it "throws a #{status} error if Lighthouse sends it back" do - allow_any_instance_of(Form526Submission).to receive(:prepare_for_evss!).and_return(nil) - allow_any_instance_of(BenefitsClaims::Service).to receive(:prepare_submission_body) - .and_raise(error_class.new(status:)) - expect_retryable_error(error_class) - end - - it "throws a #{status} error if Lighthouse sends it back for rated disabilities" do - allow_any_instance_of(Flipper) - .to(receive(:enabled?)) - .with('disability_compensation_lighthouse_rated_disabilities_provider_background', anything) - .and_return(true) - allow_any_instance_of(EVSS::DisabilityCompensationForm::SubmitForm526) - .to(receive(:fail_submission_feature_enabled?)) - .and_return(false) - allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:prepare_for_ep_merge!).and_return(nil) - allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:pending_eps?).and_return(false) - allow_any_instance_of(Form526ClaimFastTrackingConcern).to receive(:classify_vagov_contentions) - .and_return(nil) - allow_any_instance_of(VeteranVerification::Service).to receive(:get_rated_disabilities) - .and_raise(error_class.new(status:)) - expect_retryable_error(error_class) + context 'with a max ep code server error' do + it 'sets the transaction to "non_retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_500_with_max_ep_code') do + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics) + .to receive(:increment_non_retryable).once + expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) + end end end - end - end - context 'with non-MAS-related diagnostic code' do - let(:submission) do - create(:form526_submission, - :with_uploads, - user_uuid: user.uuid, - auth_headers_json: auth_headers.to_json, - saved_claim_id: saved_claim.id) - end - - it 'does not set a classification code for irrelevant claims' do - subject.perform_async(submission.id) - described_class.drain - mas_submission = Form526Submission.find(Form526JobStatus.last.form526_submission_id) - expect(mas_submission.form.dig('form526', 'form526', - 'disabilities').first['classificationCode']).to eq '8935' - end - end - - context 'when retrying a job' do - it 'doesnt recreate the job status' do - subject.perform_async(submission.id) - - jid = subject.jobs.last['jid'] - values = { - form526_submission_id: submission.id, - job_id: jid, - job_class: subject.class, - status: Form526JobStatus::STATUS[:try], - updated_at: Time.now.utc - } - Form526JobStatus.upsert(values, unique_by: :job_id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to( - receive(:increment_success).with(false, 'evss').once - ) - described_class.drain - job_status = Form526JobStatus.where(job_id: values[:job_id]).first - expect(job_status.status).to eq 'success' - expect(job_status.error_class).to eq nil - expect(job_status.job_class).to eq 'SubmitForm526AllClaim' - expect(Form526JobStatus.count).to eq 1 - end - end - - context 'with an upstream service error for EP code not valid' do - it 'sets the transaction to "non_retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_200_with_ep_not_valid') do - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_non_retryable).once - expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] - expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) + context 'with a unused [418] error' do + it 'sets the transaction to "retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_200_with_418') do + backup_jobs_count = backup_klass.jobs.count + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once + expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) + .and not_change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] + expect(backup_klass.jobs.count).to eq(backup_jobs_count) + end + end end - end - end - context 'with a max ep code server error' do - it 'sets the transaction to "non_retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_500_with_max_ep_code') do - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_non_retryable).once - expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] - expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) + context 'with a BGS error' do + it 'sets the transaction to "retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_200_with_bgs_error') do + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once + expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) + .and not_change(backup_klass.jobs, :size) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] + end + end end - end - end - context 'with a unused [418] error' do - it 'sets the transaction to "retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_200_with_418') do - backup_jobs_count = backup_klass.jobs.count - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once - expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) - .and not_change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] - expect(backup_klass.jobs.count).to eq(backup_jobs_count) + context 'with a pif in use server error' do + it 'sets the transaction to "non_retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_500_with_pif_in_use') do + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics) + .to receive(:increment_non_retryable).once + expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) + end + end end - end - end - context 'with a BGS error' do - it 'sets the transaction to "retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_200_with_bgs_error') do - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once - expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) - .and not_change(backup_klass.jobs, :size) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] + context 'with a VeteranRecordWsClientException java error' do + it 'sets the transaction to "retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_500_with_java_ws_error') do + subject.perform_async(submission.id) + expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once + expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] + end + end end - end - end - context 'with a pif in use server error' do - it 'sets the transaction to "non_retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_500_with_pif_in_use') do - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_non_retryable).once - expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] - expect(backup_klass.jobs.last['class']).to eq(backup_klass.to_s) + context 'with an error that is not mapped' do + it 'sets the transaction to "non_retryable_error"' do + VCR.use_cassette('evss/disability_compensation_form/submit_500_with_unmapped') do + subject.perform_async(submission.id) + expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + end + end end - end - end - context 'with a VeteranRecordWsClientException java error' do - it 'sets the transaction to "retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_500_with_java_ws_error') do - subject.perform_async(submission.id) - expect_any_instance_of(Sidekiq::Form526JobStatusTracker::Metrics).to receive(:increment_retryable).once - expect { described_class.drain }.to raise_error(EVSS::DisabilityCompensationForm::ServiceException) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:retryable_error] - end - end - end + context 'with an unexpected error' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) + end - context 'with an error that is not mapped' do - it 'sets the transaction to "non_retryable_error"' do - VCR.use_cassette('evss/disability_compensation_form/submit_500_with_unmapped') do - subject.perform_async(submission.id) - expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + it 'sets the transaction to "non_retryable_error"' do + subject.perform_async(submission.id) + expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + end end - end - end - - context 'with an unexpected error' do - before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) - end - it 'sets the transaction to "non_retryable_error"' do - subject.perform_async(submission.id) - expect { described_class.drain }.to change(backup_klass.jobs, :size).by(1) - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] - end - end - - context 'with an RRD claim' do - context 'with a non-retryable (unexpected) error' do - before do - allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) - end + context 'with an RRD claim' do + context 'with a non-retryable (unexpected) error' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(StandardError.new('foo')) + end - it 'sends a "non-retryable" RRD alert' do - subject.perform_async(submission.id) - described_class.drain - expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + it 'sends a "non-retryable" RRD alert' do + subject.perform_async(submission.id) + described_class.drain + expect(Form526JobStatus.last.status).to eq Form526JobStatus::STATUS[:non_retryable_error] + end + end end end end diff --git a/spec/sidekiq/form526_failure_state_snapshot_job_spec.rb b/spec/sidekiq/form526_failure_state_snapshot_job_spec.rb index 6ebb7e00642..b60e5b1aed6 100644 --- a/spec/sidekiq/form526_failure_state_snapshot_job_spec.rb +++ b/spec/sidekiq/form526_failure_state_snapshot_job_spec.rb @@ -12,225 +12,234 @@ let!(:end_date) { Time.zone.today.beginning_of_day } let!(:start_date) { end_date - 1.week } - describe '526 state logging' do - let!(:new_unprocessed) do - Timecop.freeze(modern_times) do - create(:form526_submission) + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - end - let!(:old_unprocessed) do - Timecop.freeze(olden_times) do - create(:form526_submission) - end - end - let!(:new_primary_success) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_submitted_claim_id, :with_one_succesful_job) - end - end - let!(:old_primary_success) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_submitted_claim_id, :with_one_succesful_job) - end - end - let!(:new_backup_pending) do - Timecop.freeze(modern_times) do - create(:form526_submission, :backup_path, :with_failed_primary_job) - end - end - let!(:old_backup_pending) do - Timecop.freeze(olden_times) do - create(:form526_submission, :backup_path, :with_failed_primary_job) - end - end - let!(:new_backup_success) do - Timecop.freeze(modern_times) do - create(:form526_submission, :backup_path, :paranoid_success, :with_failed_primary_job) - end - end - let!(:old_backup_success) do - Timecop.freeze(olden_times) do - create(:form526_submission, :backup_path, :paranoid_success, :with_failed_primary_job) - end - end - let!(:new_backup_vbms) do - Timecop.freeze(modern_times) do - create(:form526_submission, :backup_path, :backup_accepted, :with_failed_primary_job) - end - end - let!(:old_backup_vbms) do - Timecop.freeze(olden_times) do - create(:form526_submission, :backup_path, :backup_accepted, :with_failed_primary_job) - end - end - let!(:new_backup_rejected) do - Timecop.freeze(modern_times) do - create(:form526_submission, :backup_path, :backup_rejected, :with_failed_primary_job) - end - end - let!(:old_backup_rejected) do - Timecop.freeze(olden_times) do - create(:form526_submission, :backup_path, :backup_rejected, :with_failed_primary_job) - end - end - let!(:new_double_job_failure) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job) - end - end - let!(:old_double_job_failure) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job) - end - end - let!(:new_double_job_failure_remediated) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :remediated) - end - end - let!(:old_double_job_failure_remediated) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :remediated) - end - end - let!(:new_double_job_failure_de_remediated) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :no_longer_remediated) - end - end - let!(:old_double_job_failure_de_remediated) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :no_longer_remediated) - end - end - let!(:new_no_job_remediated) do - Timecop.freeze(modern_times) do - create(:form526_submission, :remediated) - end - end - let!(:old_no_job_remediated) do - Timecop.freeze(olden_times) do - create(:form526_submission, :remediated) - end - end - let!(:new_backup_paranoid) do - Timecop.freeze(modern_times) do - create(:form526_submission, :backup_path, :with_failed_primary_job, :paranoid_success) - end - end - let!(:old_backup_paranoid) do - Timecop.freeze(olden_times) do - create(:form526_submission, :backup_path, :with_failed_primary_job, :paranoid_success) - end - end - let!(:still_running_with_retryable_errors) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_one_failed_job) - end - end - # RARE EDGECASES - let!(:new_no_job_de_remediated) do - Timecop.freeze(modern_times) do - create(:form526_submission, :no_longer_remediated) - end - end - let!(:old_no_job_de_remediated) do - Timecop.freeze(olden_times) do - create(:form526_submission, :no_longer_remediated) - end - end - let!(:new_double_success) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path) - end - end - let!(:old_double_success) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path) - end - end - let!(:new_triple_success) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path, :remediated) - end - end - let!(:old_triple_success) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path, :remediated) - end - end - let!(:new_double_success_de_remediated) do - Timecop.freeze(modern_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path, :no_longer_remediated) - end - end - let!(:old_double_success_de_remediated) do - Timecop.freeze(olden_times) do - create(:form526_submission, :with_submitted_claim_id, :backup_path, :no_longer_remediated) - end - end - let!(:new_remediated_and_de_remediated) do - sub = Timecop.freeze(modern_times) do - create(:form526_submission, :remediated) - end - Timecop.freeze(modern_times + 1.hour) do - create(:form526_submission_remediation, - form526_submission: sub, - lifecycle: ['i am no longer remediated'], - success: false) - end - sub - end - let!(:old_remediated_and_de_remediated) do - sub = Timecop.freeze(olden_times) do - create(:form526_submission, :remediated) - end - Timecop.freeze(olden_times + 1.hour) do - create(:form526_submission_remediation, - form526_submission: sub, - lifecycle: ['i am no longer remediated'], - success: false) - end - sub - end - it 'logs 526 state metrics correctly' do - expected_log = { - total_awaiting_backup_status: [ - new_backup_pending.id - ].sort, - total_incomplete_type: [ - still_running_with_retryable_errors.id, - new_unprocessed.id, - new_backup_pending.id, - new_no_job_de_remediated.id, - new_remediated_and_de_remediated.id - ].sort, - total_failure_type: [ - old_unprocessed.id, - old_backup_pending.id, - new_backup_rejected.id, - old_backup_rejected.id, - old_double_job_failure.id, - old_double_job_failure_de_remediated.id, - old_no_job_de_remediated.id, - old_remediated_and_de_remediated.id, - new_double_job_failure.id, - new_double_job_failure_de_remediated.id - ].sort - } + describe '526 state logging' do + let!(:new_unprocessed) do + Timecop.freeze(modern_times) do + create(:form526_submission) + end + end + let!(:old_unprocessed) do + Timecop.freeze(olden_times) do + create(:form526_submission) + end + end + let!(:new_primary_success) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_submitted_claim_id, :with_one_succesful_job) + end + end + let!(:old_primary_success) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_submitted_claim_id, :with_one_succesful_job) + end + end + let!(:new_backup_pending) do + Timecop.freeze(modern_times) do + create(:form526_submission, :backup_path, :with_failed_primary_job) + end + end + let!(:old_backup_pending) do + Timecop.freeze(olden_times) do + create(:form526_submission, :backup_path, :with_failed_primary_job) + end + end + let!(:new_backup_success) do + Timecop.freeze(modern_times) do + create(:form526_submission, :backup_path, :paranoid_success, :with_failed_primary_job) + end + end + let!(:old_backup_success) do + Timecop.freeze(olden_times) do + create(:form526_submission, :backup_path, :paranoid_success, :with_failed_primary_job) + end + end + let!(:new_backup_vbms) do + Timecop.freeze(modern_times) do + create(:form526_submission, :backup_path, :backup_accepted, :with_failed_primary_job) + end + end + let!(:old_backup_vbms) do + Timecop.freeze(olden_times) do + create(:form526_submission, :backup_path, :backup_accepted, :with_failed_primary_job) + end + end + let!(:new_backup_rejected) do + Timecop.freeze(modern_times) do + create(:form526_submission, :backup_path, :backup_rejected, :with_failed_primary_job) + end + end + let!(:old_backup_rejected) do + Timecop.freeze(olden_times) do + create(:form526_submission, :backup_path, :backup_rejected, :with_failed_primary_job) + end + end + let!(:new_double_job_failure) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job) + end + end + let!(:old_double_job_failure) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job) + end + end + let!(:new_double_job_failure_remediated) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :remediated) + end + end + let!(:old_double_job_failure_remediated) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :remediated) + end + end + let!(:new_double_job_failure_de_remediated) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :no_longer_remediated) + end + end + let!(:old_double_job_failure_de_remediated) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_failed_primary_job, :with_failed_backup_job, :no_longer_remediated) + end + end + let!(:new_no_job_remediated) do + Timecop.freeze(modern_times) do + create(:form526_submission, :remediated) + end + end + let!(:old_no_job_remediated) do + Timecop.freeze(olden_times) do + create(:form526_submission, :remediated) + end + end + let!(:new_backup_paranoid) do + Timecop.freeze(modern_times) do + create(:form526_submission, :backup_path, :with_failed_primary_job, :paranoid_success) + end + end + let!(:old_backup_paranoid) do + Timecop.freeze(olden_times) do + create(:form526_submission, :backup_path, :with_failed_primary_job, :paranoid_success) + end + end + let!(:still_running_with_retryable_errors) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_one_failed_job) + end + end + # RARE EDGECASES + let!(:new_no_job_de_remediated) do + Timecop.freeze(modern_times) do + create(:form526_submission, :no_longer_remediated) + end + end + let!(:old_no_job_de_remediated) do + Timecop.freeze(olden_times) do + create(:form526_submission, :no_longer_remediated) + end + end + let!(:new_double_success) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path) + end + end + let!(:old_double_success) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path) + end + end + let!(:new_triple_success) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path, :remediated) + end + end + let!(:old_triple_success) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path, :remediated) + end + end + let!(:new_double_success_de_remediated) do + Timecop.freeze(modern_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path, :no_longer_remediated) + end + end + let!(:old_double_success_de_remediated) do + Timecop.freeze(olden_times) do + create(:form526_submission, :with_submitted_claim_id, :backup_path, :no_longer_remediated) + end + end + let!(:new_remediated_and_de_remediated) do + sub = Timecop.freeze(modern_times) do + create(:form526_submission, :remediated) + end + Timecop.freeze(modern_times + 1.hour) do + create(:form526_submission_remediation, + form526_submission: sub, + lifecycle: ['i am no longer remediated'], + success: false) + end + sub + end + let!(:old_remediated_and_de_remediated) do + sub = Timecop.freeze(olden_times) do + create(:form526_submission, :remediated) + end + Timecop.freeze(olden_times + 1.hour) do + create(:form526_submission_remediation, + form526_submission: sub, + lifecycle: ['i am no longer remediated'], + success: false) + end + sub + end - expect(described_class.new.snapshot_state).to eq(expected_log) - end + it 'logs 526 state metrics correctly' do + expected_log = { + total_awaiting_backup_status: [ + new_backup_pending.id + ].sort, + total_incomplete_type: [ + still_running_with_retryable_errors.id, + new_unprocessed.id, + new_backup_pending.id, + new_no_job_de_remediated.id, + new_remediated_and_de_remediated.id + ].sort, + total_failure_type: [ + old_unprocessed.id, + old_backup_pending.id, + new_backup_rejected.id, + old_backup_rejected.id, + old_double_job_failure.id, + old_double_job_failure_de_remediated.id, + old_no_job_de_remediated.id, + old_remediated_and_de_remediated.id, + new_double_job_failure.id, + new_double_job_failure_de_remediated.id + ].sort + } - it 'writes counts as Stats D gauges' do - prefix = described_class::STATSD_PREFIX + expect(described_class.new.snapshot_state).to eq(expected_log) + end - expect(StatsD).to receive(:gauge).with("#{prefix}.total_awaiting_backup_status_count", 1) - expect(StatsD).to receive(:gauge).with("#{prefix}.total_incomplete_type_count", 5) - expect(StatsD).to receive(:gauge).with("#{prefix}.total_failure_type_count", 10) + it 'writes counts as Stats D gauges' do + prefix = described_class::STATSD_PREFIX - described_class.new.perform + expect(StatsD).to receive(:gauge).with("#{prefix}.total_awaiting_backup_status_count", 1) + expect(StatsD).to receive(:gauge).with("#{prefix}.total_incomplete_type_count", 5) + expect(StatsD).to receive(:gauge).with("#{prefix}.total_failure_type_count", 10) + + described_class.new.perform + end + end end end end diff --git a/spec/sidekiq/lighthouse/form526_document_upload_polling_job_spec.rb b/spec/sidekiq/lighthouse/form526_document_upload_polling_job_spec.rb index 7f433d69eaa..239cd645a9e 100644 --- a/spec/sidekiq/lighthouse/form526_document_upload_polling_job_spec.rb +++ b/spec/sidekiq/lighthouse/form526_document_upload_polling_job_spec.rb @@ -18,287 +18,300 @@ allow_any_instance_of(BenefitsDocuments::Configuration).to receive(:access_token).and_return('abcd1234') end - describe '#perform' do - shared_examples 'document status updates' do |state, request_id, cassette| - around { |example| VCR.use_cassette(cassette, match_requests_on: [:body]) { example.run } } - - let!(:document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: request_id) } - - it 'updates document status' do - described_class.new.perform - expect(document.reload.aasm_state).to eq(state) - expect(document.reload.lighthouse_processing_ended_at).not_to be_nil - expect(document.reload.last_status_response).not_to be_nil - end - - it 'saves the status_last_polled_at time' do - polling_time = DateTime.new(1985, 10, 26).utc - Timecop.freeze(polling_time) do - described_class.new.perform - expect(document.reload.status_last_polled_at).to eq(polling_time) - end + [true, false].each do |flipper_value| + context "when json_schemer flipper is #{flipper_value}" do + before do + allow(Flipper).to receive(:enabled?).and_call_original + allow(Flipper).to receive(:enabled?).with(:validate_saved_claims_with_json_schemer).and_return(flipper_value) end - end - - # End-to-end integration test - completion - context 'for a document that has completed' do - # Completed Lighthouse QA environment document requestId provided by Lighthouse for end-to-end testing - it_behaves_like 'document status updates', 'completed', '22', - 'lighthouse/benefits_claims/documents/form526_document_upload_status_complete' - end - - context 'for a document that has failed' do - # Failed Lighthouse QA environment document requestId provided by Lighthouse for end-to-end testing - it_behaves_like 'document status updates', 'failed', '16819', - 'lighthouse/benefits_claims/documents/form526_document_upload_status_failed' - end - context 'for a single document request whose status is not found' do - # Non-existent Lighthouse QA environment document requestId - let!(:unknown_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '21') } - let(:error_body) do - { 'errors' => [{ 'detail' => 'Upload Request Async Status Not Found', 'status' => 404, - 'title' => 'Not Found', 'instance' => '062dd917-a229-42d7-ad39-741eb81766a8', - 'diagnostics' => '7YODuWbVvC0k+iFgaQC0SrlARmYKPKz4' }] } - end + describe '#perform' do + shared_examples 'document status updates' do |state, request_id, cassette| + around { |example| VCR.use_cassette(cassette, match_requests_on: [:body]) { example.run } } - around do |example| - VCR.use_cassette('lighthouse/benefits_claims/documents/form526_document_upload_status_not_found', - match_requests_on: [:body]) do - example.run - end - end + let!(:document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: request_id) } - it 'increments a StatsD counter and logs error' do - expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.polling_error') + it 'updates document status' do + described_class.new.perform + expect(document.reload.aasm_state).to eq(state) + expect(document.reload.lighthouse_processing_ended_at).not_to be_nil + expect(document.reload.last_status_response).not_to be_nil + end - Timecop.freeze(Time.new(1985, 10, 26).utc) do - expect(Rails.logger).to receive(:warn).with( - 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', - hash_including(response_status: 404, response_body: error_body, - lighthouse_document_request_ids: [unknown_document.lighthouse_document_request_id]) - ) - described_class.new.perform + it 'saves the status_last_polled_at time' do + polling_time = DateTime.new(1985, 10, 26).utc + Timecop.freeze(polling_time) do + described_class.new.perform + expect(document.reload.status_last_polled_at).to eq(polling_time) + end + end end - end - end - - context 'for a document with status and another document whose request id is not found' do - let!(:complete_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '22') } - let!(:unknown_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '21') } - around do |example| - VCR.use_cassette('lighthouse/benefits_claims/documents/form526_document_upload_with_request_ids_not_found', - match_requests_on: [:body]) do - example.run + # End-to-end integration test - completion + context 'for a document that has completed' do + # Completed Lighthouse QA environment document requestId provided by Lighthouse for end-to-end testing + it_behaves_like 'document status updates', 'completed', '22', + 'lighthouse/benefits_claims/documents/form526_document_upload_status_complete' end - end - it 'increments StatsD counters for both documents and logs unknown document error' do - expect(StatsD).to receive(:increment) - .with('api.form526.lighthouse_document_upload_processing_status.bdd_instructions.complete').ordered - expect(StatsD).to receive(:increment) - .with('worker.lighthouse.poll_form526_document_uploads.polling_error').ordered - - Timecop.freeze(Time.new(1985, 10, 26).utc) do - expect(Rails.logger).to receive(:warn).with( - 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', - hash_including(response_status: 404, response_body: 'Upload Request Async Status Not Found', - lighthouse_document_request_ids: [unknown_document.lighthouse_document_request_id]) - ) - described_class.new.perform + context 'for a document that has failed' do + # Failed Lighthouse QA environment document requestId provided by Lighthouse for end-to-end testing + it_behaves_like 'document status updates', 'failed', '16819', + 'lighthouse/benefits_claims/documents/form526_document_upload_status_failed' end - end - end - - context 'non-200 failure response from Lighthouse' do - let!(:pending_document) { create(:lighthouse526_document_upload) } - # Error body example from: https://dev-developer.va.gov/explore/api/benefits-documents/docs?version=current - let(:error_body) { { 'errors' => [{ 'detail' => 'Code must match \'^[A-Z]{2}$\'', 'status' => 400 }] } } - let(:error_response) { Faraday::Response.new(response_body: error_body, status: 400) } - before do - allow(BenefitsDocuments::Form526::DocumentsStatusPollingService).to receive(:call).and_return(error_response) - end - - it 'increments a StatsD counter and logs error' do - expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.polling_error') - - Timecop.freeze(Time.new(1985, 10, 26).utc) do - expect(Rails.logger).to receive(:warn).with( - 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', - hash_including(response_status: 400, response_body: error_body, - lighthouse_document_request_ids: [pending_document.lighthouse_document_request_id]) - ) - described_class.new.perform - end - end - end + context 'for a single document request whose status is not found' do + # Non-existent Lighthouse QA environment document requestId + let!(:unknown_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '21') } + let(:error_body) do + { 'errors' => [{ 'detail' => 'Upload Request Async Status Not Found', 'status' => 404, + 'title' => 'Not Found', 'instance' => '062dd917-a229-42d7-ad39-741eb81766a8', + 'diagnostics' => '7YODuWbVvC0k+iFgaQC0SrlARmYKPKz4' }] } + end - context 'retries exhausted' do - it 'updates the exhaustion StatsD counter' do - described_class.within_sidekiq_retries_exhausted_block do - expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.exhausted') - end - end + around do |example| + VCR.use_cassette('lighthouse/benefits_claims/documents/form526_document_upload_status_not_found', + match_requests_on: [:body]) do + example.run + end + end - it 'logs exhaustion metadata to the Rails logger' do - exhaustion_time = DateTime.new(1985, 10, 26).utc - sidekiq_exhaustion_metadata = { 'jid' => 8_675_309, 'error_class' => 'BROKESKI', - 'error_message' => 'We are going to need a bigger boat' } - Timecop.freeze(exhaustion_time) do - described_class.within_sidekiq_retries_exhausted_block(sidekiq_exhaustion_metadata) do - expect(Rails.logger).to receive(:warn).with( - 'Lighthouse::Form526DocumentUploadPollingJob retries exhausted', - { - job_id: 8_675_309, - error_class: 'BROKESKI', - error_message: 'We are going to need a bigger boat', - timestamp: exhaustion_time - } - ) + it 'increments a StatsD counter and logs error' do + expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.polling_error') + + Timecop.freeze(Time.new(1985, 10, 26).utc) do + expect(Rails.logger).to receive(:warn).with( + 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', + hash_including(response_status: 404, response_body: error_body, + lighthouse_document_request_ids: [unknown_document.lighthouse_document_request_id]) + ) + described_class.new.perform + end end end - end - end - - describe 'Documents Polling' do - let(:faraday_response) { instance_double(Faraday::Response, body: {}, status: 200) } - let(:polling_service) { BenefitsDocuments::Form526::DocumentsStatusPollingService } - let(:polling_time) { DateTime.new(1985, 10, 26).utc } - - before do - # Verifies correct info is being passed to both services - allow(BenefitsDocuments::Form526::DocumentsStatusPollingService).to receive(:call).and_return(faraday_response) - allow(BenefitsDocuments::Form526::UpdateDocumentsStatusService) - .to receive(:call).and_return(success: true, response: { status: 200 }) - end - context 'for a pending document' do - around { |example| Timecop.freeze(polling_time) { example.run } } + context 'for a document with status and another document whose request id is not found' do + let!(:complete_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '22') } + let!(:unknown_document) { create(:lighthouse526_document_upload, lighthouse_document_request_id: '21') } - it 'polls for unpolled and repoll documents' do - documents = [ - create(:lighthouse526_document_upload), - create(:lighthouse526_document_upload, status_last_polled_at: polling_time - 2.hours) - ] - document_request_ids = documents.map(&:lighthouse_document_request_id) + around do |example| + VCR.use_cassette('lighthouse/benefits_claims/documents/form526_document_upload_with_request_ids_not_found', + match_requests_on: [:body]) do + example.run + end + end - expect(polling_service).to receive(:call).with(document_request_ids) - described_class.new.perform + it 'increments StatsD counters for both documents and logs unknown document error' do + expect(StatsD).to receive(:increment) + .with('api.form526.lighthouse_document_upload_processing_status.bdd_instructions.complete').ordered + expect(StatsD).to receive(:increment) + .with('worker.lighthouse.poll_form526_document_uploads.polling_error').ordered + + Timecop.freeze(Time.new(1985, 10, 26).utc) do + expect(Rails.logger).to receive(:warn).with( + 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', + hash_including(response_status: 404, response_body: 'Upload Request Async Status Not Found', + lighthouse_document_request_ids: [unknown_document.lighthouse_document_request_id]) + ) + described_class.new.perform + end + end end - it 'does not poll for recently polled documents' do - recently_polled_document = create(:lighthouse526_document_upload, - status_last_polled_at: polling_time - 42.minutes) - expect(polling_service).not_to receive(:call).with([recently_polled_document.lighthouse_document_request_id]) - described_class.new.perform - end - end + context 'non-200 failure response from Lighthouse' do + let!(:pending_document) { create(:lighthouse526_document_upload) } + # Error body example from: https://dev-developer.va.gov/explore/api/benefits-documents/docs?version=current + let(:error_body) { { 'errors' => [{ 'detail' => 'Code must match \'^[A-Z]{2}$\'', 'status' => 400 }] } } + let(:error_response) { Faraday::Response.new(response_body: error_body, status: 400) } - context 'for completed and failed documents' do - let!(:documents) do - [ - create(:lighthouse526_document_upload, aasm_state: 'completed', - status_last_polled_at: polling_time - 2.hours), - create(:lighthouse526_document_upload, aasm_state: 'failed', status_last_polled_at: polling_time - 2.hours) - ] - end + before do + allow(BenefitsDocuments::Form526::DocumentsStatusPollingService) + .to receive(:call).and_return(error_response) + end - it 'does not poll for completed or failed documents' do - documents.each do |doc| - expect(polling_service).not_to receive(:call).with([doc.lighthouse_document_request_id]) + it 'increments a StatsD counter and logs error' do + expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.polling_error') + + Timecop.freeze(Time.new(1985, 10, 26).utc) do + expect(Rails.logger).to receive(:warn).with( + 'Lighthouse::Form526DocumentUploadPollingJob status endpoint error', + hash_including(response_status: 400, response_body: error_body, + lighthouse_document_request_ids: [pending_document.lighthouse_document_request_id]) + ) + described_class.new.perform + end end - described_class.new.perform end - end - end - describe 'Document Polling Logging' do - context 'for pending documents' do - let!(:pending_polling_documents) { create_list(:lighthouse526_document_upload, 2, aasm_state: 'pending') } - let!(:pending_recently_polled_document) do - create( - :lighthouse526_document_upload, - aasm_state: 'pending', - status_last_polled_at: polling_time - 45.minutes - ) - end + context 'retries exhausted' do + it 'updates the exhaustion StatsD counter' do + described_class.within_sidekiq_retries_exhausted_block do + expect(StatsD).to receive(:increment).with('worker.lighthouse.poll_form526_document_uploads.exhausted') + end + end - let(:polling_time) { DateTime.new(1985, 10, 26).utc } - let(:faraday_response) do - instance_double( - Faraday::Response, - body: { - 'data' => { - 'statuses' => [ + it 'logs exhaustion metadata to the Rails logger' do + exhaustion_time = DateTime.new(1985, 10, 26).utc + sidekiq_exhaustion_metadata = { 'jid' => 8_675_309, 'error_class' => 'BROKESKI', + 'error_message' => 'We are going to need a bigger boat' } + Timecop.freeze(exhaustion_time) do + described_class.within_sidekiq_retries_exhausted_block(sidekiq_exhaustion_metadata) do + expect(Rails.logger).to receive(:warn).with( + 'Lighthouse::Form526DocumentUploadPollingJob retries exhausted', { - 'requestId' => pending_polling_documents.first.lighthouse_document_request_id, - 'time' => { - 'startTime' => 1_502_199_000, - 'endTime' => 1_502_199_000 - }, - 'status' => 'SUCCESS' - }, { - 'requestId' => pending_polling_documents[1].lighthouse_document_request_id, - 'time' => { - 'startTime' => 1_502_199_000, - 'endTime' => 1_502_199_000 - }, - 'status' => 'FAILED', - 'error' => { - 'detail' => 'Something went wrong', - 'step' => 'BENEFITS_GATEWAY_SERVICE' - } + job_id: 8_675_309, + error_class: 'BROKESKI', + error_message: 'We are going to need a bigger boat', + timestamp: exhaustion_time } - ], - 'requestIdsNotFound' => [ - 0 - ] - } - }, - status: 200 - ) - end - - around { |example| Timecop.freeze(polling_time) { example.run } } - - before do - allow(BenefitsDocuments::Form526::DocumentsStatusPollingService) - .to receive(:call).and_return(faraday_response) - - # StatsD will receive multiple gauge calls in this code flow - allow(StatsD).to receive(:gauge) - end - - describe 'polled documents metric' do - it 'increments a StatsD gauge metric with total documents polled, discluding recently polled documents' do - expect(StatsD).to receive(:gauge) - .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_polled', 2) - - described_class.new.perform + ) + end + end end end - describe 'completed and failed documents' do - let!(:existing_completed_documents) do - create_list(:lighthouse526_document_upload, 2, aasm_state: 'completed') + describe 'Documents Polling' do + let(:faraday_response) { instance_double(Faraday::Response, body: {}, status: 200) } + let(:polling_service) { BenefitsDocuments::Form526::DocumentsStatusPollingService } + let(:polling_time) { DateTime.new(1985, 10, 26).utc } + + before do + # Verifies correct info is being passed to both services + allow(BenefitsDocuments::Form526::DocumentsStatusPollingService) + .to receive(:call).and_return(faraday_response) + allow(BenefitsDocuments::Form526::UpdateDocumentsStatusService) + .to receive(:call).and_return(success: true, response: { status: 200 }) end - let!(:existing_failed_documents) { create_list(:lighthouse526_document_upload, 2, aasm_state: 'failed') } + context 'for a pending document' do + around { |example| Timecop.freeze(polling_time) { example.run } } + + it 'polls for unpolled and repoll documents' do + documents = [ + create(:lighthouse526_document_upload), + create(:lighthouse526_document_upload, status_last_polled_at: polling_time - 2.hours) + ] + document_request_ids = documents.map(&:lighthouse_document_request_id) + + expect(polling_service).to receive(:call).with(document_request_ids) + described_class.new.perform + end + + it 'does not poll for recently polled documents' do + recently_polled_document = create(:lighthouse526_document_upload, + status_last_polled_at: polling_time - 42.minutes) + expect(polling_service).not_to receive(:call) + .with([recently_polled_document.lighthouse_document_request_id]) + described_class.new.perform + end + end - it 'increments a StatsD gauge metric with the total number of documents marked complete' do - # Should only count documents newly counted success - expect(StatsD).to receive(:gauge) - .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_marked_completed', 1) - described_class.new.perform + context 'for completed and failed documents' do + let!(:documents) do + [ + create(:lighthouse526_document_upload, aasm_state: 'completed', + status_last_polled_at: polling_time - 2.hours), + create(:lighthouse526_document_upload, aasm_state: 'failed', + status_last_polled_at: polling_time - 2.hours) + ] + end + + it 'does not poll for completed or failed documents' do + documents.each do |doc| + expect(polling_service).not_to receive(:call).with([doc.lighthouse_document_request_id]) + end + described_class.new.perform + end end + end - it 'increments a StatsD gauge metric with the total number of documents marked failed' do - # Should only count documents newly counted failed - expect(StatsD).to receive(:gauge) - .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_marked_failed', 1) - described_class.new.perform + describe 'Document Polling Logging' do + context 'for pending documents' do + let!(:pending_polling_documents) { create_list(:lighthouse526_document_upload, 2, aasm_state: 'pending') } + let!(:pending_recently_polled_document) do + create( + :lighthouse526_document_upload, + aasm_state: 'pending', + status_last_polled_at: polling_time - 45.minutes + ) + end + + let(:polling_time) { DateTime.new(1985, 10, 26).utc } + let(:faraday_response) do + instance_double( + Faraday::Response, + body: { + 'data' => { + 'statuses' => [ + { + 'requestId' => pending_polling_documents.first.lighthouse_document_request_id, + 'time' => { + 'startTime' => 1_502_199_000, + 'endTime' => 1_502_199_000 + }, + 'status' => 'SUCCESS' + }, { + 'requestId' => pending_polling_documents[1].lighthouse_document_request_id, + 'time' => { + 'startTime' => 1_502_199_000, + 'endTime' => 1_502_199_000 + }, + 'status' => 'FAILED', + 'error' => { + 'detail' => 'Something went wrong', + 'step' => 'BENEFITS_GATEWAY_SERVICE' + } + } + ], + 'requestIdsNotFound' => [ + 0 + ] + } + }, + status: 200 + ) + end + + around { |example| Timecop.freeze(polling_time) { example.run } } + + before do + allow(BenefitsDocuments::Form526::DocumentsStatusPollingService) + .to receive(:call).and_return(faraday_response) + + # StatsD will receive multiple gauge calls in this code flow + allow(StatsD).to receive(:gauge) + end + + describe 'polled documents metric' do + it 'increments a StatsD gauge metric with total documents polled, discluding recently polled documents' do + expect(StatsD).to receive(:gauge) + .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_polled', 2) + + described_class.new.perform + end + end + + describe 'completed and failed documents' do + let!(:existing_completed_documents) do + create_list(:lighthouse526_document_upload, 2, aasm_state: 'completed') + end + + let!(:existing_failed_documents) { create_list(:lighthouse526_document_upload, 2, aasm_state: 'failed') } + + it 'increments a StatsD gauge metric with the total number of documents marked complete' do + # Should only count documents newly counted success + expect(StatsD).to receive(:gauge) + .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_marked_completed', 1) + described_class.new.perform + end + + it 'increments a StatsD gauge metric with the total number of documents marked failed' do + # Should only count documents newly counted failed + expect(StatsD).to receive(:gauge) + .with('worker.lighthouse.poll_form526_document_uploads.pending_documents_marked_failed', 1) + described_class.new.perform + end + end end end end