diff --git a/config/routes.rb b/config/routes.rb index 5e99180a72e..7fa97a2cc0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -460,6 +460,7 @@ mount AccreditedRepresentativePortal::Engine, at: '/accredited_representative_portal' mount AskVAApi::Engine, at: '/ask_va_api' mount Avs::Engine, at: '/avs' + mount Burials::Engine, at: '/burials' mount CheckIn::Engine, at: '/check_in' mount DebtsApi::Engine, at: '/debts_api' mount DhpConnectedDevices::Engine, at: '/dhp_connected_devices' diff --git a/modules/burials/app/controllers/burials/v0/claims_controller.rb b/modules/burials/app/controllers/burials/v0/claims_controller.rb new file mode 100644 index 00000000000..85b91864c49 --- /dev/null +++ b/modules/burials/app/controllers/burials/v0/claims_controller.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'pension_burial/tag_sentry' +require 'common/exceptions/validation_errors' + +module Burials + module V0 + class ClaimsController < ApplicationController + skip_before_action(:authenticate) + before_action :load_user, only: :create + + service_tag 'burial-application' + + def show + claim = claim_class.find_by!(guid: params[:id]) + render json: SavedClaimSerializer.new(claim) + rescue ActiveRecord::RecordNotFound => e + monitor.track_show404(params[:id], current_user, e) + render(json: { error: e.to_s }, status: :not_found) + rescue => e + monitor.track_show_error(params[:id], current_user, e) + raise e + end + + def create + PensionBurial::TagSentry.tag_sentry + + claim = claim_class.new(form: filtered_params[:form]) + monitor.track_create_attempt(claim, current_user) + + in_progress_form = current_user ? InProgressForm.form_for_user(claim.form_id, current_user) : nil + claim.form_start_date = in_progress_form.created_at if in_progress_form + + unless claim.save + Sentry.set_tags(team: 'benefits-memorial-1') # tag sentry logs with team name + monitor.track_create_validation_error(in_progress_form, claim, current_user) + log_validation_error_to_metadata(in_progress_form, claim) + raise Common::Exceptions::ValidationErrors, claim.errors + end + + process_and_upload_to_lighthouse(in_progress_form, claim) + + monitor.track_create_success(in_progress_form, claim, current_user) + + clear_saved_form(claim.form_id) + render json: SavedClaimSerializer.new(claim) + rescue => e + monitor.track_create_error(in_progress_form, claim, current_user, e) + raise e + end + + private + + # an identifier that matches the parameter that the form will be set as in the JSON submission. + def short_name + 'burial_claim' + end + + # a subclass of SavedClaim, runs json-schema validations and performs any storage and attachment processing + def claim_class + Burials::SavedClaim + end + + def process_and_upload_to_lighthouse(in_progress_form, claim) + claim.process_attachments! + + Lighthouse::SubmitBenefitsIntakeClaim.perform_async(claim.id) + rescue => e + monitor.track_process_attachment_error(in_progress_form, claim, current_user) + raise e + end + + # Filters out the parameters to form access. + def filtered_params + params.require(short_name.to_sym).permit(:form) + end + + ## + # include validation error on in_progress_form metadata. + # `noop` if in_progress_form is `blank?` + # + # @param in_progress_form [InProgressForm] + # @param claim [Pensions::SavedClaim] + # + def log_validation_error_to_metadata(in_progress_form, claim) + return if in_progress_form.blank? + + metadata = in_progress_form.metadata + metadata['submission']['error_message'] = claim&.errors&.errors&.to_s + in_progress_form.update(metadata:) + end + + ## + # retreive a monitor for tracking + # + # @return [Burials::Monitor] + # + def monitor + @monitor ||= Burials::Monitor.new + end + end + end +end diff --git a/modules/burials/app/models/burials/saved_claim.rb b/modules/burials/app/models/burials/saved_claim.rb new file mode 100644 index 00000000000..45ea0862999 --- /dev/null +++ b/modules/burials/app/models/burials/saved_claim.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'pension_burial/processing_office' + +module Burials + class SavedClaim < ::SavedClaim + FORM = '21P-530EZ' + + def process_attachments! + refs = attachment_keys.map { |key| Array(open_struct_form.send(key)) }.flatten + files = PersistentAttachment.where(guid: refs.map(&:confirmationCode)) + files.find_each { |f| f.update(saved_claim_id: id) } + end + + def regional_office + PensionBurial::ProcessingOffice.address_for(open_struct_form.claimantAddress.postalCode) + end + + def attachment_keys + %i[transportationReceipts deathCertificate militarySeparationDocuments additionalEvidence].freeze + end + + def email + parsed_form['claimantEmail'] + end + + def form_matches_schema + return unless form_is_string + + JSON::Validator.fully_validate(VetsJsonSchema::SCHEMAS[form_id], parsed_form).each do |v| + errors.add(:form, v.to_s) + end + end + + def process_pdf(pdf_path, timestamp = nil, form_id = nil) + processed_pdf = PDFUtilities::DatestampPdf.new(pdf_path).run( + text: 'Application Submitted on va.gov', + x: 400, + y: 675, + text_only: true, # passing as text only because we override how the date is stamped in this instance + timestamp:, + page_number: 6, + template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf", + multistamp: true + ) + renamed_path = "tmp/pdfs/#{form_id}_#{id}_final.pdf" + File.rename(processed_pdf, renamed_path) # rename for vbms upload + renamed_path # return the renamed path + end + + def business_line + 'NCA' + end + + ## + # utility function to retrieve claimant first name from form + # + # @return [String] the claimant first name + # + def veteran_first_name + parsed_form.dig('veteranFullName', 'first') + end + + def veteran_last_name + parsed_form.dig('veteranFullName', 'last') + end + + def claimaint_first_name + parsed_form.dig('claimantFullName', 'first') + end + + def benefits_claimed + claimed = [] + claimed << 'Burial Allowance' if parsed_form['burialAllowance'] + claimed << 'Plot Allowance' if parsed_form['plotAllowance'] + claimed << 'Transportation' if parsed_form['transportation'] + claimed + end + end +end diff --git a/modules/burials/lib/burials/monitor.rb b/modules/burials/lib/burials/monitor.rb new file mode 100644 index 00000000000..ec8ff6f738e --- /dev/null +++ b/modules/burials/lib/burials/monitor.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require 'burials/notification_email' +require 'zero_silent_failures/monitor' + +module Burials + ## + # Monitor functions for Rails logging and StatsD + # + class Monitor < ::ZeroSilentFailures::Monitor + # statsd key for api + CLAIM_STATS_KEY = 'api.burial_claim' + + # statsd key for sidekiq + SUBMISSION_STATS_KEY = 'app.burial.submit_benefits_intake_claim' + + attr_reader :tags + + def initialize + super('burial-application') + + @tags = ['form_id:21P-530EZ'] + end + + ## + # log GET 404 from controller + # @see BurialClaimsController + # + # @param confirmation_number [UUID] saved_claim guid + # @param current_user [User] + # @param e [ActiveRecord::RecordNotFound] + # + def track_show404(confirmation_number, current_user, e) + additional_context = { + confirmation_number:, + user_account_uuid: current_user&.user_account_uuid, + message: e&.message, + tags: + } + track_request('error', '21P-530EZ submission not found', CLAIM_STATS_KEY, + call_location: caller_locations.first, **additional_context) + end + + ## + # log GET 500 from controller + # @see BurialClaimsController + # + # @param confirmation_number [UUID] saved_claim guid + # @param current_user [User] + # @param e [Error] + # + def track_show_error(confirmation_number, current_user, e) + additional_context = { + confirmation_number:, + user_account_uuid: current_user&.user_account_uuid, + message: e&.message, + tags: + } + track_request('error', '21P-530EZ fetching submission failed', CLAIM_STATS_KEY, + call_location: caller_locations.first, **additional_context) + end + + ## + # log POST processing started + # @see BurialClaimsController + # + # @param claim [SavedClaim::Burial] + # @param current_user [User] + # + def track_create_attempt(claim, current_user) + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: current_user&.user_account_uuid, + tags: + } + track_request('info', '21P-530EZ submission to Sidekiq begun', "#{CLAIM_STATS_KEY}.attempt", + call_location: caller_locations.first, **additional_context) + end + + ## + # log POST claim save validation error + # @see BurialClaimsController + # + # @param in_progress_form [InProgressForm] + # @param claim [SavedClaim::Burial] + # @param current_user [User] + # @param e [Error] + # + def track_create_validation_error(in_progress_form, claim, current_user) + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: current_user&.user_account_uuid, + in_progress_form_id: in_progress_form&.id, + errors: claim&.errors&.errors, + tags: + } + track_request('error', '21P-530EZ submission validation error', "#{CLAIM_STATS_KEY}.validation_error", + call_location: caller_locations.first, **additional_context) + end + + ## + # log POST processing failure + # @see BurialClaimsController + # + # @param in_progress_form [InProgressForm] + # @param claim [SavedClaim::Burial] + # @param current_user [User] + # @param e [Error] + # + def track_create_error(in_progress_form, claim, current_user, e = nil) + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: current_user&.user_account_uuid, + in_progress_form_id: in_progress_form&.id, + errors: claim&.errors&.errors, + message: e&.message, + tags: + } + track_request('error', '21P-530EZ submission to Sidekiq failed', "#{CLAIM_STATS_KEY}.failure", + call_location: caller_locations.first, **additional_context) + end + + ## + # log POST processing success + # @see BurialClaimsController + # + # @param in_progress_form [InProgressForm] + # @param claim [SavedClaim::Burial] + # @param current_user [User] + # + def track_create_success(in_progress_form, claim, current_user) + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: current_user&.user_account_uuid, + in_progress_form_id: in_progress_form&.id, + errors: claim&.errors&.errors, + tags: + } + track_request('info', '21P-530EZ submission to Sidekiq success', "#{CLAIM_STATS_KEY}.success", + call_location: caller_locations.first, **additional_context) + end + + ## + # log process_attachments! error + # @see BurialClaimsController + # + # @param in_progress_form [InProgressForm] + # @param claim [SavedClaim::Burial] + # @param current_user [User] + # + def track_process_attachment_error(in_progress_form, claim, current_user) + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: current_user&.user_account_uuid, + in_progress_form_id: in_progress_form&.id, + errors: claim&.errors&.errors, + tags: + } + track_request('error', '21P-530EZ process attachment error', "#{CLAIM_STATS_KEY}.process_attachment_error", + call_location: caller_locations.first, **additional_context) + end + + ## + # log Sidkiq job exhaustion, complete failure after all retries + # @see Lighthouse::SubmitBenefitsIntakeClaim + # + # @param msg [Hash] sidekiq exhaustion response + # @param claim [SavedClaim::Burial] + # + def track_submission_exhaustion(msg, claim = nil) + user_account_uuid = msg['args'].length <= 1 ? nil : msg['args'][1] + additional_context = { + confirmation_number: claim&.confirmation_number, + user_account_uuid: user_account_uuid, + form_id: claim&.form_id, + claim_id: msg['args'].first, + message: msg, + tags: + } + call_location = caller_locations.first + + if claim + Burials::NotificationEmail.new(claim.id).deliver(:error) + log_silent_failure_avoided(additional_context, user_account_uuid, call_location:) + else + log_silent_failure(additional_context, user_account_uuid, call_location:) + end + + track_request('error', 'Lighthouse::SubmitBenefitsIntakeClaim Burial 21P-530EZ submission to LH exhausted!', + "#{SUBMISSION_STATS_KEY}.exhausted", call_location:, **additional_context) + end + end +end diff --git a/modules/burials/spec/controllers/v0/claims_controller_spec.rb b/modules/burials/spec/controllers/v0/claims_controller_spec.rb new file mode 100644 index 00000000000..e42b55331b8 --- /dev/null +++ b/modules/burials/spec/controllers/v0/claims_controller_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'burials/monitor' +require 'support/controller_spec_helper' + +RSpec.describe Burials::V0::ClaimsController, type: :request do + let(:monitor) { double('Burials::Monitor') } + + before do + allow(Burials::Monitor).to receive(:new).and_return(monitor) + allow(monitor).to receive_messages(track_show404: nil, track_show_error: nil, track_create_attempt: nil, + track_create_error: nil, track_create_success: nil, + track_create_validation_error: nil, track_process_attachment_error: nil) + end + + describe 'with a user' do + let(:form) { build(:burials_saved_claim) } + let(:param_name) { :burial_claim } + let(:form_id) { '21P-530EZ' } + let(:user) { create(:user) } + + it 'logs validation errors' do + allow(Burials::SavedClaim).to receive(:new).and_return(form) + allow(form).to receive_messages(save: false, errors: 'mock error') + + expect(monitor).to receive(:track_create_attempt).once + expect(monitor).to receive(:track_create_validation_error).once + expect(monitor).to receive(:track_create_error).once + expect(form).not_to receive(:process_attachments!) + + post '/burials/v0/claims', params: { param_name => { form: form.form } } + + expect(response).to have_http_status(:internal_server_error) + end + + it 'passes successfully' do + allow(Burials::SavedClaim).to receive(:new).and_return(form) + + expect(monitor).to receive(:track_create_attempt).once + expect(monitor).to receive(:track_create_success).once + expect(form).to receive(:process_attachments!) + + post '/burials/v0/claims', params: { param_name => { form: form.form } } + + expect(response).to have_http_status(:success) + end + end + + describe '#show' do + let(:claim) { build(:burials_saved_claim) } + + it 'returns a success when the claim is found' do + allow(Burials::SavedClaim).to receive(:find_by!).and_return(claim) + + get '/burials/v0/claims/:id', params: { id: claim.guid } + + expect(response).to have_http_status(:ok) + end + + it 'returns an error if the claim is not found' do + expect(monitor).to receive(:track_show404).once + + get '/burials/v0/claims/:id', params: { id: 'non-existant-saved-claim' } + + expect(response).to have_http_status(:not_found) + end + + it 'logs show errors' do + error = StandardError.new('Mock Error') + allow(Burials::SavedClaim).to receive(:find_by!).and_raise(error) + + expect(monitor).to receive(:track_show_error).once + + get '/burials/v0/claims/:id', params: { id: 'non-existant-saved-claim' } + + expect(response).to have_http_status(:internal_server_error) + end + end + + describe '#process_and_upload_to_lighthouse' do + let(:claim) { build(:burials_saved_claim) } + let(:in_progress_form) { build(:in_progress_form) } + let(:error) { StandardError.new('Something went wrong') } + + it 'returns a success' do + expect(claim).to receive(:process_attachments!) + + subject.send(:process_and_upload_to_lighthouse, in_progress_form, claim) + end + + it 'returns a failure' do + allow(claim).to receive(:process_attachments!).and_raise(error) + + expect do + subject.send(:process_and_upload_to_lighthouse, in_progress_form, claim) + end.to raise_error(StandardError, 'Something went wrong') + + expect(monitor).to have_received(:track_process_attachment_error).with(in_progress_form, claim, anything) + end + end + + describe '#log_validation_error_to_metadata' do + let(:claim) { build(:burials_saved_claim) } + let(:in_progress_form) { build(:in_progress_form) } + + it 'returns if a `blank` in_progress_form' do + ['', [], {}, nil].each do |blank| + expect(in_progress_form).not_to receive(:update) + result = subject.send(:log_validation_error_to_metadata, blank, claim) + expect(result).to be_nil + end + end + + it 'updates the in_progress_form' do + expect(in_progress_form).to receive(:metadata).and_return(in_progress_form.metadata) + expect(in_progress_form).to receive(:update) + subject.send(:log_validation_error_to_metadata, in_progress_form, claim) + end + end +end diff --git a/modules/burials/spec/factories/saved_claim.rb b/modules/burials/spec/factories/saved_claim.rb new file mode 100644 index 00000000000..ff7532b1302 --- /dev/null +++ b/modules/burials/spec/factories/saved_claim.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :burials_saved_claim, class: 'Burials::SavedClaim' do + form_id { '21P-530EZ' } + form do + { + privacyAgreementAccepted: true, + veteranFullName: { + first: 'WESLEY', + last: 'FORD' + }, + claimantEmail: 'foo@foo.com', + deathDate: '1989-12-13', + veteranDateOfBirth: '1986-05-06', + veteranSocialSecurityNumber: '796043735', + claimantAddress: { + country: 'USA', + state: 'CA', + postalCode: '90210', + street: '123 Main St', + city: 'Anytown' + }, + claimantFullName: { + first: 'Derrick', + middle: 'A', + last: 'Stewart' + }, + burialAllowance: true, + plotAllowance: true, + transportation: true + }.to_json + end + end +end diff --git a/modules/burials/spec/lib/burials/monitor_spec.rb b/modules/burials/spec/lib/burials/monitor_spec.rb new file mode 100644 index 00000000000..71c61002794 --- /dev/null +++ b/modules/burials/spec/lib/burials/monitor_spec.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Burials::Monitor do + let(:monitor) { described_class.new } + let(:claim) { create(:burial_claim) } + let(:ipf) { create(:in_progress_form) } + let(:claim_stats_key) { described_class::CLAIM_STATS_KEY } + let(:submission_stats_key) { described_class::SUBMISSION_STATS_KEY } + + context 'with all params supplied' do + let(:current_user) { create(:user) } + let(:monitor_error) { create(:monitor_error) } + + describe '#track_show404' do + it 'logs a not found error' do + log = '21P-530EZ submission not found' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + message: monitor_error.message, + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'error', + log, + claim_stats_key, + call_location: anything, + **payload + ) + monitor.track_show404(claim.confirmation_number, current_user, monitor_error) + end + end + + describe '#track_show_error' do + it 'logs a submission failed error' do + log = '21P-530EZ fetching submission failed' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + message: monitor_error.message, + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'error', + log, + claim_stats_key, + call_location: anything, + **payload + ) + monitor.track_show_error(claim.confirmation_number, current_user, monitor_error) + end + end + + describe '#track_create_attempt' do + it 'logs sidekiq started' do + log = '21P-530EZ submission to Sidekiq begun' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'info', + log, + "#{claim_stats_key}.attempt", + call_location: anything, + **payload + ) + monitor.track_create_attempt(claim, current_user) + end + end + + describe '#track_create_validation_error' do + it 'logs create failed' do + log = '21P-530EZ submission validation error' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + in_progress_form_id: ipf.id, + errors: [], + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'error', + log, + "#{claim_stats_key}.validation_error", + call_location: anything, + **payload + ) + monitor.track_create_validation_error(ipf, claim, current_user) + end + end + + describe '#track_process_attachment_error' do + it 'logs process attachment failed' do + log = '21P-530EZ process attachment error' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + in_progress_form_id: ipf.id, + errors: [], + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'error', + log, + "#{claim_stats_key}.process_attachment_error", + call_location: anything, + **payload + ) + monitor.track_process_attachment_error(ipf, claim, current_user) + end + end + + describe '#track_create_error' do + it 'logs sidekiq failed' do + log = '21P-530EZ submission to Sidekiq failed' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + in_progress_form_id: ipf.id, + errors: [], + message: monitor_error.message, + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'error', + log, + "#{claim_stats_key}.failure", + call_location: anything, + **payload + ) + monitor.track_create_error(ipf, claim, current_user, monitor_error) + end + end + + describe '#track_create_success' do + it 'logs sidekiq success' do + log = '21P-530EZ submission to Sidekiq success' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.user_account_uuid, + in_progress_form_id: ipf.id, + errors: [], + tags: monitor.tags + } + + expect(monitor).to receive(:track_request).with( + 'info', + log, + "#{claim_stats_key}.success", + call_location: anything, + **payload + ) + monitor.track_create_success(ipf, claim, current_user) + end + end + + describe '#track_submission_exhaustion' do + context 'with a claim parameter' do + it 'logs sidekiq job exhaustion' do + notification = double(Burials::NotificationEmail) + + msg = { 'args' => [claim.id, current_user.uuid] } + + log = 'Lighthouse::SubmitBenefitsIntakeClaim Burial 21P-530EZ submission to LH exhausted!' + payload = { + confirmation_number: claim.confirmation_number, + user_account_uuid: current_user.uuid, + form_id: claim.form_id, + claim_id: claim.id, # pulled from msg.args + message: msg, + tags: monitor.tags + } + + expect(Burials::NotificationEmail).to receive(:new).with(claim.id).and_return notification + expect(notification).to receive(:deliver).with(:error) + expect(monitor).to receive(:log_silent_failure_avoided).with(payload, current_user.uuid, anything) + + expect(monitor).to receive(:track_request).with( + 'error', + log, + "#{submission_stats_key}.exhausted", + call_location: anything, + **payload + ) + + monitor.track_submission_exhaustion(msg, claim) + end + end + + context 'without a claim parameter' do + it 'logs sidekiq job exhaustion' do + msg = { 'args' => [claim.id, current_user.uuid] } + + log = 'Lighthouse::SubmitBenefitsIntakeClaim Burial 21P-530EZ submission to LH exhausted!' + payload = { + confirmation_number: nil, + user_account_uuid: current_user.uuid, + form_id: nil, + claim_id: claim.id, # pulled from msg.args + message: msg, + tags: monitor.tags + } + + expect(Burials::NotificationEmail).not_to receive(:new) + expect(monitor).to receive(:log_silent_failure).with(payload, current_user.uuid, anything) + + expect(monitor).to receive(:track_request).with( + 'error', + log, + "#{submission_stats_key}.exhausted", + call_location: anything, + **payload + ) + + monitor.track_submission_exhaustion(msg, nil) + end + end + end + end +end diff --git a/modules/burials/spec/models/burials/saved_claim_spec.rb b/modules/burials/spec/models/burials/saved_claim_spec.rb new file mode 100644 index 00000000000..6aed1764c78 --- /dev/null +++ b/modules/burials/spec/models/burials/saved_claim_spec.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Burials::SavedClaim do + subject { described_class.new } + + let(:instance) { build(:burials_saved_claim) } + + it 'responds to #confirmation_number' do + expect(subject.confirmation_number).to eq(subject.guid) + end + + it 'has necessary constants' do + expect(described_class).to have_constant(:FORM) + end + + it 'descends from saved_claim' do + expect(described_class.ancestors).to include(SavedClaim) + end + + describe '#process_attachments!' do + it 'does NOT start a job to submit the saved claim via Benefits Intake' do + expect(Lighthouse::SubmitBenefitsIntakeClaim).not_to receive(:perform_async) + instance.process_attachments! + end + end + + context 'a record is processed through v2' do + it 'inherits init callsbacks from saved_claim' do + expect(subject.form_id).to eq('21P-530EZ') + expect(subject.guid).not_to be_nil + expect(subject.type).to eq(described_class.to_s) + end + + context 'validates against the form schema' do + before do + expect(instance.valid?).to be(true) + expect(JSON::Validator).to receive(:fully_validate).once.and_call_original + end + + # NOTE: We assume all forms have the privacyAgreementAccepted element. Obviously. + it 'rejects forms with missing elements' do + bad_form = instance.parsed_form.deep_dup + bad_form.delete('privacyAgreementAccepted') + instance.form = bad_form.to_json + instance.remove_instance_variable(:@parsed_form) + expect(instance.valid?).to be(false) + expect(instance.errors.full_messages.size).to eq(1) + expect(instance.errors.full_messages).to include(/privacyAgreementAccepted/) + end + end + end + + describe '#email' do + it 'returns the users email' do + expect(instance.email).to eq('foo@foo.com') + end + end + + describe '#benefits_claimed' do + it 'returns a full array of values' do + benefits_claimed = instance.benefits_claimed + expected = ['Burial Allowance', 'Plot Allowance', 'Transportation'] + + expect(benefits_claimed.length).to eq(3) + expect(benefits_claimed).to eq(expected) + end + + it 'returns at least an empty array' do + form = instance.parsed_form + + form = form.merge({ 'transportation' => false }) + claim = build(:burials_saved_claim, form: form.to_json) + benefits_claimed = claim.benefits_claimed + expected = ['Burial Allowance', 'Plot Allowance'] + expect(benefits_claimed.length).to eq(2) + expect(benefits_claimed).to eq(expected) + + form = form.merge({ 'plotAllowance' => false }) + claim = build(:burials_saved_claim, form: form.to_json) + benefits_claimed = claim.benefits_claimed + expected = ['Burial Allowance'] + expect(benefits_claimed.length).to eq(1) + expect(benefits_claimed).to eq(expected) + + form = form.merge({ 'burialAllowance' => false }) + claim = build(:burials_saved_claim, form: form.to_json) + benefits_claimed = claim.benefits_claimed + expected = [] + expect(benefits_claimed.length).to eq(0) + expect(benefits_claimed).to eq(expected) + end + end + + describe '#process_pdf' do + let(:pdf_path) { 'path/to/original.pdf' } + let(:timestamp) { '2025-01-14T12:00:00Z' } + let(:form_id) { '21P-530EZ' } + let(:processed_pdf_path) { 'path/to/processed.pdf' } + let(:renamed_path) { "tmp/pdfs/#{form_id}__final.pdf" } + let(:pdf_utilities_instance) { instance_double(PDFUtilities::DatestampPdf) } + + before do + allow(PDFUtilities::DatestampPdf).to receive(:new).with(pdf_path).and_return(pdf_utilities_instance) + allow(pdf_utilities_instance).to receive(:run).and_return(processed_pdf_path) + allow(File).to receive(:rename).with(processed_pdf_path, renamed_path) + end + + it 'processes the PDF and renames the file correctly' do + result = subject.process_pdf(pdf_path, timestamp, form_id) + + expect(PDFUtilities::DatestampPdf).to have_received(:new).with(pdf_path) + expect(pdf_utilities_instance).to have_received(:run).with( + text: 'Application Submitted on va.gov', + x: 400, + y: 675, + text_only: true, + timestamp: timestamp, + page_number: 6, + template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf", + multistamp: true + ) + expect(File).to have_received(:rename).with(processed_pdf_path, renamed_path) + expect(result).to eq(renamed_path) + end + end + + describe '#business_line' do + it 'returns the correct business line' do + expect(subject.business_line).to eq('NCA') + end + end + + describe '#veteran_first_name' do + it 'returns the first name of the veteran from parsed_form' do + allow(instance).to receive(:parsed_form).and_return({ 'veteranFullName' => { 'first' => 'WESLEY' } }) + expect(instance.veteran_first_name).to eq('WESLEY') + end + + it 'returns nil if the key does not exist' do + allow(instance).to receive(:parsed_form).and_return({}) + expect(instance.veteran_first_name).to be_nil + end + end + + describe '#veteran_last_name' do + it 'returns the last name of the veteran from parsed_form' do + allow(instance).to receive(:parsed_form).and_return({ 'veteranFullName' => { 'last' => 'FORD' } }) + expect(instance.veteran_last_name).to eq('FORD') + end + + it 'returns nil if the key does not exist' do + allow(instance).to receive(:parsed_form).and_return({}) + expect(instance.veteran_last_name).to be_nil + end + end + + describe '#claimaint_first_name' do + it 'returns the first name of the claimant from parsed_form' do + allow(instance).to receive(:parsed_form).and_return({ 'claimantFullName' => { 'first' => 'Derrick' } }) + expect(instance.claimaint_first_name).to eq('Derrick') + end + + it 'returns nil if the key does not exist' do + allow(instance).to receive(:parsed_form).and_return({}) + expect(instance.claimaint_first_name).to be_nil + end + end +end