diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0d49491012a..230d4e56dc2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1315,21 +1315,16 @@ spec/fixtures/notice_of_disagreements/NOD_show_response_200.json @department-of- spec/fixtures/notice_of_disagreements/valid_NOD_create_request.json @department-of-veterans-affairs/backend-review-group spec/fixtures/okta @department-of-veterans-affairs/lighthouse-pivot spec/fixtures/okta/okta_callback_request_idme_1567760195.json @department-of-veterans-affairs/lighthouse-pivot +spec/fixtures/pdf_fill @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/10-10CG @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/21-0538 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/21-0781 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/21-4142 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/21-674 @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/21-8940 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/21P-0969 @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/21P-530 @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/21P-530V2 @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/26-1880 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/28-1900 @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/28-8832 @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_fill/5655 @department-of-veterans-affairs/vsa-debt-resolution spec/fixtures/pdf_fill/686C-674 @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group -spec/fixtures/pdf_fill/extras.pdf @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pdf_utilities @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/fixtures/pension @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group spec/fixtures/preneeds @department-of-veterans-affairs/mbs-core-team @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml index bce18493148..aee76f49812 100644 --- a/.github/workflows/code_checks.yml +++ b/.github/workflows/code_checks.yml @@ -126,7 +126,7 @@ jobs: - name: Setup Database uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0 with: - timeout_minutes: 20 + timeout_minutes: 10 retry_wait_seconds: 3 # Seconds max_attempts: 3 command: | @@ -134,7 +134,7 @@ jobs: -c "CI=true RAILS_ENV=test DISABLE_BOOTSNAP=true bundle exec parallel_test -n 24 -e 'bin/rails db:reset'" - name: Run Specs - timeout-minutes: 20 + timeout-minutes: 15 run: | docker compose -f docker-compose.test.yml run web bash \ -c "CI=true DISABLE_BOOTSNAP=true bundle exec parallel_rspec spec/ modules/ -n 24 -o '--color --tty'" diff --git a/Gemfile.lock b/Gemfile.lock index 1db03339a0d..56ff3e4abee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -245,7 +245,7 @@ GEM attr_extras (7.1.0) awesome_print (1.9.2) aws-eventstream (1.3.0) - aws-partitions (1.1010.0) + aws-partitions (1.1013.0) aws-sdk-core (3.213.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -254,7 +254,7 @@ GEM aws-sdk-kms (1.95.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.172.0) + aws-sdk-s3 (1.173.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -425,10 +425,10 @@ GEM tzinfo ethon (0.16.0) ffi (>= 1.15.0) - factory_bot (6.4.5) + factory_bot (6.5.0) activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) faker (3.5.1) i18n (>= 1.8.11, < 2) @@ -529,6 +529,11 @@ GEM google-protobuf (4.28.3) bigdecimal rake (>= 13) + google-protobuf (4.28.3-java) + bigdecimal + ffi (~> 1) + ffi-compiler (~> 1) + rake (>= 13) googleauth (1.11.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) @@ -639,7 +644,7 @@ GEM rake (~> 13.0) lockbox (2.0.0) logger (1.6.1) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) lumberjack (1.2.10) @@ -660,15 +665,15 @@ GEM rake mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.7) - minitest (5.25.1) - mock_redis (0.45.0) + mini_portile2 (2.8.8) + minitest (5.25.2) + mock_redis (0.46.0) msgpack (1.7.2) msgpack (1.7.2-java) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.4.1) - mutex_m (0.2.0) + mutex_m (0.3.0) nap (1.1.0) nenv (0.3.0) net-http (0.4.1) @@ -685,8 +690,8 @@ GEM net-smtp (0.5.0) net-protocol net-ssh (7.2.0) - nio4r (2.7.3) - nio4r (2.7.3-java) + nio4r (2.7.4) + nio4r (2.7.4-java) nkf (0.2.0) nkf (0.2.0-java) nokogiri (1.16.7) @@ -736,7 +741,7 @@ GEM racc patience_diff (1.2.0) optimist (~> 3.0) - pdf-core (0.9.0) + pdf-core (0.10.0) pdf-forms (1.5.1) cliver (~> 0.3.2) rexml (~> 3.2, >= 3.2.6) @@ -756,9 +761,10 @@ GEM activerecord (>= 6.1) activesupport (>= 6.1) pkce_challenge (1.0.0) - prawn (2.4.0) - pdf-core (~> 0.9.0) - ttfunk (~> 1.7) + prawn (2.5.0) + matrix (~> 0.4) + pdf-core (~> 0.10.0) + ttfunk (~> 1.8) prawn-markup (1.0.0) nokogiri prawn @@ -776,21 +782,21 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) pstore (0.1.3) - psych (5.1.2) + psych (5.2.0) stringio - psych (5.1.2-java) + psych (5.2.0-java) jar-dependencies (>= 0.1.7) public_suffix (6.0.1) - puma (6.4.3) + puma (6.5.0) nio4r (~> 2.0) - puma (6.4.3-java) + puma (6.5.0-java) nio4r (~> 2.0) pundit (2.4.0) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) racc (1.8.1-java) - rack (2.2.9) + rack (2.2.10) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (2.0.2) @@ -805,7 +811,7 @@ GEM rack-timeout (0.7.0) rack-vcr (0.1.6) vcr (>= 2.9) - rackup (1.0.0) + rackup (1.0.1) rack (< 3) webrick rails (7.1.4.1) @@ -849,7 +855,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rchardet (1.8.0) - rdoc (6.7.0) + rdoc (6.8.1) psych (>= 4.0.0) redis (5.3.0) redis-client (>= 0.22.0) @@ -858,7 +864,7 @@ GEM redis-namespace (1.11.0) redis (>= 4) regexp_parser (2.9.2) - reline (0.5.10) + reline (0.5.11) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -927,9 +933,9 @@ GEM json-schema (>= 2.2, < 6.0) railties (>= 5.2, < 8.0) rspec-core (>= 2.14) - rswag-ui (2.15.0) - actionpack (>= 5.2, < 8.0) - railties (>= 5.2, < 8.0) + rswag-ui (2.16.0) + actionpack (>= 5.2, < 8.1) + railties (>= 5.2, < 8.1) rtesseract (3.1.3) rubocop (1.68.0) json (~> 2.3) @@ -1032,7 +1038,7 @@ GEM ssrf_filter (1.1.2) staccato (0.5.3) statsd-instrument (3.9.7) - stringio (3.1.1) + stringio (3.1.2) strong_migrations (2.0.2) activerecord (>= 6.1) super_diff (0.13.0) @@ -1049,7 +1055,8 @@ GEM timecop (0.9.10) timeout (0.4.2) trailblazer-option (0.1.2) - ttfunk (1.7.0) + ttfunk (1.8.0) + bigdecimal (~> 3.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) @@ -1107,7 +1114,7 @@ GEM xmlmapper (0.8.1) nokogiri (~> 1.11) yard (0.9.37) - zeitwerk (2.6.18) + zeitwerk (2.7.1) PLATFORMS aarch64-linux diff --git a/app/models/saved_claim/caregivers_assistance_claim.rb b/app/models/saved_claim/caregivers_assistance_claim.rb index f21b0a0a783..65b5dc6e222 100644 --- a/app/models/saved_claim/caregivers_assistance_claim.rb +++ b/app/models/saved_claim/caregivers_assistance_claim.rb @@ -33,6 +33,24 @@ def to_pdf(filename = nil, **) raise end + def form_matches_schema + super unless Flipper.enabled?(:caregiver_retry_form_validation) + + return unless form_is_string + + schema = VetsJsonSchema::SCHEMAS[self.class::FORM] + validation_errors = validate_form_with_retries(schema) + + validation_errors.each do |e| + errors.add(e[:fragment], e[:message]) + e[:errors]&.flatten(2)&.each { |nested| errors.add(nested[:fragment], nested[:message]) if nested.is_a? Hash } + end + + unless validation_errors.empty? + Rails.logger.error('SavedClaim form did not pass validation', { guid:, errors: validation_errors }) + end + end + # SavedClaims require regional_office to be defined, CaregiversAssistanceClaim has no purpose for it. # # CaregiversAssistanceClaims are not processed regional VA offices. @@ -75,4 +93,28 @@ def destroy_attachment Form1010cg::Attachment.find_by(guid: parsed_form['poaAttachmentId'])&.destroy! end + + def validate_form_with_retries(schema) + attempts = 0 + max_attempts = 3 + + begin + attempts += 1 + errors_array = JSON::Validator.fully_validate(schema, parsed_form, { errors_as_objects: true }) + Rails.logger.info("Form validation succeeded on attempt #{attempts}/#{max_attempts}") if attempts > 1 + errors_array + rescue => e + if attempts <= max_attempts + Rails.logger.warn("Retrying form validation due to error: #{e.message} (Attempt #{attempts}/#{max_attempts})") + sleep(1) # Delay 1 second in between attempts + retry + else + PersonalInformationLog.create(data: { schema:, parsed_form:, params: { errors_as_objects: true } }, + error_class: 'SavedClaim FormValidationError') + Rails.logger.error('Error during form validation after maximimum retries', + { error: e.message, backtrace: e.backtrace, schema: }) + raise + end + end + end end diff --git a/app/models/saved_claim/dependency_claim.rb b/app/models/saved_claim/dependency_claim.rb index ab62fd15dda..9ded9b3074a 100644 --- a/app/models/saved_claim/dependency_claim.rb +++ b/app/models/saved_claim/dependency_claim.rb @@ -139,23 +139,30 @@ def to_pdf(form_id: FORM) # Future work will be integrating into the Va Notify common lib: # https://github.com/department-of-veterans-affairs/vets-api/blob/master/lib/va_notify/notification_email.rb - def send_failure_email(email) - template_ids = [] - template_ids << Settings.vanotify.services.va_gov.template_id.form21_686c_action_needed_email if submittable_686? - template_ids << Settings.vanotify.services.va_gov.template_id.form21_674_action_needed_email if submittable_674? - - template_ids.each do |template_id| - if email.present? - VANotify::EmailJob.perform_async( - email, - template_id, - { - 'first_name' => parsed_form.dig('veteran_information', 'full_name', 'first')&.upcase.presence, - 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), - 'confirmation_number' => confirmation_number - } - ) - end + def send_failure_email(email) # rubocop:disable Metrics/MethodLength + # if the claim is both a 686c and a 674, send a combination email. + # otherwise, check to see which individual type it is and send the corresponding email. + template_id = if submittable_686? && submittable_674? + Settings.vanotify.services.va_gov.template_id.form21_686c_674_action_needed_email + elsif submittable_686? + Settings.vanotify.services.va_gov.template_id.form21_686c_action_needed_email + elsif submittable_674? + Settings.vanotify.services.va_gov.template_id.form21_674_action_needed_email + else + Rails.logger.error('Email template cannot be assigned for SavedClaim', saved_claim_id: id) + nil + end + + if email.present? && template_id.present? + VANotify::EmailJob.perform_async( + email, + template_id, + { + 'first_name' => parsed_form.dig('veteran_information', 'full_name', 'first')&.upcase.presence, + 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), + 'confirmation_number' => confirmation_number + } + ) end end diff --git a/app/services/mhv/user_account/creator.rb b/app/services/mhv/user_account/creator.rb index aaf35f75c74..85f4077214d 100644 --- a/app/services/mhv/user_account/creator.rb +++ b/app/services/mhv/user_account/creator.rb @@ -32,7 +32,7 @@ def perform def create_mhv_user_account! account = MHVUserAccount.new(mhv_account_creation_response) account.validate! - + MPIData.find(icn)&.destroy account end diff --git a/config/features.yml b/config/features.yml index f4e1e7928f5..fdeee335e78 100644 --- a/config/features.yml +++ b/config/features.yml @@ -80,6 +80,9 @@ features: actor_type: user description: Send 10-10CG submission failure email to Veteran using VANotify. enable_in_development: true + caregiver_retry_form_validation: + actor_type: user + description: Enables 1010CG to retry schema validation hca_browser_monitoring_enabled: actor_type: user description: Enables browser monitoring for the health care application. @@ -237,6 +240,10 @@ features: actor_type: user description: When enabled, sends poa forms to BD via the refactored logic enable_in_development: true + claims_api_use_person_web_service: + actor_type: user + description: Uses person web service rather than local bgs + enable_in_development: true claims_api_526_v2_uploads_bd_refactor: actor_type: user description: When enabled, sends 526 forms to BD via the refactored logic @@ -857,10 +864,6 @@ features: actor_type: user description: Enable/disable 526ez in progress form reminders (sent via VaNotify) enable_in_development: true - va_notify_user_account_job: - actor_type: user - description: Enable/disable UserAccountJob in VANotify (replacement for IcnJob) - enable_in_development: true letters_check_discrepancies: actor_type: user description: Enables ability to log letter discrepancies between evss and lighthouse diff --git a/config/initializers/breakers.rb b/config/initializers/breakers.rb index c9d4f1188f3..57da61650bd 100644 --- a/config/initializers/breakers.rb +++ b/config/initializers/breakers.rb @@ -21,6 +21,7 @@ require 'mhv_ac/configuration' require 'mpi/configuration' require 'pagerduty/configuration' +require 'post911_sob/dgib/configuration' require 'preneeds/configuration' require 'rx/configuration' require 'sm/configuration' @@ -62,6 +63,7 @@ HCA::Configuration.instance.breakers_service, MHVAC::Configuration.instance.breakers_service, MPI::Configuration.instance.breakers_service, + Post911SOB::DGIB::Configuration.instance.breakers_service, Preneeds::Configuration.instance.breakers_service, SM::Configuration.instance.breakers_service, VAProfile::AddressValidation::Configuration.instance.breakers_service, diff --git a/config/settings.yml b/config/settings.yml index 84b48ea9f97..ae6ac19c385 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1454,6 +1454,9 @@ dgi: jwt: public_key_path: spec/fixtures/post911_sob/dgib/public_test.pem private_key_path: spec/fixtures/post911_sob/dgib/private_test.pem + claimants: + url: ~ + mock: false # Settings for the VEText integration (mobile push notifications) vetext_push: diff --git a/lib/periodic_jobs.rb b/lib/periodic_jobs.rb index 205d2817977..526efe95789 100644 --- a/lib/periodic_jobs.rb +++ b/lib/periodic_jobs.rb @@ -207,7 +207,7 @@ mgr.register('0 2,9,16 * * 1-5', 'VBADocuments::FlipperStatusAlert') # Rotates Lockbox/KMS record keys and _ciphertext fields every October 12th (when the KMS key auto-rotate) - mgr.register('10 5 * * *', 'KmsKeyRotation::BatchInitiatorJob') + mgr.register('10 1 * * *', 'KmsKeyRotation::BatchInitiatorJob') # Updates veteran representatives address attributes (including lat, long, location, address fields, email address, phone number) # rubocop:disable Layout/LineLength mgr.register('0 3 * * *', 'Representatives::QueueUpdates') diff --git a/lib/post911_sob/dgib/client.rb b/lib/post911_sob/dgib/client.rb new file mode 100644 index 00000000000..a9ed572f32a --- /dev/null +++ b/lib/post911_sob/dgib/client.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'common/client/base' +require 'post911_sob/dgib/configuration' +require 'post911_sob/dgib/authentication_token_service' + +module Post911SOB + module DGIB + class Client < Common::Client::Base + include Common::Client::Concerns::Monitoring + + configuration Post911SOB::DGIB::Configuration + + BENEFIT_TYPE = 'Chapter33' + + def initialize(claimant_id) + @claimant_id = claimant_id + + super() + end + + def get_entitlement_transferred_out + # TO-DO add monitoring and serialized response + # TO-DO Filter response by chapter33 benefit type + options = { timeout: 60 } + perform(:get, end_point, {}, request_headers, options) + end + + private + + def end_point + "transferees/#{@claimant_id}/toe" + end + + def request_headers + { + Authorization: "Bearer #{Post911SOB::DGIB::AuthenticationTokenService.call}" + } + end + end + end +end diff --git a/lib/post911_sob/dgib/configuration.rb b/lib/post911_sob/dgib/configuration.rb new file mode 100644 index 00000000000..640bcb8e263 --- /dev/null +++ b/lib/post911_sob/dgib/configuration.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'common/client/configuration/rest' + +module Post911SOB + module DGIB + class Configuration < Common::Client::Configuration::REST + SETTINGS = Settings.dgi.post911_sob.claimants + + # TO-DO: Datadog + + def base_path + SETTINGS.url.to_s + end + + def service_name + 'Post911SOB/DGIB' + end + + def connection + @conn ||= Faraday.new(base_path, headers: base_request_headers, request: request_options) do |faraday| + faraday.use :breakers + faraday.use Faraday::Response::RaiseError + faraday.request :json + + faraday.response :betamocks if mock_enabled? + faraday.response :snakecase, symbolize: false + faraday.response :json, content_type: /\bjson/ # ensures only json content types parsed + faraday.adapter Faraday.default_adapter + end + end + + private + + def mock_enabled? + SETTINGS.mock || false + end + end + end +end diff --git a/modules/claims_api/app/services/claims_api/dependent_claimant_poa_assignment_service.rb b/modules/claims_api/app/services/claims_api/dependent_claimant_poa_assignment_service.rb index b2ec6e08637..8c7f9218fb7 100644 --- a/modules/claims_api/app/services/claims_api/dependent_claimant_poa_assignment_service.rb +++ b/modules/claims_api/app/services/claims_api/dependent_claimant_poa_assignment_service.rb @@ -9,6 +9,7 @@ module ClaimsApi class DependentClaimantPoaAssignmentService def initialize(**options) + @poa_id = options[:poa_id] @poa_code = options[:poa_code] @veteran_participant_id = options[:veteran_participant_id] @dependent_participant_id = options[:dependent_participant_id] @@ -25,7 +26,7 @@ def assign_poa_to_dependent! log(level: :error, detail: 'Failed to assign POA to dependent') - raise ::Common::Exceptions::FailedDependency + raise ::Common::Exceptions::ServiceError end private @@ -36,7 +37,8 @@ def person_web_service end def log(level: :info, **rest) - ClaimsApi::Logger.log('dependent_claimant_poa_assignment_service', level:, poa_code: @poa_code, **rest) + ClaimsApi::Logger.log('dependent_claimant_poa_assignment_service', level:, poa_id: @poa_id, poa_code: @poa_code, + **rest) end def assign_poa_to_dependent_via_manage_ptcpnt_rlnshp? @@ -51,19 +53,28 @@ def assign_poa_to_dependent_via_manage_ptcpnt_rlnshp? return true end - log(level: :warn, - detail: 'Something else went wrong with manage_ptcpnt_rlnshp. Falling back to update_benefit_claim.') - - false + raise ::Common::Exceptions::ServiceError rescue ::Common::Exceptions::ServiceError => e if e.errors.first.detail == 'PtcpntIdA has open claims.' log(detail: 'Dependent has open claims, continuing.') - else - log(level: :warn, - detail: 'Something else went wrong with manage_ptcpnt_rlnshp. Falling back to update_benefit_claim.') + + return false end - false + raise e + rescue => e + log(level: :error, detail: 'Something else went wrong with manage_ptcpnt_rlnshp.', error: error_details(e)) + + raise e + end + + def error_details(e) + { + message: e.message, + detail: e.try(:detail), + details: e.try(:details), + errors: e.try(:errors)&.map(&:to_h) + }.compact end def iso_to_date(iso_date) @@ -92,6 +103,12 @@ def assign_poa_to_dependent_via_update_benefit_claim? first_open_claim = dependent_claims.find do |claim| claim[:phase_type] != 'Complete' && claim[:ptcpnt_vet_id] == @veteran_participant_id end + if first_open_claim.nil? || first_open_claim.blank? + log(detail: 'Dependent has no open claims.', statuses: dependent_claims.pluck(:phase_type).uniq) + + raise ::Common::Exceptions::ServiceError + end + first_open_claim_details = claim_details(first_open_claim[:benefit_claim_id]) benefit_claim_update_input = build_benefit_claim_update_input(claim_details: first_open_claim_details) @@ -111,12 +128,13 @@ def dependent_claims local_bgs = ClaimsApi::LocalBGS.new(external_uid: @dependent_participant_id, external_key: @dependent_participant_id) res = local_bgs.find_benefit_claims_status_by_ptcpnt_id(@dependent_participant_id) + benefit_claims = Array.wrap(res&.dig(:benefit_claims_dto, :benefit_claim)) - return res&.dig(:benefit_claims_dto, :benefit_claim) if res&.dig(:benefit_claims_dto, :benefit_claim).present? + return benefit_claims if benefit_claims.present? && benefit_claims.is_a?(Array) && benefit_claims.first.present? log(level: :error, detail: 'Dependent claims not found in BGS') - raise ::Common::Exceptions::FailedDependency + raise ::Common::Exceptions::ResourceNotFound end def benefit_claim_web_service @@ -136,7 +154,7 @@ def claim_details(claim_id) log(level: :error, detail: 'Claim details not found in BGS', claim_id:) - raise ::Common::Exceptions::FailedDependency + raise ::Common::Exceptions::ResourceNotFound end def poa_participant_id @@ -146,7 +164,7 @@ def poa_participant_id log(level: :error, detail: 'POA code/participant ID combo not found in BGS') - raise ::Common::Exceptions::FailedDependency + raise ::Common::Exceptions::ResourceNotFound end def manage_ptcpnt_rlnshp_poa_success?(response) @@ -162,7 +180,7 @@ def benefit_claim_type(pgm_type_cd) else log(level: :error, detail: 'Program type code not recognized', pgm_type_cd:) - raise ::Common::Exceptions::FailedDependency + raise ::Common::Exceptions::BadRequest end end end diff --git a/modules/claims_api/app/sidekiq/claims_api/poa_assign_dependent_claimant_job.rb b/modules/claims_api/app/sidekiq/claims_api/poa_assign_dependent_claimant_job.rb index 410ca11c5ad..2cace86cb5f 100644 --- a/modules/claims_api/app/sidekiq/claims_api/poa_assign_dependent_claimant_job.rb +++ b/modules/claims_api/app/sidekiq/claims_api/poa_assign_dependent_claimant_job.rb @@ -8,6 +8,7 @@ def perform(poa_id) poa = ClaimsApi::PowerOfAttorney.find(poa_id) service = dependent_claimant_poa_assignment_service( + poa.id, poa.form_data, poa.auth_headers ) @@ -42,8 +43,9 @@ def handle_error(poa, e) raise e end - def dependent_claimant_poa_assignment_service(data, auth_headers) + def dependent_claimant_poa_assignment_service(poa_id, data, auth_headers) ClaimsApi::DependentClaimantPoaAssignmentService.new( + poa_id:, poa_code: find_poa_code(data), veteran_participant_id: auth_headers['va_eauth_pid'], dependent_participant_id: auth_headers.dig('dependent', 'participant_id'), diff --git a/modules/claims_api/app/swagger/claims_api/description/v1.md b/modules/claims_api/app/swagger/claims_api/description/v1.md index c4084b33cc7..f193fa0cb96 100644 --- a/modules/claims_api/app/swagger/claims_api/description/v1.md +++ b/modules/claims_api/app/swagger/claims_api/description/v1.md @@ -34,8 +34,8 @@ This API accepts a payload of requests and responses on a per-form basis, with t ### Attachment and file size limits There is no limit on the number of files a payload can contain, but size limits do apply. - Uploaded documents cannot be larger than 11" x 11" - - The entire payload cannot exceed 5 GB - - No single file in a payload can exceed 100 MB + - The entire payload cannot exceed 100 GB + - No single file in a payload can exceed 25 MB ### Authentication and authorization To make an API request, follow our [authentication process](https://developer.va.gov/explore/api/benefits-claims/authorization-code) to receive an [OAuth token](https://oauth.net/2/). diff --git a/modules/claims_api/app/swagger/claims_api/v1/swagger.json b/modules/claims_api/app/swagger/claims_api/v1/swagger.json index 8ef3fdd9062..574ec7697da 100644 --- a/modules/claims_api/app/swagger/claims_api/v1/swagger.json +++ b/modules/claims_api/app/swagger/claims_api/v1/swagger.json @@ -4,7 +4,7 @@ "title": "Benefits Claims", "version": "v1", "termsOfService": "https://developer.va.gov/terms-of-service", - "description": "This API automatically establishes and submits these VA forms.\n| Form number | Form name | Description |\n| :------------- | :----------: | -----------: |\n| [21-526EZ](https://www.va.gov/find-forms/about-form-21-526ez/) | Application for Disability Compensation and Related Compensation Benefits | Used to apply for VA disability compensation and related benefits. |\n| [21-0966](https://www.va.gov/find-forms/about-form-21-0966/) | Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC | Submits an intent to file to secure the earliest possible effective date for any retroactive payments. |\n| [21-22](https://www.va.gov/find-forms/about-form-21-22/) | Appointment of Veterans Service Organization as Claimant's Representative | Used to assign a Veterans Service Organization as a POA to help a Veteran or dependent with benefits or claims. |\n| [21-22a](https://www.va.gov/find-forms/about-form-21-22a/) | Appointment of Individual As Claimant's Representative | Used to assign an individual as a POA to help a Veteran with benefits or claims. |\n\nIt also lets claimants or their authorized representatives:\n - Digitally submit supporting documentation for disability compensation claims.\n - Retrieve information such as status for any claim, including pension and burial.\n - Retrieve power of attorney (POA) status for individuals and Veterans Service Organizations (VSOs).\n - Retrieve intent to file status.\n\n## Background\nThe Benefits Claims API offers faster establishment and enhanced reporting for several VA claims and forms. Using this API provides many benefits, such as:\n - Automatic claim and POA establishment\n - Direct establishment of disability compensation claims in Veterans Benefits Management System (VBMS) to avoid unnecessary manual processing and entry by Veteran Service Representatives (VSRs)\n - Faster claims processing by several days\n - End-to-end claims status and result tracking by claim ID\n\nForms not supported by the Benefits Claims API are submitted using the [Benefits Intake API](https://developer.va.gov/explore/benefits/docs/benefits?version=current), which places uploaded PDFs into the Centralized Mail Portal to be manually processed.\n\n## Appointing an accredited representative for dependents\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\n\n## Technical Overview\nThis API accepts a payload of requests and responses on a per-form basis, with the payload identifying the form and Veteran. Trackable responses provide a unique ID which is used with the appropriate GET endpoint to track a submission’s processing status.\n\n### Attachment and file size limits\nThere is no limit on the number of files a payload can contain, but size limits do apply.\n - Uploaded documents cannot be larger than 11\" x 11\"\n - The entire payload cannot exceed 5 GB\n - No single file in a payload can exceed 100 MB\n\n### Authentication and authorization\nTo make an API request, follow our [authentication process](https://developer.va.gov/explore/api/benefits-claims/authorization-code) to receive an [OAuth token](https://oauth.net/2/).\n\n#### Representative authorization\nRepresentatives seeking authorization for a claimant must first [authenticate](https://developer.va.gov/explore/api/benefits-claims/authorization-code) and then pass the Veteran’s information in the right header:\n - SSN in X-VA-SSN\n - First name in X-VA-First-Name\n - Last name in X-VA-Last-Name\n - Date of birth in X-VA-Birth-Date\n\nOmitting the information will cause the API to treat the representative as the claimant.\n\n#### Veteran authorization\nVeterans seeking authorization do not need to include headers such as X-VA-First-Name since the token authentication via ID.me, MyHealtheVet, or DSLogon provides this information.\n\n### POA Codes\nVeteran representatives receive their organization’s POA code. If they are the assigned POA for a claimant, that claimant will have a matching POA code. When a claim is submitted, this API verifies that the representative and Veteran codes match against each other and the codes in the [Office of General Council (OGC) Database](https://www.va.gov/ogc/apps/accreditation/index.asp).\n\nUse the [Power of Attorney endpoint](#operations-Power_of_Attorney-post2122) to assign or update POA status. A newly appointed representative may not be able to submit forms for a Veteran until a day after their POA code is first associated with the OGC data set.\n\n### Test data for sandbox environment use\n[Test data](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md) is used for all forms in the sandbox environment and for 21-526 submissions in the staging environment.\n\n### Claim and form processing\nClaims and forms are first submitted by this API and then established in VBMS. A 200 response means only that your claim or form was submitted successfully. To see if your submission is processed or has reached VBMS, you must check its status using the appropriate GET endpoint and the ID returned with your submission response.\n\nA “claim established” status means the claim has reached VBMS. In sandbox, submissions can take over an hour to reach “claim established” status. In production, this may take over two days.\n" + "description": "This API automatically establishes and submits these VA forms.\n| Form number | Form name | Description |\n| :------------- | :----------: | -----------: |\n| [21-526EZ](https://www.va.gov/find-forms/about-form-21-526ez/) | Application for Disability Compensation and Related Compensation Benefits | Used to apply for VA disability compensation and related benefits. |\n| [21-0966](https://www.va.gov/find-forms/about-form-21-0966/) | Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC | Submits an intent to file to secure the earliest possible effective date for any retroactive payments. |\n| [21-22](https://www.va.gov/find-forms/about-form-21-22/) | Appointment of Veterans Service Organization as Claimant's Representative | Used to assign a Veterans Service Organization as a POA to help a Veteran or dependent with benefits or claims. |\n| [21-22a](https://www.va.gov/find-forms/about-form-21-22a/) | Appointment of Individual As Claimant's Representative | Used to assign an individual as a POA to help a Veteran with benefits or claims. |\n\nIt also lets claimants or their authorized representatives:\n - Digitally submit supporting documentation for disability compensation claims.\n - Retrieve information such as status for any claim, including pension and burial.\n - Retrieve power of attorney (POA) status for individuals and Veterans Service Organizations (VSOs).\n - Retrieve intent to file status.\n\n## Background\nThe Benefits Claims API offers faster establishment and enhanced reporting for several VA claims and forms. Using this API provides many benefits, such as:\n - Automatic claim and POA establishment\n - Direct establishment of disability compensation claims in Veterans Benefits Management System (VBMS) to avoid unnecessary manual processing and entry by Veteran Service Representatives (VSRs)\n - Faster claims processing by several days\n - End-to-end claims status and result tracking by claim ID\n\nForms not supported by the Benefits Claims API are submitted using the [Benefits Intake API](https://developer.va.gov/explore/benefits/docs/benefits?version=current), which places uploaded PDFs into the Centralized Mail Portal to be manually processed.\n\n## Appointing an accredited representative for dependents\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\n\n## Technical Overview\nThis API accepts a payload of requests and responses on a per-form basis, with the payload identifying the form and Veteran. Trackable responses provide a unique ID which is used with the appropriate GET endpoint to track a submission’s processing status.\n\n### Attachment and file size limits\nThere is no limit on the number of files a payload can contain, but size limits do apply.\n - Uploaded documents cannot be larger than 11\" x 11\"\n - The entire payload cannot exceed 100 GB\n - No single file in a payload can exceed 25 MB\n\n### Authentication and authorization\nTo make an API request, follow our [authentication process](https://developer.va.gov/explore/api/benefits-claims/authorization-code) to receive an [OAuth token](https://oauth.net/2/).\n\n#### Representative authorization\nRepresentatives seeking authorization for a claimant must first [authenticate](https://developer.va.gov/explore/api/benefits-claims/authorization-code) and then pass the Veteran’s information in the right header:\n - SSN in X-VA-SSN\n - First name in X-VA-First-Name\n - Last name in X-VA-Last-Name\n - Date of birth in X-VA-Birth-Date\n\nOmitting the information will cause the API to treat the representative as the claimant.\n\n#### Veteran authorization\nVeterans seeking authorization do not need to include headers such as X-VA-First-Name since the token authentication via ID.me, MyHealtheVet, or DSLogon provides this information.\n\n### POA Codes\nVeteran representatives receive their organization’s POA code. If they are the assigned POA for a claimant, that claimant will have a matching POA code. When a claim is submitted, this API verifies that the representative and Veteran codes match against each other and the codes in the [Office of General Council (OGC) Database](https://www.va.gov/ogc/apps/accreditation/index.asp).\n\nUse the [Power of Attorney endpoint](#operations-Power_of_Attorney-post2122) to assign or update POA status. A newly appointed representative may not be able to submit forms for a Veteran until a day after their POA code is first associated with the OGC data set.\n\n### Test data for sandbox environment use\n[Test data](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md) is used for all forms in the sandbox environment and for 21-526 submissions in the staging environment.\n\n### Claim and form processing\nClaims and forms are first submitted by this API and then established in VBMS. A 200 response means only that your claim or form was submitted successfully. To see if your submission is processed or has reached VBMS, you must check its status using the appropriate GET endpoint and the ID returned with your submission response.\n\nA “claim established” status means the claim has reached VBMS. In sandbox, submissions can take over an hour to reach “claim established” status. In production, this may take over two days.\n" }, "tags": [ { diff --git a/modules/claims_api/lib/claims_api/poa_vbms_sidekiq.rb b/modules/claims_api/lib/claims_api/poa_vbms_sidekiq.rb index 90b140aad40..416b0dfefff 100644 --- a/modules/claims_api/lib/claims_api/poa_vbms_sidekiq.rb +++ b/modules/claims_api/lib/claims_api/poa_vbms_sidekiq.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'claims_api/vbms_uploader' +require 'bgs_service/person_web_service' module ClaimsApi module PoaVbmsSidekiq @@ -53,7 +54,7 @@ def retrieve_veteran_file_number(power_of_attorney:) ssn = power_of_attorney.auth_headers['va_eauth_pnid'] begin - bgs_service(power_of_attorney:).people.find_by_ssn(ssn)&.[](:file_nbr) # rubocop:disable Rails/DynamicFindBy + bgs_service(power_of_attorney:).find_by_ssn(ssn)&.[](:file_nbr) # rubocop:disable Rails/DynamicFindBy rescue BGS::ShareError => e error_message = "A BGS failure occurred while trying to retrieve Veteran 'FileNumber'" log_exception_to_sentry(e, nil, { message: error_message }, 'warn') @@ -62,10 +63,17 @@ def retrieve_veteran_file_number(power_of_attorney:) end def bgs_service(power_of_attorney:) - BGS::Services.new( - external_uid: power_of_attorney.auth_headers['va_eauth_pid'], - external_key: power_of_attorney.auth_headers['va_eauth_pid'] - ) + if Flipper.enabled? :claims_api_use_person_web_service + ClaimsApi::PersonWebService.new( + external_uid: power_of_attorney.auth_headers['va_eauth_pid'], + external_key: power_of_attorney.auth_headers['va_eauth_pid'] + ) + else + BGS::Services.new( + external_uid: power_of_attorney.auth_headers['va_eauth_pid'], + external_key: power_of_attorney.auth_headers['va_eauth_pid'] + ).people + end end end end diff --git a/modules/claims_api/spec/sidekiq/poa_form_builder_job_spec.rb b/modules/claims_api/spec/sidekiq/poa_form_builder_job_spec.rb index 79de3c1783e..744d84e4cbd 100644 --- a/modules/claims_api/spec/sidekiq/poa_form_builder_job_spec.rb +++ b/modules/claims_api/spec/sidekiq/poa_form_builder_job_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' require 'pdf_fill/filler' -RSpec.describe ClaimsApi::V1::PoaFormBuilderJob, type: :job do +RSpec.describe ClaimsApi::V1::PoaFormBuilderJob, type: :job, vcr: 'bgs/person_web_service/find_by_ssn' do subject { described_class } let(:power_of_attorney) { create(:power_of_attorney, :with_full_headers) } diff --git a/modules/claims_api/spec/sidekiq/poa_vbms_sidekiq_spec.rb b/modules/claims_api/spec/sidekiq/poa_vbms_sidekiq_spec.rb index ff7415227fd..e23eae1ecc7 100644 --- a/modules/claims_api/spec/sidekiq/poa_vbms_sidekiq_spec.rb +++ b/modules/claims_api/spec/sidekiq/poa_vbms_sidekiq_spec.rb @@ -2,10 +2,17 @@ require 'rails_helper' require 'claims_api/poa_vbms_sidekiq' +require 'bgs_service/person_web_service' -RSpec.describe ClaimsApi::PoaVbmsSidekiq do +RSpec.describe ClaimsApi::PoaVbmsSidekiq, vcr: 'bgs/person_web_service/find_by_ssn' do let(:dummy_class) { Class.new { extend ClaimsApi::PoaVbmsSidekiq } } + before do + Sidekiq::Job.clear_all + allow_any_instance_of(Flipper).to receive(:enabled?).with(:claims_api_use_person_web_service).and_return false + allow_any_instance_of(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_poa_use_bd).and_return false + end + describe 'upload_to_vbms' do let(:power_of_attorney) { create(:power_of_attorney) } @@ -40,5 +47,29 @@ ) end end + + describe 'when the claims_api_use_person_web_service flipper is on' do + let(:person_web_service) { instance_double(ClaimsApi::PersonWebService) } + + before do + allow(Flipper).to receive(:enabled?).with(:claims_api_use_person_web_service).and_return true + allow(ClaimsApi::PersonWebService).to receive(:new).with(external_uid: anything, + external_key: anything) + .and_return(person_web_service) + allow(person_web_service).to receive(:find_by_ssn).and_return({ file_nbr: '796111863' }) + end + + it 'calls local bgs services instead of bgs-ext' do + allow_any_instance_of(ClaimsApi::VBMSUploader).to receive(:upload!).and_return( + { + vbms_new_document_version_ref_id: 'some value', + vbms_document_series_ref_id: 'some value' + } + ) + + dummy_class.upload_to_vbms(power_of_attorney, '/some/random/path') + expect(person_web_service).to have_received(:find_by_ssn) + end + end end end diff --git a/modules/claims_api/spec/sidekiq/poa_vbms_upload_job_spec.rb b/modules/claims_api/spec/sidekiq/poa_vbms_upload_job_spec.rb index 92f2ffc92a9..6bffb510d34 100644 --- a/modules/claims_api/spec/sidekiq/poa_vbms_upload_job_spec.rb +++ b/modules/claims_api/spec/sidekiq/poa_vbms_upload_job_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' require_relative '../support/fake_vbms' -RSpec.describe ClaimsApi::PoaVBMSUploadJob, type: :job do +RSpec.describe ClaimsApi::PoaVBMSUploadJob, type: :job, vcr: 'bgs/person_web_service/find_by_ssn' do subject { described_class } before do @@ -12,6 +12,7 @@ allow(VBMS::Client).to receive(:from_env_vars).and_return(@vbms_client) allow(Flipper).to receive(:enabled?).with(:claims_load_testing).and_return false allow(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_poa_use_bd).and_return false + allow(Flipper).to receive(:enabled?).with(:claims_api_use_person_web_service).and_return false allow_any_instance_of(ClaimsApi::V2::BenefitsDocuments::Service) .to receive(:get_auth_token).and_return('some-value-here') end diff --git a/modules/claims_api/spec/sidekiq/v2/poa_form_builder_job_spec.rb b/modules/claims_api/spec/sidekiq/v2/poa_form_builder_job_spec.rb index d2d5a453d7e..23cb4d1ce47 100644 --- a/modules/claims_api/spec/sidekiq/v2/poa_form_builder_job_spec.rb +++ b/modules/claims_api/spec/sidekiq/v2/poa_form_builder_job_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' require 'pdf_fill/filler' -RSpec.describe ClaimsApi::V2::PoaFormBuilderJob, type: :job do +RSpec.describe ClaimsApi::V2::PoaFormBuilderJob, type: :job, vcr: 'bgs/person_web_service/find_by_ssn' do subject { described_class } let(:power_of_attorney) { create(:power_of_attorney, :with_full_headers) } @@ -15,7 +15,8 @@ before do Sidekiq::Job.clear_all - Flipper.disable(:lighthouse_claims_api_poa_use_bd) + allow_any_instance_of(Flipper).to receive(:enabled?).with(:claims_api_use_person_web_service).and_return false + allow_any_instance_of(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_poa_use_bd).and_return false end describe 'generating and uploading the signed pdf' do @@ -453,7 +454,7 @@ let(:output_path) { 'some.pdf' } before do - Flipper.enable(:lighthouse_claims_api_poa_use_bd) + allow_any_instance_of(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_poa_use_bd).and_return true pdf_constructor_double = instance_double(ClaimsApi::V2::PoaPdfConstructor::Organization) allow_any_instance_of(ClaimsApi::V2::PoaFormBuilderJob).to receive(:pdf_constructor) .and_return(pdf_constructor_double) @@ -462,7 +463,7 @@ end it 'calls the Benefits Documents uploader instead of VBMS' do - Flipper.disable(:claims_api_poa_uploads_bd_refactor) + allow_any_instance_of(Flipper).to receive(:enabled?).with(:claims_api_poa_uploads_bd_refactor).and_return false expect_any_instance_of(ClaimsApi::VBMSUploader).not_to receive(:upload_document) expect_any_instance_of(ClaimsApi::BD).to receive(:upload) subject.new.perform(power_of_attorney.id, '2122', rep.id, action: 'post') @@ -473,8 +474,8 @@ let(:pdf_path) { 'modules/claims_api/spec/fixtures/21-22/signed_filled_final.pdf' } before do - Flipper.enable(:lighthouse_claims_api_poa_use_bd) - Flipper.enable(:claims_api_poa_uploads_bd_refactor) + allow_any_instance_of(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_poa_use_bd).and_return true + allow_any_instance_of(Flipper).to receive(:enabled?).with(:claims_api_poa_uploads_bd_refactor).and_return true pdf_constructor_double = instance_double(ClaimsApi::V2::PoaPdfConstructor::Organization) allow_any_instance_of(ClaimsApi::V2::PoaFormBuilderJob).to receive(:pdf_constructor) .and_return(pdf_constructor_double) diff --git a/modules/debts_api/app/controllers/debts_api/v0/digital_disputes_controller.rb b/modules/debts_api/app/controllers/debts_api/v0/digital_disputes_controller.rb new file mode 100644 index 00000000000..1309122154e --- /dev/null +++ b/modules/debts_api/app/controllers/debts_api/v0/digital_disputes_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module DebtsApi + module V0 + class DigitalDisputesController < ApplicationController + service_tag 'financial-report' + + def create + # Just returning data back for now while we wait on our integration partner + render json: digital_disputes_params + end + + private + + def digital_disputes_params + params.permit( + contact_information: %i[ + email + phone_number + address_line1 + address_line2 + city + ], + debt_information: %i[ + debt + dispute_reason + support_statement + ] + ) + end + end + end +end diff --git a/modules/debts_api/config/routes.rb b/modules/debts_api/config/routes.rb index 5d9ea4397bc..2c33a3e713c 100644 --- a/modules/debts_api/config/routes.rb +++ b/modules/debts_api/config/routes.rb @@ -9,6 +9,8 @@ end end + resources :digital_disputes, only: %i[create] + get 'financial_status_reports/rehydrate_submission/:submission_id', to: 'financial_status_reports#rehydrate' post 'financial_status_reports/transform_and_submit', to: 'financial_status_reports#transform_and_submit' diff --git a/modules/debts_api/spec/requests/debts_api/v0/digital_disputes_spec.rb b/modules/debts_api/spec/requests/debts_api/v0/digital_disputes_spec.rb new file mode 100644 index 00000000000..38a4c1d8619 --- /dev/null +++ b/modules/debts_api/spec/requests/debts_api/v0/digital_disputes_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'DebtsApi::V0::DigitalDisputes', type: :request do + let(:user) { build(:user, :loa3) } + + before do + sign_in_as(user) + end + + describe '#create' do + let(:params) do + get_fixture_absolute('modules/debts_api/spec/fixtures/digital_disputes/standard_submission') + end + + it 'returns digital_disputes_params' do + post( + '/debts_api/v0/digital_disputes', + params: params, + as: :json + ) + + expect(response).to have_http_status(:ok) + end + end +end diff --git a/modules/pensions/spec/lib/pdf_fill/fixtures/overflow_extras.pdf b/modules/pensions/spec/lib/pdf_fill/fixtures/overflow_extras.pdf index dde5b73a43e..9f501c05006 100644 Binary files a/modules/pensions/spec/lib/pdf_fill/fixtures/overflow_extras.pdf and b/modules/pensions/spec/lib/pdf_fill/fixtures/overflow_extras.pdf differ diff --git a/modules/simple_forms_api/app/models/simple_forms_api/vba_20_10206.rb b/modules/simple_forms_api/app/models/simple_forms_api/vba_20_10206.rb index 14f9a54df08..a4204ea713b 100644 --- a/modules/simple_forms_api/app/models/simple_forms_api/vba_20_10206.rb +++ b/modules/simple_forms_api/app/models/simple_forms_api/vba_20_10206.rb @@ -29,6 +29,10 @@ def zip_code_is_us_based @data.dig('address', 'country') == 'USA' end + def words_to_remove + citizen_ssn + address + date_of_birth + home_phone + end + def desired_stamps [] end @@ -55,5 +59,35 @@ def track_user_identity(confirmation_number) StatsD.increment("#{STATS_KEY}.#{identity}") Rails.logger.info('Simple forms api - 20-10206 submission user identity', identity:, confirmation_number:) end + + private + + def citizen_ssn + [ + data.dig('citizen_id', 'ssn')&.[](0..2), + data.dig('citizen_id', 'ssn')&.[](3..4), + data.dig('citizen_id', 'ssn')&.[](5..8) + ] + end + + def address + [data.dig('address', 'postal_code')&.[](0..4), data.dig('address', 'postal_code')&.[](5..8)] + end + + def date_of_birth + [ + data['date_of_birth']&.[](0..3), + data['date_of_birth']&.[](5..6), + data['date_of_birth']&.[](8..9) + ] + end + + def home_phone + [ + data['home_phone']&.gsub('-', '')&.[](0..2), + data['home_phone']&.gsub('-', '')&.[](3..5), + data['home_phone']&.gsub('-', '')&.[](6..9) + ] + end end end diff --git a/modules/simple_forms_api/lib/simple_forms_api.rb b/modules/simple_forms_api/lib/simple_forms_api.rb index 4ac5088fa83..84c928a7f22 100644 --- a/modules/simple_forms_api/lib/simple_forms_api.rb +++ b/modules/simple_forms_api/lib/simple_forms_api.rb @@ -39,6 +39,8 @@ def scrub_pii(message) words_to_remove += SimpleFormsApi::VBA210966.new(params).words_to_remove when '20-10207' words_to_remove += SimpleFormsApi::VBA2010207.new(params).words_to_remove + when '20-10206' + words_to_remove += SimpleFormsApi::VBA2010206.new(params).words_to_remove when '40-10007' words_to_remove += SimpleFormsApi::VBA4010007.new(params).words_to_remove else diff --git a/modules/va_notify/app/services/va_notify/in_progress_form_reminder.rb b/modules/va_notify/app/services/va_notify/in_progress_form_reminder.rb index 43c1d383913..e60c5205bef 100644 --- a/modules/va_notify/app/services/va_notify/in_progress_form_reminder.rb +++ b/modules/va_notify/app/services/va_notify/in_progress_form_reminder.rb @@ -10,7 +10,6 @@ class InProgressFormReminder class MissingICN < StandardError; end - # rubocop:disable Metrics/MethodLength def perform(form_id) @in_progress_form = InProgressForm.find(form_id) return unless enabled? @@ -22,27 +21,18 @@ def perform(form_id) if only_one_supported_in_progress_form? template_id = VANotify::InProgressFormHelper::TEMPLATE_ID.fetch(in_progress_form.form_id) - if Flipper.enabled?(:va_notify_user_account_job) - UserAccountJob.perform_async(in_progress_form.user_account_id, - template_id, - personalisation_details_single) - else - IcnJob.perform_async(veteran.icn, template_id, personalisation_details_single) - end + UserAccountJob.perform_async(in_progress_form.user_account_id, + template_id, + personalisation_details_single) elsif oldest_in_progress_form? template_id = VANotify::InProgressFormHelper::TEMPLATE_ID.fetch('generic') - if Flipper.enabled?(:va_notify_user_account_job) - UserAccountJob.perform_async(in_progress_form.user_account_id, - template_id, - personalisation_details_multiple) - else - IcnJob.perform_async(veteran.icn, template_id, personalisation_details_single) - end + UserAccountJob.perform_async(in_progress_form.user_account_id, + template_id, + personalisation_details_multiple) end rescue VANotify::Veteran::MPINameError, VANotify::Veteran::MPIError nil end - # rubocop:enable Metrics/MethodLength private diff --git a/modules/va_notify/app/sidekiq/va_notify/icn_job.rb b/modules/va_notify/app/sidekiq/va_notify/icn_job.rb deleted file mode 100644 index f09bcfa2138..00000000000 --- a/modules/va_notify/app/sidekiq/va_notify/icn_job.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module VANotify - class IcnJob - include Sidekiq::Job - include SentryLogging - sidekiq_options retry: 14 - - sidekiq_retries_exhausted do |msg, _ex| - job_id = msg['jid'] - job_class = msg['class'] - error_class = msg['error_class'] - error_message = msg['error_message'] - - message = "#{job_class} retries exhausted" - Rails.logger.error(message, { job_id:, error_class:, error_message: }) - StatsD.increment("sidekiq.jobs.#{job_class.underscore}.retries_exhausted") - end - - def perform(icn, template_id, personalisation = nil, api_key = Settings.vanotify.services.va_gov.api_key) - notify_client = VaNotify::Service.new(api_key) - - notify_client.send_email( - { - recipient_identifier: { id_value: icn, id_type: 'ICN' }, - template_id:, personalisation: - }.compact - ) - StatsD.increment('api.vanotify.icn_job.success') - rescue Common::Exceptions::BackendServiceException => e - handle_backend_exception(e, icn, template_id, personalisation) - end - - def handle_backend_exception(e, icn, template_id, personalisation) - if e.status_code == 400 - log_exception_to_sentry( - e, - { - args: { recipient_identifier: { id_value: icn, id_type: 'ICN' }, - template_id:, personalisation: } - }, - { error: :va_notify_icn_job } - ) - else - raise e - end - end - end -end diff --git a/modules/va_notify/app/sidekiq/va_notify/one_time_in_progress_reminder.rb b/modules/va_notify/app/sidekiq/va_notify/one_time_in_progress_reminder.rb index 998558cbfbb..2fc271acdcd 100644 --- a/modules/va_notify/app/sidekiq/va_notify/one_time_in_progress_reminder.rb +++ b/modules/va_notify/app/sidekiq/va_notify/one_time_in_progress_reminder.rb @@ -17,11 +17,7 @@ def perform(user_account_id, form_name, template_id, personalisation) user_account = UserAccount.find(user_account_id) InProgressRemindersSent.create!(user_account_id:, form_id: form_name) - if Flipper.enabled?(:va_notify_user_account_job) - VANotify::UserAccountJob.perform_async(user_account.id, template_id, personalisation) - else - VANotify::IcnJob.perform_async(user_account.icn, template_id, personalisation) - end + VANotify::UserAccountJob.perform_async(user_account.id, template_id, personalisation) end private diff --git a/modules/va_notify/spec/sidekiq/icn_job_spec.rb b/modules/va_notify/spec/sidekiq/icn_job_spec.rb deleted file mode 100644 index 61a67a0a614..00000000000 --- a/modules/va_notify/spec/sidekiq/icn_job_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -require 'sidekiq/testing' - -RSpec.describe VANotify::IcnJob, type: :worker do - let(:icn) { '1013062086V794840' } - let(:template_id) { 'template_id' } - - before do - allow_any_instance_of(VaNotify::Configuration).to receive(:base_path).and_return('http://fakeapi.com') - - allow(Settings.vanotify.services.va_gov).to receive(:api_key).and_return( - 'test-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' - ) - end - - describe '#perform' do - it 'sends an email using the template id' do - client = double - expect(VaNotify::Service).to receive(:new).with(Settings.vanotify.services.va_gov.api_key).and_return(client) - - expect(client).to receive(:send_email).with( - { - recipient_identifier: { - id_value: icn, - id_type: 'ICN' - }, - template_id: - } - ) - - expect(StatsD).to receive(:increment).with('api.vanotify.icn_job.success') - - described_class.new.perform(icn, template_id) - end - - it 'can use non-default api key' do - client = double - api_key = 'test-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy-zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz' - expect(VaNotify::Service).to receive(:new).with(api_key).and_return(client) - - expect(client).to receive(:send_email).with( - { - recipient_identifier: { - id_value: icn, - id_type: 'ICN' - }, - template_id:, - personalisation: {} - } - ) - personalization = {} - - described_class.new.perform(icn, template_id, personalization, api_key) - end - - context 'when vanotify returns a 400 error' do - it 'rescues and logs the error' do - VCR.use_cassette('va_notify/bad_request') do - job = described_class.new - expect(job).to receive(:log_exception_to_sentry).with( - instance_of(Common::Exceptions::BackendServiceException), - { - args: { - recipient_identifier: { - id_value: icn, - id_type: 'ICN' - }, - template_id:, - personalisation: nil - } - }, - { - error: :va_notify_icn_job - } - ) - - job.perform(icn, template_id) - end - end - end - end - - describe 'when job has failed' do - let(:error) { RuntimeError.new('an error occurred!') } - let(:msg) do - { - 'jid' => 123, - 'class' => described_class.to_s, - 'error_class' => 'RuntimeError', - 'error_message' => 'an error occurred!' - } - end - - it 'logs an error to the Rails console and increments StatsD counter' do - expect(Rails.logger).to receive(:error).with( - 'VANotify::IcnJob retries exhausted', - { - job_id: 123, - error_class: 'RuntimeError', - error_message: 'an error occurred!' - } - ) - expect(StatsD).to receive(:increment).with('sidekiq.jobs.va_notify/icn_job.retries_exhausted') - described_class.sidekiq_retries_exhausted_block.call(msg, error) - end - end -end diff --git a/rakelib/post911_sob.rake b/rakelib/post911_sob.rake new file mode 100644 index 00000000000..853260892aa --- /dev/null +++ b/rakelib/post911_sob.rake @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'post911_sob/dgib/client' + +namespace :post911_sob do + namespace :dgib do + desc 'Test connection between vets-api and DGIB claimant-service' + task :connect, %i[claimant_id base_url] => :environment do |_cmd, args| + args.with_defaults(base_url: Settings.dgi.post911_sob.claimants.url) + + # Allow for base url to be overridden for testing purposes + Settings.dgi.post911_sob.claimants.url = args[:base_url] + + client = Post911SOB::DGIB::Client.new(args[:claimant_id]) + client.get_entitlement_transferred_out + end + end +end diff --git a/spec/fixtures/pdf_fill/10-10CG/signed/overflow_extras.pdf b/spec/fixtures/pdf_fill/10-10CG/signed/overflow_extras.pdf index a256b92f496..d7555730b98 100644 Binary files a/spec/fixtures/pdf_fill/10-10CG/signed/overflow_extras.pdf and b/spec/fixtures/pdf_fill/10-10CG/signed/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-0538/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-0538/overflow_extras.pdf index ed6fa5d36e1..ca1d09137c2 100644 Binary files a/spec/fixtures/pdf_fill/21-0538/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-0538/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-0781/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-0781/overflow_extras.pdf index 40abc72af58..e5635515297 100644 Binary files a/spec/fixtures/pdf_fill/21-0781/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-0781/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-0781a/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-0781a/overflow_extras.pdf index 9859b605f2c..170d212e31f 100644 Binary files a/spec/fixtures/pdf_fill/21-0781a/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-0781a/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-4142/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-4142/overflow_extras.pdf index af2b0910574..08553309372 100644 Binary files a/spec/fixtures/pdf_fill/21-4142/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-4142/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-674/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-674/overflow_extras.pdf index ff4984e9238..f86aa03c737 100644 Binary files a/spec/fixtures/pdf_fill/21-674/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-674/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21-8940/overflow_extras.pdf b/spec/fixtures/pdf_fill/21-8940/overflow_extras.pdf index 26205865bb1..16451c879f5 100644 Binary files a/spec/fixtures/pdf_fill/21-8940/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21-8940/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21P-0969/overflow_extras.pdf b/spec/fixtures/pdf_fill/21P-0969/overflow_extras.pdf index 3f8a7e2025a..3b32fd56ee4 100644 Binary files a/spec/fixtures/pdf_fill/21P-0969/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21P-0969/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/21P-530V2/overflow_extras.pdf b/spec/fixtures/pdf_fill/21P-530V2/overflow_extras.pdf index 4744f819c93..d6b7b9bd6fe 100644 Binary files a/spec/fixtures/pdf_fill/21P-530V2/overflow_extras.pdf and b/spec/fixtures/pdf_fill/21P-530V2/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/26-1880/overflow_extras.pdf b/spec/fixtures/pdf_fill/26-1880/overflow_extras.pdf index bbb51be6390..4a0b556295c 100644 Binary files a/spec/fixtures/pdf_fill/26-1880/overflow_extras.pdf and b/spec/fixtures/pdf_fill/26-1880/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/28-1900/overflow_extras.pdf b/spec/fixtures/pdf_fill/28-1900/overflow_extras.pdf index 6573f728777..b95333ff675 100644 Binary files a/spec/fixtures/pdf_fill/28-1900/overflow_extras.pdf and b/spec/fixtures/pdf_fill/28-1900/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/28-8832/overflow_extras.pdf b/spec/fixtures/pdf_fill/28-8832/overflow_extras.pdf index 1347c6f7852..eca6e601591 100644 Binary files a/spec/fixtures/pdf_fill/28-8832/overflow_extras.pdf and b/spec/fixtures/pdf_fill/28-8832/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/5655/overflow_extras.pdf b/spec/fixtures/pdf_fill/5655/overflow_extras.pdf index 1840e04a1a8..7613c6996d8 100644 Binary files a/spec/fixtures/pdf_fill/5655/overflow_extras.pdf and b/spec/fixtures/pdf_fill/5655/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/686C-674/overflow_extras.pdf b/spec/fixtures/pdf_fill/686C-674/overflow_extras.pdf index bb36f3f7862..b50d9fc662a 100644 Binary files a/spec/fixtures/pdf_fill/686C-674/overflow_extras.pdf and b/spec/fixtures/pdf_fill/686C-674/overflow_extras.pdf differ diff --git a/spec/fixtures/pdf_fill/extras.pdf b/spec/fixtures/pdf_fill/extras.pdf index 45d3c9e2dfc..dd78cc1859c 100644 Binary files a/spec/fixtures/pdf_fill/extras.pdf and b/spec/fixtures/pdf_fill/extras.pdf differ diff --git a/spec/models/saved_claim/caregivers_assistance_claim_spec.rb b/spec/models/saved_claim/caregivers_assistance_claim_spec.rb index 8f31817c321..131cdd68d64 100644 --- a/spec/models/saved_claim/caregivers_assistance_claim_spec.rb +++ b/spec/models/saved_claim/caregivers_assistance_claim_spec.rb @@ -95,6 +95,141 @@ end end + describe 'validations' do + let(:claim) { build(:caregivers_assistance_claim) } + + before do + allow(Flipper).to receive(:enabled?).and_call_original + end + + context 'caregiver_retry_form_validation disabled' do + before do + allow(Flipper).to receive(:enabled?).with(:caregiver_retry_form_validation).and_return(false) + end + + context 'no validation errors' do + before do + allow(JSON::Validator).to receive(:fully_validate).and_return([]) + end + + it 'returns true' do + expect(claim.validate).to eq true + end + end + + context 'validation errors' do + it 'calls the parent method when the toggle is off' do + allow(claim).to receive(:form_matches_schema).and_call_original + + claim.validate + + expect(claim).to have_received(:form_matches_schema) + end + end + end + + context 'caregiver_retry_form_validation enabled' do + before do + allow(Flipper).to receive(:enabled?).with(:caregiver_retry_form_validation).and_return(true) + end + + context 'no validation errors' do + before do + allow(JSON::Validator).to receive(:fully_validate).and_return([]) + end + + it 'returns true' do + expect(Rails.logger).not_to receive(:info) + .with('Form validation succeeded on attempt 1/3') + + expect(claim.validate).to eq true + end + end + + context 'validation errors' do + let(:schema_errors) { [{ fragment: 'error' }] } + + context 'when JSON:Validator.fully_validate returns errors' do + before do + allow(JSON::Validator).to receive(:fully_validate).and_return(schema_errors) + end + + it 'adds validation errors to the form' do + expect(JSON::Validator).not_to receive(:fully_validate_schema) + + expect(Rails.logger).not_to receive(:info) + .with('Form validation succeeded on attempt 1/3') + + claim.validate + expect(claim.errors.full_messages).not_to be_empty + end + end + + context 'when JSON:Validator.fully_validate throws an exception' do + let(:exception_text) { 'Some exception' } + let(:exception) { StandardError.new(exception_text) } + + context '3 times' do + let(:schema) { 'schema_content' } + + before do + allow(VetsJsonSchema::SCHEMAS).to receive(:[]).and_return(schema) + allow(JSON::Validator).to receive(:fully_validate).and_raise(exception) + end + + it 'logs exceptions and raises exception' do + expect(Rails.logger).to receive(:warn) + .with("Retrying form validation due to error: #{exception_text} (Attempt 1/3)").once + expect(Rails.logger).not_to receive(:info) + .with('Form validation succeeded on attempt 1/3') + expect(Rails.logger).to receive(:warn) + .with("Retrying form validation due to error: #{exception_text} (Attempt 2/3)").once + expect(Rails.logger).to receive(:warn) + .with("Retrying form validation due to error: #{exception_text} (Attempt 3/3)").once + + expect(Rails.logger).to receive(:error) + .with('Error during form validation after maximimum retries', { error: exception.message, + backtrace: anything, schema: }) + + expect(PersonalInformationLog).to receive(:create).with( + data: { schema: schema, + parsed_form: claim.parsed_form, + params: { errors_as_objects: true } }, + error_class: 'SavedClaim FormValidationError' + ) + + expect { claim.validate }.to raise_error(exception.class, exception.message) + end + end + + context '1 time but succeeds after retrying' do + before do + # Throws exception the first time, returns empty array on subsequent calls + call_count = 0 + allow(JSON::Validator).to receive(:fully_validate).and_wrap_original do + call_count += 1 + if call_count == 1 + raise exception + else + [] + end + end + end + + it 'logs exception and validates succesfully after the retry' do + expect(Rails.logger).to receive(:warn) + .with("Retrying form validation due to error: #{exception_text} (Attempt 1/3)").once + expect(Rails.logger).to receive(:info) + .with('Form validation succeeded on attempt 2/3').once + + expect(claim.validate).to eq true + end + end + end + end + end + end + describe '#process_attachments!' do it 'raises a NotImplementedError' do expect { subject.process_attachments! }.to raise_error(NotImplementedError) diff --git a/spec/sidekiq/central_mail/submit_central_form686c_job_spec.rb b/spec/sidekiq/central_mail/submit_central_form686c_job_spec.rb index 4acb8c24cdb..4c27544abd8 100644 --- a/spec/sidekiq/central_mail/submit_central_form686c_job_spec.rb +++ b/spec/sidekiq/central_mail/submit_central_form686c_job_spec.rb @@ -304,7 +304,7 @@ end end - it 'logs the error to zsf and sends two emails with the 686C 674 templates' do + it 'logs the error to zsf and a combo email with 686c-674' do CentralMail::SubmitCentralForm686cJob.within_sidekiq_retries_exhausted_block( { 'args' => [claim.id, encrypted_vet_info, encrypted_user_struct] } ) do @@ -313,16 +313,7 @@ expect(SavedClaim::DependencyClaim).to receive(:find).with(claim.id).and_return(claim) expect(VANotify::EmailJob).to receive(:perform_async).with( 'vets.gov.user+228@gmail.com', - 'form21_686c_action_needed_email_template_id', - { - 'first_name' => 'MARK', - 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), - 'confirmation_number' => claim.confirmation_number - } - ) - expect(VANotify::EmailJob).to receive(:perform_async).with( - 'vets.gov.user+228@gmail.com', - 'form21_674_action_needed_email_template_id', + 'form21_686c_674_action_needed_email_template_id', { 'first_name' => 'MARK', 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), diff --git a/spec/sidekiq/decision_review/sc_status_updater_job_spec.rb b/spec/sidekiq/decision_review/sc_status_updater_job_spec.rb index 75e4e9f3625..1c1d0740663 100644 --- a/spec/sidekiq/decision_review/sc_status_updater_job_spec.rb +++ b/spec/sidekiq/decision_review/sc_status_updater_job_spec.rb @@ -2,528 +2,21 @@ require 'rails_helper' require 'decision_review_v1/service' +require 'sidekiq/decision_review/shared_examples_for_status_updater_jobs' RSpec.describe DecisionReview::ScStatusUpdaterJob, type: :job do subject { described_class } - let(:service) { instance_double(DecisionReviewV1::Service) } - - let(:guid1) { SecureRandom.uuid } - let(:guid2) { SecureRandom.uuid } - let(:guid3) { SecureRandom.uuid } - - let(:response_complete) do - response = JSON.parse(VetsJsonSchema::EXAMPLES.fetch('SC-SHOW-RESPONSE-200_V2').to_json) # deep copy - response['data']['attributes']['status'] = 'complete' - instance_double(Faraday::Response, body: response) - end - - let(:response_pending) do - instance_double(Faraday::Response, body: VetsJsonSchema::EXAMPLES.fetch('SC-SHOW-RESPONSE-200_V2')) - end - - let(:response_error) do - response = JSON.parse(VetsJsonSchema::EXAMPLES.fetch('SC-SHOW-RESPONSE-200_V2').to_json) # deep copy - response['data']['attributes']['status'] = 'error' - instance_double(Faraday::Response, body: response) - end - - let(:upload_response_vbms) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_upload_show_response_200.json')) - instance_double(Faraday::Response, body: response) - end - - let(:upload_response_processing) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_upload_show_response_200.json')) - response['data']['attributes']['status'] = 'processing' - instance_double(Faraday::Response, body: response) - end - - let(:upload_response_error) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_upload_show_response_200.json')) - response['data']['attributes']['status'] = 'error' - response['data']['attributes']['detail'] = 'Invalid PDF' - instance_double(Faraday::Response, body: response) - end - - before do - allow(DecisionReviewV1::Service).to receive(:new).and_return(service) - end + include_context 'status updater job context', SavedClaim::SupplementalClaim describe 'perform' do context 'with flag enabled', :aggregate_failures do before do Flipper.enable :decision_review_saved_claim_sc_status_updater_job_enabled - allow(StatsD).to receive(:increment) - end - - context 'SavedClaim records are present and there are no evidence uploads' do - before do - SavedClaim::SupplementalClaim.create(guid: guid1, form: '{}') - SavedClaim::SupplementalClaim.create(guid: guid2, form: '{}') - SavedClaim::SupplementalClaim.create(guid: guid3, form: '{}', delete_date: DateTime.new(2024, 2, 1).utc) - SavedClaim::HigherLevelReview.create(form: '{}') - SavedClaim::NoticeOfDisagreement.create(form: '{}') - end - - it 'updates SavedClaim::SupplementalClaim delete_date for completed records without a delete_date' do - expect(service).to receive(:get_supplemental_claim).with(guid1).and_return(response_complete) - expect(service).to receive(:get_supplemental_claim).with(guid2).and_return(response_pending) - expect(service).not_to receive(:get_supplemental_claim).with(guid3) - - expect(service).not_to receive(:get_higher_level_review) - expect(service).not_to receive(:get_notice_of_disagreement) - - frozen_time = DateTime.new(2024, 1, 1).utc - - Timecop.freeze(frozen_time) do - subject.new.perform - - claim1 = SavedClaim::SupplementalClaim.find_by(guid: guid1) - expect(claim1.delete_date).to eq frozen_time + 59.days - expect(claim1.metadata).to include 'complete' - expect(claim1.metadata_updated_at).to eq frozen_time - - claim2 = SavedClaim::SupplementalClaim.find_by(guid: guid2) - expect(claim2.delete_date).to be_nil - expect(claim2.metadata).to include 'pending' - expect(claim2.metadata_updated_at).to eq frozen_time - end - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.processing_records', 2).exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.delete_date_update').exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.status', tags: ['status:pending']) - .exactly(1).time - end - end - - context 'SavedClaim records are present with completed status in LH and have associated evidence uploads' do - let(:upload_id) { SecureRandom.uuid } - let(:upload_id2) { SecureRandom.uuid } - let(:upload_id3) { SecureRandom.uuid } - let(:upload_id4) { SecureRandom.uuid } - - before do - allow(Rails.logger).to receive(:info) - SavedClaim::SupplementalClaim.create(guid: guid1, form: '{}') - SavedClaim::SupplementalClaim.create(guid: guid2, form: '{}') - SavedClaim::SupplementalClaim.create(guid: guid3, form: '{}') - - appeal_submission = create(:appeal_submission, submitted_appeal_uuid: guid1) - create(:appeal_submission_upload, appeal_submission:, lighthouse_upload_id: upload_id) - - appeal_submission2 = create(:appeal_submission, submitted_appeal_uuid: guid2) - create(:appeal_submission_upload, appeal_submission: appeal_submission2, lighthouse_upload_id: upload_id2) - - # One upload vbms, other one still processing - appeal_submission3 = create(:appeal_submission, submitted_appeal_uuid: guid3) - create(:appeal_submission_upload, appeal_submission: appeal_submission3, lighthouse_upload_id: upload_id3) - create(:appeal_submission_upload, appeal_submission: appeal_submission3, lighthouse_upload_id: upload_id4) - end - - it 'only sets delete_date for SavedClaim::SupplementalClaim with all attachments in vbms status' do - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id) - .and_return(upload_response_vbms) - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id2) - .and_return(upload_response_processing) - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id3) - .and_return(upload_response_vbms) - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id4) - .and_return(upload_response_processing) - - expect(service).to receive(:get_supplemental_claim).with(guid1).and_return(response_complete) - expect(service).to receive(:get_supplemental_claim).with(guid2).and_return(response_complete) - expect(service).to receive(:get_supplemental_claim).with(guid3).and_return(response_complete) - - frozen_time = DateTime.new(2024, 1, 1).utc - - Timecop.freeze(frozen_time) do - subject.new.perform - - claim1 = SavedClaim::SupplementalClaim.find_by(guid: guid1) - expect(claim1.delete_date).to eq frozen_time + 59.days - expect(claim1.metadata_updated_at).to eq frozen_time - expect(claim1.metadata).to include 'complete' - expect(claim1.metadata).to include 'vbms' - - claim2 = SavedClaim::SupplementalClaim.find_by(guid: guid2) - expect(claim2.delete_date).to be_nil - expect(claim2.metadata_updated_at).to eq frozen_time - expect(claim2.metadata).to include 'complete' - expect(claim2.metadata).to include 'processing' - - claim3 = SavedClaim::SupplementalClaim.find_by(guid: guid3) - expect(claim3.delete_date).to be_nil - expect(claim3.metadata_updated_at).to eq frozen_time - - metadata3 = JSON.parse(claim3.metadata) - expect(metadata3['status']).to eq 'complete' - expect(metadata3['uploads'].pluck('id', 'status')) - .to contain_exactly([upload_id3, 'vbms'], [upload_id4, 'processing']) - end - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.processing_records', 3).exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.delete_date_update').exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.status', tags: ['status:complete']) - .exactly(2).times - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_upload.status', tags: ['status:vbms']) - .exactly(2).times - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_upload.status', tags: ['status:processing']) - .exactly(2).times - expect(Rails.logger).not_to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob evidence status error', anything) - end - end - - context 'SavedClaim records are present with completed status in LH and have associated secondary forms' do - let(:benefits_intake_service) { instance_double(BenefitsIntake::Service) } - let!(:secondary_form1) { create(:secondary_appeal_form4142, guid: SecureRandom.uuid) } - let!(:secondary_form2) { create(:secondary_appeal_form4142, guid: SecureRandom.uuid) } - let!(:secondary_form3) { create(:secondary_appeal_form4142, guid: SecureRandom.uuid) } - let!(:secondary_form_with_delete_date) do - create(:secondary_appeal_form4142, guid: SecureRandom.uuid, delete_date: 10.days.from_now) - end - let!(:saved_claim1) do - SavedClaim::SupplementalClaim.create(guid: secondary_form1.appeal_submission.submitted_appeal_uuid, - form: '{}') - end - let!(:saved_claim2) do - SavedClaim::SupplementalClaim.create(guid: secondary_form2.appeal_submission.submitted_appeal_uuid, - form: '{}') - end - let!(:saved_claim3) do - SavedClaim::SupplementalClaim.create(guid: secondary_form3.appeal_submission.submitted_appeal_uuid, - form: '{}') - end - let!(:saved_claim4) do - SavedClaim::SupplementalClaim - .create(guid: secondary_form_with_delete_date.appeal_submission.submitted_appeal_uuid, form: '{}') - end - - let(:upload_response_4142_vbms) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_4142_show_response_200.json')) - instance_double(Faraday::Response, body: response) - end - - let(:upload_response_4142_processing) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_4142_show_response_200.json')) - response['data']['attributes']['status'] = 'processing' - instance_double(Faraday::Response, body: response) - end - - let(:upload_response_4142_error) do - response = JSON.parse(File.read('spec/fixtures/supplemental_claims/SC_4142_show_response_200.json')) - response['data']['attributes']['status'] = 'error' - response['data']['attributes']['detail'] = 'Invalid PDF' - instance_double(Faraday::Response, body: response) - end - - before do - allow(DecisionReviewV1::Service).to receive(:new).and_return(service) - allow(BenefitsIntake::Service).to receive(:new).and_return(benefits_intake_service) - allow(service).to receive(:get_supplemental_claim).with(saved_claim1.guid).and_return(response_complete) - allow(service).to receive(:get_supplemental_claim).with(saved_claim2.guid).and_return(response_complete) - allow(service).to receive(:get_supplemental_claim).with(saved_claim3.guid).and_return(response_complete) - allow(service).to receive(:get_supplemental_claim).with(saved_claim4.guid).and_return(response_complete) - - allow(StatsD).to receive(:increment) - allow(Rails.logger).to receive(:info) - end - - it 'does NOT check status for 4142 records that already have a delete_date' do - expect(benefits_intake_service).to receive(:get_status).with(uuid: secondary_form1.guid) - expect(benefits_intake_service).to receive(:get_status).with(uuid: secondary_form2.guid) - expect(benefits_intake_service).to receive(:get_status).with(uuid: secondary_form3.guid) - expect(benefits_intake_service).not_to receive(:get_status) - .with(uuid: secondary_form_with_delete_date.guid) - subject.new.perform - end - - context 'updating 4142 information' do - let(:frozen_time) { DateTime.new(2024, 1, 1).utc } - - before do - allow(benefits_intake_service).to receive(:get_status) - .with(uuid: secondary_form1.guid).and_return(upload_response_4142_vbms) - allow(benefits_intake_service).to receive(:get_status) - .with(uuid: secondary_form2.guid).and_return(upload_response_4142_processing) - allow(benefits_intake_service).to receive(:get_status) - .with(uuid: secondary_form3.guid).and_return(upload_response_4142_error) - end - - it 'updates the status and sets delete_date if appropriate' do - Timecop.freeze(frozen_time) do - subject.new.perform - end - expect(secondary_form1.reload.status).to include('vbms') - expect(secondary_form1.reload.status_updated_at).to eq frozen_time - expect(secondary_form1.reload.delete_date).to eq frozen_time + 59.days - - expect(secondary_form2.reload.status).to include('processing') - expect(secondary_form2.reload.status_updated_at).to eq frozen_time - expect(secondary_form2.reload.delete_date).to be_nil - - expect(secondary_form3.reload.status).to include('error') - expect(secondary_form3.reload.status_updated_at).to eq frozen_time - expect(secondary_form3.reload.delete_date).to be_nil - end - - it 'logs ands increments metrics for updates to the 4142 status' do - Timecop.freeze(frozen_time) do - subject.new.perform - end - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_secondary_form.delete_date_update') - .exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_secondary_form.status', tags: ['status:vbms']) - .exactly(1).time - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_secondary_form.status', - tags: ['status:processing']) - .exactly(1).time - - expect(Rails.logger).to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob secondary form status error', anything) - expect(StatsD).to have_received(:increment) - .with('silent_failure', tags: ['service:supplemental-claims-4142', - 'function: PDF submission to Lighthouse']) - .exactly(1).time - end - - context 'when the 4142 status is unchanged' do - let(:previous_status) do - { - 'status' => 'processing' - } - end - - before do - secondary_form2.update!(status: previous_status.to_json, status_updated_at: frozen_time - 3.days) - end - - it 'does not log or increment metrics for a status change' do - Timecop.freeze(frozen_time) do - subject.new.perform - end - - expect(secondary_form2.reload.status_updated_at).to eq frozen_time - expect(StatsD).not_to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_secondary_form.status', - tags: ['status:processing']) - end - end - - context 'when at least one secondary form is not in vbms status' do - it 'does not set the delete_date for the related SavedCalim::SupplementlClaim' do - Timecop.freeze(frozen_time) do - subject.new.perform - end - - expect(saved_claim1.reload.delete_date).to eq frozen_time + 59.days - expect(saved_claim2.delete_date).to be_nil - end - end - end - - context 'with 4142 flag disabled' do - before do - Flipper.disable :decision_review_track_4142_submissions - end - - it 'does not query SecondaryAppealForm records' do - expect(SecondaryAppealForm).not_to receive(:where) - - subject.new.perform - end - end end - context 'SavedClaim record with previous metadata' do - before do - allow(Rails.logger).to receive(:info) - end - - let(:guid4) { SecureRandom.uuid } - let(:guid5) { SecureRandom.uuid } - - let(:upload_id) { SecureRandom.uuid } - let(:upload_id2) { SecureRandom.uuid } - let(:upload_id3) { SecureRandom.uuid } - - let(:metadata1) do - { - 'status' => 'submitted', - 'uploads' => [ - { - 'status' => 'error', - 'detail' => 'Invalid PDF', - 'id' => upload_id - } - ] - } - end - - let(:metadata2) do - { - 'status' => 'submitted', - 'uploads' => [ - { - 'status' => 'pending', - 'detail' => nil, - 'id' => upload_id2 - }, - { - 'status' => 'processing', - 'detail' => nil, - 'id' => upload_id3 - } - ] - } - end - - it 'does not increment metrics for unchanged form status or existing final statuses' do - SavedClaim::SupplementalClaim.create(guid: guid1, form: '{}', metadata: '{"status":"error","uploads":[]}') - SavedClaim::SupplementalClaim.create(guid: guid2, form: '{}', metadata: '{"status":"submitted","uploads":[]}') - SavedClaim::SupplementalClaim.create(guid: guid3, form: '{}', metadata: '{"status":"pending","uploads":[]}') - SavedClaim::SupplementalClaim.create(guid: guid4, form: '{}', metadata: '{"status":"complete,"uploads":[]}') - SavedClaim::SupplementalClaim.create(guid: guid5, form: '{}', metadata: '{"status":"DR_404,"uploads":[]}') - - expect(service).not_to receive(:get_supplemental_claim).with(guid1) - expect(service).to receive(:get_supplemental_claim).with(guid2).and_return(response_error) - expect(service).to receive(:get_supplemental_claim).with(guid3).and_return(response_pending) - expect(service).not_to receive(:get_supplemental_claim).with(guid4) - expect(service).not_to receive(:get_supplemental_claim).with(guid5) - - subject.new.perform - - claim2 = SavedClaim::SupplementalClaim.find_by(guid: guid2) - expect(claim2.delete_date).to be_nil - expect(claim2.metadata).to include 'error' - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.status', tags: ['status:error']) - .exactly(1).time - expect(StatsD).not_to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.status', tags: ['status:pending']) - - expect(Rails.logger).not_to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob form status error', guid: guid1) - expect(Rails.logger).to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob form status error', guid: guid2) - expect(StatsD).to have_received(:increment) - .with('silent_failure', tags: ['service:supplemental-claims', - 'function: form submission to Lighthouse']) - .exactly(1).time - end - - it 'does not increment metrics for unchanged evidence status or existing final statuses' do - SavedClaim::SupplementalClaim.create(guid: guid1, form: '{}', metadata: metadata1.to_json) - appeal_submission = create(:appeal_submission, submitted_appeal_uuid: guid1) - create(:appeal_submission_upload, appeal_submission:, lighthouse_upload_id: upload_id) - - SavedClaim::SupplementalClaim.create(guid: guid2, form: '{}', metadata: metadata2.to_json) - appeal_submission2 = create(:appeal_submission, submitted_appeal_uuid: guid2) - create(:appeal_submission_upload, appeal_submission: appeal_submission2, lighthouse_upload_id: upload_id2) - create(:appeal_submission_upload, appeal_submission: appeal_submission2, lighthouse_upload_id: upload_id3) - - expect(service).to receive(:get_supplemental_claim).with(guid1).and_return(response_pending) - expect(service).to receive(:get_supplemental_claim).with(guid2).and_return(response_error) - - expect(service).not_to receive(:get_supplemental_claim_upload).with(guid: upload_id) - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id2) - .and_return(upload_response_error) - expect(service).to receive(:get_supplemental_claim_upload).with(guid: upload_id3) - .and_return(upload_response_processing) - - subject.new.perform - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_upload.status', tags: ['status:error']) - .exactly(1).times - expect(StatsD).not_to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater_upload.status', tags: ['status:processing']) - - expect(Rails.logger).not_to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob evidence status error', - guid: anything, lighthouse_upload_id: upload_id, detail: anything) - expect(Rails.logger).to have_received(:info) - .with('DecisionReview::SavedClaimScStatusUpdaterJob evidence status error', - guid: guid2, lighthouse_upload_id: upload_id2, detail: 'Invalid PDF') - expect(StatsD).to have_received(:increment) - .with('silent_failure', tags: ['service:supplemental-claims', - 'function: evidence submission to Lighthouse']) - .exactly(1).time - end - end - - context 'Retrieving SavedClaim records fails' do - before do - allow(SavedClaim::SupplementalClaim).to receive(:where).and_raise(ActiveRecord::ConnectionTimeoutError) - allow(Rails.logger).to receive(:error) - end - - it 'rescues the error and logs' do - subject.new.perform - - expect(Rails.logger).to have_received(:error) - .with('DecisionReview::SavedClaimScStatusUpdaterJob error', anything) - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.error').once - end - end - - context 'an error occurs while processing form, attachments, or secondary form' do - before do - SavedClaim::SupplementalClaim.create(guid: guid1, form: '{}') - SavedClaim::SupplementalClaim.create(guid: guid2, form: '{}') - - allow(service).to receive(:get_supplemental_claim).and_raise(exception) - allow(Rails.logger).to receive(:error) - end - - context 'and it is a temporary error' do - let(:exception) { DecisionReviewV1::ServiceException.new(key: 'DR_504') } - - it 'handles request errors and increments the statsd metric' do - allow(service).to receive(:get_supplemental_claim).and_raise(DecisionReviewV1::ServiceException) - - subject.new.perform - - expect(StatsD).to have_received(:increment) - .with('worker.decision_review.saved_claim_sc_status_updater.error').exactly(2).times - end - end - - context 'and it is a 404 error' do - let(:exception) { DecisionReviewV1::ServiceException.new(key: 'DR_404') } - - it 'updates the status of the record' do - subject.new.perform - - sc1 = SavedClaim::SupplementalClaim.find_by(guid: guid1) - metadata1 = JSON.parse(sc1.metadata) - expect(metadata1['status']).to eq 'DR_404' - - sc2 = SavedClaim::SupplementalClaim.find_by(guid: guid2) - metadata2 = JSON.parse(sc2.metadata) - expect(metadata2['status']).to eq 'DR_404' - - expect(Rails.logger).to have_received(:error) - .with('DecisionReview::SavedClaimScStatusUpdaterJob error', { guid: anything, message: anything }) - .exactly(2).times - end - end - end + include_examples 'status updater job with base forms', SavedClaim::SupplementalClaim + include_examples 'status updater job when forms include evidence', SavedClaim::SupplementalClaim end context 'with flag disabled' do @@ -531,8 +24,8 @@ Flipper.disable :decision_review_saved_claim_sc_status_updater_job_enabled end - it 'does not query SavedClaim::HigherLevelReview records' do - expect(SavedClaim::HigherLevelReview).not_to receive(:where) + it 'does not query SavedClaim::SupplementalClaim records' do + expect(SavedClaim::SupplementalClaim).not_to receive(:where) subject.new.perform end diff --git a/spec/support/vcr_cassettes/bgs/person_web_service/find_by_ssn.yml b/spec/support/vcr_cassettes/bgs/person_web_service/find_by_ssn.yml new file mode 100644 index 00000000000..52cdc548ad4 --- /dev/null +++ b/spec/support/vcr_cassettes/bgs/person_web_service/find_by_ssn.yml @@ -0,0 +1,900 @@ +--- +http_interactions: +- request: + method: get + uri: "/PersonWebServiceBean/PersonWebService?WSDL" + body: + encoding: US-ASCII + string: '' + headers: + Host: + - ".vba.va.gov" + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 21 Nov 2024 21:19:08 GMT + Server: + - Apache + X-Frame-Options: + - SAMEORIGIN + Transfer-Encoding: + - chunked + Content-Type: + - text/xml;charset=utf-8 + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + body: + encoding: UTF-8 + string: |- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + recorded_at: Thu, 21 Nov 2024 21:19:08 GMT +- request: + method: post + uri: "/PersonWebServiceBean/PersonWebService" + body: + encoding: UTF-8 + string: |- + + + VAgovAPI + + + 10.0.0.205 + 281 + VAgovAPI + 796378881 + 796378881 + + + 796378881 + headers: + Host: + - ".vba.va.gov" + Soapaction: + - '"findPersonBySSN"' + Content-Type: + - text/xml;charset=UTF-8 + Content-Length: + - '1269' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 21 Nov 2024 21:19:08 GMT + Server: + - Apache + X-Frame-Options: + - SAMEORIGIN + Transfer-Encoding: + - chunked + Content-Type: + - text/xml; charset=utf-8 + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + body: + encoding: UTF-8 + string: rO0ABXdTAB13ZWJsb2dpYy5hcHAuQ29ycG9yYXRlRGF0YUVBUgAAANYAAAAjd2VibG9naWMud29ya2FyZWEuU3RyaW5nV29ya0NvbnRleHQABTMuNS4wAAA=SALINASKS1954-12-15T00:00:00-06:002022-10-03T15:35:53-05:002022-10-03T15:35:53-05:00YCORP0valid@somedomain.comN796378881JESSE-2M2024-11-21T15:18:35-06:00VAgovAPIlighthouse-vets-apilighthouse-vets-api281VAGOVAPI - + BUPD218935360UVAGOVAPIGRAY-1ADSUSEONLY-1YNUU600045026Person600045026NN796378881031781Y + recorded_at: Thu, 21 Nov 2024 21:19:08 GMT +recorded_with: VCR 6.3.1