diff --git a/app/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email.rb b/app/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email.rb
new file mode 100644
index 00000000000..2d6f2100bfa
--- /dev/null
+++ b/app/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'va_notify/service'
+
+module EVSS
+ module DisabilityCompensationForm
+ class Form0781DocumentUploadFailureEmail < Job
+ STATSD_METRIC_PREFIX = 'api.form_526.veteran_notifications.form0781_upload_failure_email'
+
+ # retry for one day
+ sidekiq_options retry: 14
+
+ sidekiq_retries_exhausted do |msg, _ex|
+ job_id = msg['jid']
+ error_class = msg['error_class']
+ error_message = msg['error_message']
+ timestamp = Time.now.utc
+ form526_submission_id = msg['args'].first
+
+ # Job status records are upserted in the JobTracker module
+ # when the retryable_error_handler is called
+ form_job_status = Form526JobStatus.find_by(job_id:)
+ bgjob_errors = form_job_status.bgjob_errors || {}
+ new_error = {
+ "#{timestamp.to_i}": {
+ caller_method: __method__.to_s,
+ timestamp:,
+ form526_submission_id:
+ }
+ }
+ form_job_status.update(
+ status: Form526JobStatus::STATUS[:exhausted],
+ bgjob_errors: bgjob_errors.merge(new_error)
+ )
+
+ Rails.logger.warn(
+ 'Form0781DocumentUploadFailureEmail retries exhausted',
+ {
+ job_id:,
+ timestamp:,
+ form526_submission_id:,
+ error_class:,
+ error_message:
+ }
+ )
+
+ StatsD.increment("#{STATSD_METRIC_PREFIX}.exhausted")
+ rescue => e
+ Rails.logger.error(
+ 'Failure in Form0781DocumentUploadFailureEmail#sidekiq_retries_exhausted',
+ {
+ job_id:,
+ messaged_content: e.message,
+ submission_id: form526_submission_id,
+ pre_exhaustion_failure: {
+ error_class:,
+ error_message:
+ }
+ }
+ )
+ raise e
+ end
+
+ def perform(form526_submission_id)
+ form526_submission = Form526Submission.find(form526_submission_id)
+
+ with_tracking('Form0781DocumentUploadFailureEmail', form526_submission.saved_claim_id, form526_submission_id) do
+ notify_client = VaNotify::Service.new(Settings.vanotify.services.benefits_disability.api_key)
+
+ email_address = form526_submission.veteran_email_address
+ first_name = form526_submission.get_first_name
+ date_submitted = form526_submission.format_creation_time_for_mailers
+
+ notify_client.send_email(
+ email_address:,
+ template_id: mailer_template_id,
+ personalisation: {
+ first_name:,
+ date_submitted:
+ }
+ )
+
+ StatsD.increment("#{STATSD_METRIC_PREFIX}.success")
+ end
+ rescue => e
+ retryable_error_handler(e)
+ end
+
+ private
+
+ def retryable_error_handler(error)
+ # Needed to log the error properly in the Sidekiq::Form526JobStatusTracker::JobTracker,
+ # which is included near the top of this job's inheritance tree in EVSS::DisabilityCompensationForm::JobStatus
+ super(error)
+ raise error
+ end
+
+ def mailer_template_id
+ Settings.vanotify.services
+ .benefits_disability.template_id.form0781_upload_failure_notification_template_id
+ end
+ end
+ end
+end
diff --git a/app/sidekiq/evss/disability_compensation_form/submit_form0781.rb b/app/sidekiq/evss/disability_compensation_form/submit_form0781.rb
index eac3530139f..48d73e938d3 100644
--- a/app/sidekiq/evss/disability_compensation_form/submit_form0781.rb
+++ b/app/sidekiq/evss/disability_compensation_form/submit_form0781.rb
@@ -66,6 +66,10 @@ class SubmitForm0781 < Job
StatsD.increment("#{STATSD_KEY_PREFIX}.exhausted")
+ if Flipper.enabled?(:form526_send_0781_failure_notification)
+ EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail.perform_async(form526_submission_id)
+ end
+
::Rails.logger.warn(
'Submit Form 0781 Retries exhausted',
{ job_id:, error_class:, error_message:, timestamp:, form526_submission_id: }
diff --git a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
index a4b69c3d7ce..2602ed8ff6b 100644
--- a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
+++ b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
@@ -29,7 +29,6 @@ class BenefitsIntakeClaimError < StandardError; end
StatsD.increment("#{STATSD_KEY_PREFIX}.exhausted")
end
- # rubocop:disable Metrics/MethodLength
def perform(saved_claim_id)
@claim = SavedClaim.find(saved_claim_id)
@@ -39,20 +38,11 @@ def perform(saved_claim_id)
else
process_record(@claim)
end
- @attachment_paths = @claim.persistent_attachments.map do |record|
- process_record(record)
- end
+ @attachment_paths = @claim.persistent_attachments.map { |record| process_record(record) }
create_form_submission_attempt
- payload = {
- upload_url: @lighthouse_service.location,
- file: split_file_and_path(@pdf_path),
- metadata: generate_metadata.to_json,
- attachments: @attachment_paths.map(&method(:split_file_and_path))
- }
-
- response = @lighthouse_service.upload_doc(**payload)
+ response = @lighthouse_service.upload_doc(**lighthouse_service_upload_payload)
raise BenefitsIntakeClaimError, response.body unless response.success?
Rails.logger.info('Lighthouse::SubmitBenefitsIntakeClaim succeeded', generate_log_details)
@@ -65,7 +55,6 @@ def perform(saved_claim_id)
cleanup_file_paths
end
- # rubocop:enable Metrics/MethodLength
def generate_metadata
form = @claim.parsed_form
veteran_full_name = form['veteranFullName']
@@ -84,7 +73,6 @@ def generate_metadata
SimpleFormsApiSubmission::MetadataValidator.validate(metadata, zip_code_is_us_based: check_zipcode(address))
end
- # rubocop:disable Metrics/MethodLength
def process_record(record, timestamp = nil, form_id = nil)
pdf_path = record.to_pdf
stamped_path1 = PDFUtilities::DatestampPdf.new(pdf_path).run(text: 'VA.GOV', x: 5, y: 5, timestamp:)
@@ -95,29 +83,41 @@ def process_record(record, timestamp = nil, form_id = nil)
text_only: true
)
if form_id.present? && ['21P-530V2'].include?(form_id)
- PDFUtilities::DatestampPdf.new(stamped_path2).run(
- text: 'Application Submitted on va.gov',
- x: 425,
- y: 675,
- text_only: true, # passing as text only because we override how the date is stamped in this instance
- timestamp:,
- page_number: 5,
- size: 9,
- template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf",
- multistamp: true
- )
+ stamped_pdf_with_form(form_id, stamped_path2, timestamp)
else
stamped_path2
end
end
- # rubocop:enable Metrics/MethodLength
def split_file_and_path(path)
{ file: path, file_name: path.split('/').last }
end
private
+ def lighthouse_service_upload_payload
+ {
+ upload_url: @lighthouse_service.location,
+ file: split_file_and_path(@pdf_path),
+ metadata: generate_metadata.to_json,
+ attachments: @attachment_paths.map(&method(:split_file_and_path))
+ }
+ end
+
+ def stamped_pdf_with_form(form_id, path, timestamp)
+ PDFUtilities::DatestampPdf.new(path).run(
+ text: 'Application Submitted on va.gov',
+ x: 425,
+ y: 675,
+ text_only: true, # passing as text only because we override how the date is stamped in this instance
+ timestamp:,
+ page_number: 5,
+ size: 9,
+ template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf",
+ multistamp: true
+ )
+ end
+
def generate_log_details(e = nil)
details = {
claim_id: @claim.id,
diff --git a/config/features.yml b/config/features.yml
index ef4f5f670ca..830c2729701 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -588,6 +588,10 @@ features:
actor_type: user
description: Enables enqueuing a Form4142DocumentUploadFailureEmail if a SubmitForm4142Job job exhausts its retries
enable_in_development: true
+ form526_send_0781_failure_notification:
+ actor_type: user
+ description: Enables enqueuing a Form0781DocumentUploadFailureEmail if a SubmitForm0781Job job exhausts its retries
+ enable_in_development: true
form0994_confirmation_email:
actor_type: user
description: Enables form 0994 email submission confirmation (VaNotify)
@@ -1173,6 +1177,13 @@ features:
show_meb_service_history_categorize_disagreement:
actor_type: user
enable_in_development: false
+ show_meb_5490_maintenance_alert:
+ actor_type: user
+ description: Displays an alert to users on 5490 intro page that the Backend Service is Down.
+ enable_in_development: false
+ meb_1606_30_automation:
+ actor_type: user
+ description: Enables MEB form to handle Chapter 1606/30 forms as well as Chapter 33.
meb_exclusion_period_enabled:
actor_type: user
description: enables exclusion period checks
diff --git a/config/settings.yml b/config/settings.yml
index 556f14ddb44..0b00e0d1ba4 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -1356,6 +1356,7 @@ vanotify:
template_id:
form526_document_upload_failure_notification_template_id: form526_document_upload_failure_notification_template_id
form4142_upload_failure_notification_template_id: form4142_upload_failure_notification_template_id
+ form0781_upload_failure_notification_template_id: form0781_upload_failure_notification_template_id
ivc_champva:
api_key: fake_secret
template_id:
diff --git a/lib/bgs/form674.rb b/lib/bgs/form674.rb
index 6f18f071228..977e40c5d9e 100644
--- a/lib/bgs/form674.rb
+++ b/lib/bgs/form674.rb
@@ -14,18 +14,17 @@ module BGS
class Form674
include SentryLogging
- attr_reader :user, :saved_claim
+ attr_reader :user, :saved_claim, :proc_id
def initialize(user, saved_claim)
@user = user
+ @proc_id = vnp_proc_id
@saved_claim = saved_claim
@end_product_name = '130 - Automated School Attendance 674'
@end_product_code = '130SCHATTEBN'
end
- # rubocop:disable Metrics/MethodLength
def submit(payload)
- proc_id = create_proc_id_and_form
veteran = VnpVeteran.new(proc_id:, payload:, user:, claim_type: '130SCHATTEBN').create
process_relationships(proc_id, veteran, payload)
@@ -38,16 +37,7 @@ def submit(payload)
# temporary logging to troubleshoot
log_message_to_sentry("#{proc_id} - #{@end_product_code}", :warn, '', { team: 'vfs-ebenefits' })
- benefit_claim_record = BenefitClaim.new(
- args: {
- vnp_benefit_claim: vnp_benefit_claim_record,
- veteran:,
- user:,
- proc_id:,
- end_product_name: @end_product_name,
- end_product_code: @end_product_code
- }
- ).create
+ benefit_claim_record = BenefitClaim.new(args: benefit_claim_args(vnp_benefit_claim_record, veteran)).create
begin
vnp_benefit_claim.update(benefit_claim_record, vnp_benefit_claim_record)
@@ -60,19 +50,23 @@ def submit(payload)
bgs_service.update_proc(proc_id, proc_state: 'MANUAL_VAGOV')
rescue
- Rails.logger.warning('BGS::Form674.submit failed after creating benefit claim in BGS',
- {
- user_uuid: user.uuid,
- saved_claim_id: saved_claim.id,
- icn: user.icn,
- error: e.message
- })
+ log_submit_failure(error)
end
end
- # rubocop:enable Metrics/MethodLength
private
+ def benefit_claim_args(vnp_benefit_claim_record, veteran)
+ {
+ vnp_benefit_claim: vnp_benefit_claim_record,
+ veteran:,
+ user:,
+ proc_id:,
+ end_product_name: @end_product_name,
+ end_product_code: @end_product_code
+ }
+ end
+
def process_relationships(proc_id, veteran, payload)
dependent = DependentHigherEdAttendance.new(proc_id:, payload:, user: @user).create
@@ -96,7 +90,7 @@ def process_674(proc_id, dependent, payload)
).create
end
- def create_proc_id_and_form
+ def vnp_proc_id
vnp_response = bgs_service.create_proc(proc_state: 'MANUAL_VAGOV')
bgs_service.create_proc_form(
vnp_response[:vnp_proc_id],
@@ -130,6 +124,16 @@ def set_claim_type(proc_state)
end
end
+ def log_submit_failure(error)
+ Rails.logger.warning('BGS::Form674.submit failed after creating benefit claim in BGS',
+ {
+ user_uuid: user.uuid,
+ saved_claim_id: saved_claim.id,
+ icn: user.icn,
+ error: error.message
+ })
+ end
+
def bgs_service
BGS::Service.new(@user)
end
diff --git a/lib/bgs/marriages.rb b/lib/bgs/marriages.rb
index 7fb16382825..348311b8413 100644
--- a/lib/bgs/marriages.rb
+++ b/lib/bgs/marriages.rb
@@ -22,7 +22,6 @@ def create_all
private
- # rubocop:disable Metrics/MethodLength
def report_marriage_history(type)
@dependents_application[type].each do |former_spouse|
former_marriage = BGSDependents::MarriageHistory.new(former_spouse)
@@ -35,22 +34,25 @@ def report_marriage_history(type)
participant,
'Spouse',
'Ex-Spouse',
- {
- type:,
- begin_date: marriage_info['start_date'],
- marriage_country: marriage_info['marriage_country'],
- marriage_state: marriage_info['marriage_state'],
- marriage_city: marriage_info['marriage_city'],
- divorce_state: marriage_info['divorce_state'],
- divorce_city: marriage_info['divorce_city'],
- divorce_country: marriage_info['divorce_country'],
- end_date: marriage_info['end_date'],
- marriage_termination_type_code: marriage_info['marriage_termination_type_code']
- }
+ spouse_dependent_optional_fields(type, marriage_info)
)
end
end
- # rubocop:enable Metrics/MethodLength
+
+ def spouse_dependent_optional_fields(type, marriage_info)
+ {
+ type:,
+ begin_date: marriage_info['start_date'],
+ marriage_country: marriage_info['marriage_country'],
+ marriage_state: marriage_info['marriage_state'],
+ marriage_city: marriage_info['marriage_city'],
+ divorce_state: marriage_info['divorce_state'],
+ divorce_city: marriage_info['divorce_city'],
+ divorce_country: marriage_info['divorce_country'],
+ end_date: marriage_info['end_date'],
+ marriage_termination_type_code: marriage_info['marriage_termination_type_code']
+ }
+ end
def add_spouse
spouse = BGSDependents::Spouse.new(@dependents_application)
diff --git a/lib/bgs/power_of_attorney_verifier.rb b/lib/bgs/power_of_attorney_verifier.rb
index 41e243202d7..3b4e5522109 100644
--- a/lib/bgs/power_of_attorney_verifier.rb
+++ b/lib/bgs/power_of_attorney_verifier.rb
@@ -19,7 +19,7 @@ def previous_poa_code
@previous_poa_code ||= @veteran.previous_power_of_attorney.try(:code)
end
- def verify(user) # rubocop:disable Metrics/MethodLength
+ def verify(user)
reps = Veteran::Service::Representative.all_for_user(first_name: user.first_name,
last_name: user.last_name)
raise ::Common::Exceptions::Unauthorized, detail: 'VSO Representative Not Found' if reps.blank?
@@ -28,17 +28,7 @@ def verify(user) # rubocop:disable Metrics/MethodLength
if user.middle_name.blank?
raise ::Common::Exceptions::Unauthorized, detail: 'Ambiguous VSO Representative Results'
else
- middle_initial = user.middle_name[0]
- reps = Veteran::Service::Representative.all_for_user(first_name: user.first_name,
- last_name: user.last_name,
- middle_initial:)
-
- if reps.blank? || reps.count > 1
- reps = Veteran::Service::Representative.all_for_user(first_name: user.first_name,
- last_name: user.last_name,
- poa_code: current_poa_code)
- end
-
+ reps = representatives_with_middle_names_for_user(user)
raise ::Common::Exceptions::Unauthorized, detail: 'VSO Representative Not Found' if reps.blank?
raise ::Common::Exceptions::Unauthorized, detail: 'Ambiguous VSO Representative Results' if reps.count > 1
end
@@ -55,5 +45,21 @@ def verify(user) # rubocop:disable Metrics/MethodLength
def matches(veteran_poa_code, representative)
representative.poa_codes.include?(veteran_poa_code)
end
+
+ private
+
+ def representatives_with_middle_names_for_user(user)
+ middle_initial = user.middle_name[0]
+ reps = Veteran::Service::Representative.all_for_user(first_name: user.first_name,
+ last_name: user.last_name,
+ middle_initial:)
+
+ if reps.blank? || reps.count > 1
+ reps = Veteran::Service::Representative.all_for_user(first_name: user.first_name,
+ last_name: user.last_name,
+ poa_code: current_poa_code)
+ end
+ reps
+ end
end
end
diff --git a/lib/va_profile/profile/v3/health_benefit_bio_response.rb b/lib/va_profile/profile/v3/health_benefit_bio_response.rb
index 1b333021400..93c7e8a4df7 100644
--- a/lib/va_profile/profile/v3/health_benefit_bio_response.rb
+++ b/lib/va_profile/profile/v3/health_benefit_bio_response.rb
@@ -8,28 +8,35 @@ module VAProfile
module Profile
module V3
class HealthBenefitBioResponse < VAProfile::Response
+ attribute :code, String
attribute :contacts, Array[VAProfile::Models::AssociatedPerson]
attribute :messages, Array[VAProfile::Models::Message]
attribute :va_profile_tx_audit_id, String
def initialize(response)
- body = response.body
+ body = response&.body
contacts = body.dig('profile', 'health_benefit', 'associated_persons')
&.select { |p| valid_contact_types.include?(p['contact_type']) }
&.sort_by { |p| valid_contact_types.index(p['contact_type']) }
messages = body['messages']
+ code = messages&.first&.dig('code')
va_profile_tx_audit_id = response.response_headers['vaprofiletxauditid']
- super(response.status, { contacts:, messages:, va_profile_tx_audit_id: })
+ super(response.status, { code:, contacts:, messages:, va_profile_tx_audit_id: })
end
def debug_data
{
+ code:,
status:,
message:,
va_profile_tx_audit_id:
}
end
+ def server_error?
+ status >= 500
+ end
+
private
def valid_contact_types
diff --git a/lib/va_profile/profile/v3/service.rb b/lib/va_profile/profile/v3/service.rb
index 8b06ccc5f59..e9369942c18 100644
--- a/lib/va_profile/profile/v3/service.rb
+++ b/lib/va_profile/profile/v3/service.rb
@@ -29,6 +29,8 @@ def get_health_benefit_bio
service_response = perform(:post, path, { bios: [{ bioPath: 'healthBenefit' }] })
response = VAProfile::Profile::V3::HealthBenefitBioResponse.new(service_response)
Sentry.set_extras(response.debug_data) unless response.ok?
+ code = response.code || 502
+ raise_backend_exception("VET360_#{code}", self.class) if response.server_error?
response
end
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client/definitions.rb b/modules/claims_api/app/clients/claims_api/bgs_client/definitions.rb
index 96421084068..603d0faea24 100644
--- a/modules/claims_api/app/clients/claims_api/bgs_client/definitions.rb
+++ b/modules/claims_api/app/clients/claims_api/bgs_client/definitions.rb
@@ -201,6 +201,15 @@ module PersonWebService
path: 'PersonWebService'
)
+ module FindDependentsByPtcpntId
+ DEFINITION =
+ Action.new(
+ service: PersonWebService::DEFINITION,
+ name: 'findDependentsByPtcpntId',
+ key: 'DependentDTO'
+ )
+ end
+
module FindPersonBySSN
DEFINITION =
Action.new(
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb b/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
index ba3b011c6bd..85aff8f0da8 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
@@ -131,11 +131,6 @@ def validate_form_526_change_of_address_zip
source: '/changeOfAddress/zipFirstFive',
detail: 'The zipFirstFive is required if the country is USA.'
)
- elsif address['country'] != 'USA' && address['internationalPostalCode'].blank?
- collect_error_messages(
- source: '/changeOfAddress/internationalPostalCode',
- detail: 'The internationalPostalCode is required if the country is not USA.'
- )
elsif address['country'] == 'USA' && address['internationalPostalCode'].present?
collect_error_messages(
source: '/changeOfAddress/internationalPostalCode',
@@ -198,11 +193,6 @@ def validate_form_526_current_mailing_address_zip
source: '/veteranIdentification/mailingAddress/zipFirstFive',
detail: 'The zipFirstFive is required if the country is USA.'
)
- elsif mailing_address['country'] != 'USA' && mailing_address['internationalPostalCode'].blank?
- collect_error_messages(
- source: '/veteranIdentification/mailingAddress/internationalPostalCode',
- detail: 'The internationalPostalCode is required if the country is not USA.'
- )
elsif mailing_address['country'] == 'USA' && mailing_address['internationalPostalCode'].present?
collect_error_messages(
source: '/veteranIdentification/mailingAddress/internationalPostalCode',
@@ -227,7 +217,7 @@ def validate_disability_name
disability_name = disability&.dig('name')
if disability_name.blank?
collect_error_messages(source: "/disabilities/#{idx}/name",
- detail: "The disability name is required for /disabilities/#{idx}/name")
+ detail: "The disability name (#{idx}) is required.")
end
end
end
@@ -243,9 +233,8 @@ def validate_form_526_disability_classification_code
validate_form_526_disability_code_enddate(disability['classificationCode'].to_i, idx)
else
collect_error_messages(source: "/disabilities/#{idx}/classificationCode",
- detail: 'The classificationCode must match an active code ' \
- 'returned from the /disabilities endpoint of the Benefits ' \
- 'Reference Data API.')
+ detail: "The classificationCode (#{idx}) must match an active code " \
+ 'returned from the /disabilities endpoint of the Benefits ')
end
end
end
@@ -279,7 +268,7 @@ def validate_form_526_disability_approximate_begin_date
next if date_is_valid_against_current_time_after_check_on_format?(approx_begin_date)
collect_error_messages(source: "disabilities/#{idx}/approximateDate",
- detail: 'The approximateDate is not valid.')
+ detail: "The approximateDate (#{idx}) is not valid.")
end
end
@@ -292,8 +281,8 @@ def validate_form_526_disability_service_relevance
service_relevance = disability&.dig('serviceRelevance')
if disability_action_type == 'NEW' && service_relevance.blank?
collect_error_messages(source: "disabilities/#{idx}/serviceRelevance",
- detail: 'The serviceRelevance is required if ' \
- "disabilityActionType' is NEW.")
+ detail: "The serviceRelevance (#{idx}) is required if " \
+ "'disabilityActionType' is NEW.")
end
end
end
@@ -307,12 +296,12 @@ def validate_special_issues
if disability['specialIssues'].include? 'POW'
if confinements.blank?
collect_error_messages(source: "disabilities/#{idx}/specialIssues",
- detail: 'serviceInformation.confinements is required if ' \
+ detail: "serviceInformation.confinements (#{idx}) is required if " \
'specialIssues includes POW.')
elsif disability_action_type == 'INCREASE'
collect_error_messages(source: "disabilities/#{idx}/specialIssues",
- detail: 'disabilityActionType cannot be INCREASE if ' \
- 'specialIssues includes POW.')
+ detail: "disabilityActionType (#{idx}) cannot be INCREASE if " \
+ 'specialIssues includes POW for.')
end
end
end
@@ -322,7 +311,7 @@ def validate_form_526_disability_secondary_disabilities # rubocop:disable Metric
form_attributes['disabilities'].each_with_index do |disability, dis_idx|
if disability['disabilityActionType'] == 'NONE' && disability['secondaryDisabilities'].blank?
collect_error_messages(source: "disabilities/#{dis_idx}/",
- detail: 'If the `disabilityActionType` is set to `NONE` ' \
+ detail: "If the `disabilityActionType` (#{dis_idx}) is set to `NONE` " \
'there must be a secondary disability present.')
end
next if disability['secondaryDisabilities'].blank?
@@ -370,7 +359,7 @@ def validate_form_526_disability_secondary_disability_classification_code(second
return if brd_classification_ids.include?(secondary_disability['classificationCode'].to_i)
collect_error_messages(source: "disabilities/#{dis_idx}/secondaryDisabilities/#{sd_idx}/classificationCode",
- detail: 'classificationCode must match an active code ' \
+ detail: "classificationCode (#{dis_idx}) must match an active code " \
'returned from the /disabilities endpoint of the Benefits Reference Data API.')
end
@@ -382,7 +371,7 @@ def validate_form_526_disability_secondary_disability_approximate_begin_date(sec
return if date_is_valid_against_current_time_after_check_on_format?(secondary_disability['approximateDate'])
collect_error_messages(source: "/disabilities/#{dis_idx}/secondaryDisability/#{sd_idx}/approximateDate",
- detail: 'approximateDate must be a date in the past.')
+ detail: "approximateDate (#{dis_idx}) must be a date in the past.")
end
def validate_form_526_veteran_homelessness # rubocop:disable Metrics/MethodLength
@@ -636,7 +625,7 @@ def validate_treatment_dates(treatments) # rubocop:disable Metrics/MethodLength
collect_error_messages(
source: "/treatments/#{idx}/beginDate",
- detail: 'Each treatment begin date must be after the first activeDutyBeginDate.'
+ detail: "Each treatment begin date (#{idx}) must be after the first activeDutyBeginDate"
)
end
end
@@ -706,14 +695,14 @@ def validate_service_periods(service_information, target_veteran)
def age_exception(idx)
collect_error_messages(
source: "/serviceInformation/servicePeriods/#{idx}/activeDutyBeginDate",
- detail: "Active Duty Begin Date cannot be on or before Veteran's thirteenth birthday."
+ detail: "Active Duty Begin Date (#{idx}) cannot be on or before Veteran's thirteenth birthday."
)
end
def begin_date_exception(idx)
collect_error_messages(
source: "/serviceInformation/servicePeriods/#{idx}/activeDutyEndDate",
- detail: 'activeDutyEndDate needs to be after activeDutyBeginDate'
+ detail: "activeDutyEndDate (#{idx}) needs to be after activeDutyBeginDate."
)
end
@@ -732,7 +721,6 @@ def validate_form_526_location_codes(service_information)
collect_error_messages(
detail: 'The Reference Data Service is unavailable to verify the separation location code for the claimant'
)
-
return
end
@@ -743,7 +731,7 @@ def validate_form_526_location_codes(service_information)
collect_error_messages(
source: "/serviceInformation/servicePeriods/#{idx}/separationLocationCode",
- detail: 'The separation location code for the claimant is not a valid value'
+ detail: "The separation location code (#{idx}) for the claimant is not a valid value."
)
end
end
@@ -775,7 +763,7 @@ def validate_confinements(service_information) # rubocop:disable Metrics/MethodL
if begin_date_is_after_end_date?(approximate_begin_date, approximate_end_date)
collect_error_messages(
source: "/confinements/#{idx}/",
- detail: 'Confinement approximate end date must be after approximate begin date.'
+ detail: "Confinement approximate end date (#{idx}) must be after approximate begin date."
)
end
@@ -791,7 +779,7 @@ def validate_confinements(service_information) # rubocop:disable Metrics/MethodL
approximate_begin_date)
collect_error_messages(
source: "/confinements/#{idx}/approximateBeginDate",
- detail: 'Confinement approximate begin date must be after earliest active duty begin date.'
+ detail: "Confinement approximate begin date (#{idx}) must be after earliest active duty begin date."
)
end
@@ -800,14 +788,14 @@ def validate_confinements(service_information) # rubocop:disable Metrics/MethodL
if overlapping_confinement_periods?(idx)
collect_error_messages(
source: "/confinements/#{idx}/approximateBeginDate",
- detail: 'Confinement periods may not overlap each other.'
+ detail: "Confinement periods (#{idx}) may not overlap each other."
)
end
unless confinement_dates_are_within_service_period?(approximate_begin_date, approximate_end_date,
service_periods)
collect_error_messages(
source: "/confinements/#{idx}",
- detail: 'Confinement dates must be within one of the service period dates.'
+ detail: "Confinement dates (#{idx}) must be within one of the service period dates."
)
end
end
@@ -880,9 +868,9 @@ def validate_service_branch_names(service_information)
unless downcase_branches.include?(sp['serviceBranch'].downcase)
collect_error_messages(
source: "/serviceInformation/servicePeriods/#{idx}/serviceBranch",
- detail: 'serviceBranch must match a service branch ' \
+ detail: "serviceBranch (#{idx}) must match a service branch " \
'returned from the /service-branches endpoint of the Benefits ' \
- 'Reference Data API.'
+ 'Reference Data API.' \
)
end
end
diff --git a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
index 38cbb9691dd..70df5321eff 100644
--- a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
+++ b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
@@ -2143,10 +2143,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -2252,10 +2253,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -3523,10 +3525,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -3632,10 +3635,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -5069,7 +5073,7 @@
"202 without a transactionId": {
"value": {
"data": {
- "id": "925698c0-acd8-4155-80ba-6aed534825da",
+ "id": "40167e4f-703f-48bb-9c06-eea907988788",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -5254,7 +5258,7 @@
},
"federalActivation": {
"activationDate": "2023-10-01",
- "anticipatedSeparationDate": "2024-08-31"
+ "anticipatedSeparationDate": "2024-09-07"
},
"confinements": [
{
@@ -5300,7 +5304,7 @@
"202 with a transactionId": {
"value": {
"data": {
- "id": "285b02b0-6a6b-4a57-ba90-77a7cfc01e0c",
+ "id": "917204da-61f3-4f0d-a2a5-e5a423b44c1a",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -5678,10 +5682,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -5787,10 +5792,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -7058,10 +7064,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -7167,10 +7174,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -9215,10 +9223,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -9324,10 +9333,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -10506,7 +10516,7 @@
"application/json": {
"example": {
"data": {
- "id": "4e010a53-2107-486a-b006-5e98b8394c15",
+ "id": "555d0e33-d296-465c-b054-ab459381dc3e",
"type": "forms/526",
"attributes": {
"claimProcessType": "STANDARD_CLAIM_PROCESS",
@@ -14166,8 +14176,8 @@
"id": "1",
"type": "intent_to_file",
"attributes": {
- "creationDate": "2024-08-29",
- "expirationDate": "2025-08-29",
+ "creationDate": "2024-09-05",
+ "expirationDate": "2025-09-05",
"type": "compensation",
"status": "active"
}
@@ -15063,7 +15073,7 @@
"application/json": {
"example": {
"data": {
- "id": "fb3346c4-25c3-45fb-bb7c-99edeb99c5f5",
+ "id": "2042b892-b0c9-4551-8c90-55538b3d6324",
"type": "individual",
"attributes": {
"code": "067",
@@ -15756,7 +15766,7 @@
"application/json": {
"example": {
"data": {
- "id": "14b7e4ed-1537-479c-ab98-fb65eb97ef18",
+ "id": "378415a5-f585-44ac-9a66-f97891a7c116",
"type": "organization",
"attributes": {
"code": "083",
@@ -17707,10 +17717,10 @@
"application/json": {
"example": {
"data": {
- "id": "7ab0c254-3162-45fd-87dd-dc44d14cb2f9",
+ "id": "b17376c2-39a2-44a5-a4bd-3a9d2b1de18f",
"type": "claimsApiPowerOfAttorneys",
"attributes": {
- "dateRequestAccepted": "2024-08-29",
+ "dateRequestAccepted": "2024-09-05",
"previousPoa": null,
"representative": {
"serviceOrganization": {
diff --git a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
index 587d31af0d9..b45b19e464d 100644
--- a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
+++ b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
@@ -756,10 +756,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -865,10 +866,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -2136,10 +2138,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -2245,10 +2248,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -3682,7 +3686,7 @@
"202 without a transactionId": {
"value": {
"data": {
- "id": "0db55941-4811-4fd9-80cd-f743761a64ea",
+ "id": "f6c279e0-7e00-4c31-86d3-839c896e8865",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -3867,7 +3871,7 @@
},
"federalActivation": {
"activationDate": "2023-10-01",
- "anticipatedSeparationDate": "2024-08-31"
+ "anticipatedSeparationDate": "2024-09-07"
},
"confinements": [
{
@@ -3913,7 +3917,7 @@
"202 with a transactionId": {
"value": {
"data": {
- "id": "01a05245-ad48-47c0-b044-60bd1a1cf4d5",
+ "id": "d0634b7d-7919-4546-8730-da0b46241272",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -4291,10 +4295,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -4400,10 +4405,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -5671,10 +5677,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -5780,10 +5787,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -7828,10 +7836,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -7937,10 +7946,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": "string",
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
@@ -9119,7 +9129,7 @@
"application/json": {
"example": {
"data": {
- "id": "746039f5-8aa5-49b5-abea-5ed07ac16b35",
+ "id": "4f521b24-e79b-482f-b85b-7e80ca67701d",
"type": "forms/526",
"attributes": {
"claimProcessType": "STANDARD_CLAIM_PROCESS",
@@ -12779,8 +12789,8 @@
"id": "1",
"type": "intent_to_file",
"attributes": {
- "creationDate": "2024-08-29",
- "expirationDate": "2025-08-29",
+ "creationDate": "2024-09-05",
+ "expirationDate": "2025-09-05",
"type": "compensation",
"status": "active"
}
@@ -13676,7 +13686,7 @@
"application/json": {
"example": {
"data": {
- "id": "69b82793-223a-4d60-ab61-eb67ddb77fc6",
+ "id": "e3f67c59-3391-445d-9629-f215f8495484",
"type": "individual",
"attributes": {
"code": "067",
@@ -14369,7 +14379,7 @@
"application/json": {
"example": {
"data": {
- "id": "5cfd4d55-d0c5-4476-87c1-a1956c8dc02b",
+ "id": "7bdfdf32-45d7-4724-9e97-ad4762cc5642",
"type": "organization",
"attributes": {
"code": "083",
@@ -16320,10 +16330,10 @@
"application/json": {
"example": {
"data": {
- "id": "22563f3d-74c4-44b8-b2a0-0da7bfe639a4",
+ "id": "50a3a1dd-2ec2-40a3-9588-7cad8377b515",
"type": "claimsApiPowerOfAttorneys",
"attributes": {
- "dateRequestAccepted": "2024-08-29",
+ "dateRequestAccepted": "2024-09-05",
"previousPoa": null,
"representative": {
"serviceOrganization": {
diff --git a/modules/claims_api/config/schemas/v2/526.json b/modules/claims_api/config/schemas/v2/526.json
index 39f0449b2a6..b38bc007ddf 100644
--- a/modules/claims_api/config/schemas/v2/526.json
+++ b/modules/claims_api/config/schemas/v2/526.json
@@ -116,10 +116,11 @@
"nullable": true
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's current mailing address. Required if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's current mailing address. Do not include if 'country' is 'USA'.",
"type": ["string", "null"],
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
}
}
},
@@ -222,10 +223,11 @@
"example": "6789"
},
"internationalPostalCode": {
- "description": "International postal code for the Veteran's new address. Requried if 'country' is not 'USA'. Do not include if 'country' is 'USA'.",
+ "description": "International postal code for the Veteran's new address. Do not include if 'country' is 'USA'.",
"type": ["string", "null"],
- "maxLength": 100,
- "nullable": true
+ "maxLength": 16,
+ "nullable": true,
+ "pattern": "^[a-zA-Z0-9]*$"
},
"dates": {
"type": "object",
diff --git a/modules/claims_api/lib/bgs_service/person_web_service.rb b/modules/claims_api/lib/bgs_service/person_web_service.rb
new file mode 100644
index 00000000000..1fde82af939
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/person_web_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ class PersonWebService < ClaimsApi::LocalBGS
+ def bean_name
+ 'PersonWebServiceBean/PersonWebService'
+ end
+
+ def find_dependents_by_ptcpnt_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ptcpntId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']").first.content = v
+ end
+
+ make_request(endpoint: bean_name, action: 'findDependentsByPtcpntId', body:, key: 'DependentDTO')
+ end
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/person_web_service_spec.rb b/modules/claims_api/spec/lib/claims_api/person_web_service_spec.rb
new file mode 100644
index 00000000000..fbdaf09b89f
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/person_web_service_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'bgs_service/person_web_service'
+
+describe ClaimsApi::PersonWebService do
+ subject { described_class.new external_uid: 'xUid', external_key: 'xKey' }
+
+ describe '#find_dependents_by_ptcpnt_id' do
+ it 'responds as expected' do
+ VCR.use_cassette('claims_api/bgs/person_web_service/find_dependents_by_ptcpnt_id') do
+ # rubocop:disable Style/NumericLiterals
+ result = subject.find_dependents_by_ptcpnt_id(600052699)
+ # rubocop:enable Style/NumericLiterals
+ expect(result).to be_a Hash
+ expect(result[:dependent][:first_nm]).to eq 'MARGIE'
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/spec/requests/v2/veterans/526_spec.rb b/modules/claims_api/spec/requests/v2/veterans/526_spec.rb
index eeee5ae4794..4a517b713da 100644
--- a/modules/claims_api/spec/requests/v2/veterans/526_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/526_spec.rb
@@ -3107,7 +3107,7 @@ def update_json_and_submit(updated_json_lambda)
expect(response).to have_http_status(:unprocessable_entity)
response_body = JSON.parse(response.body)
expect(response_body['errors'][0]['detail']).to eq(
- "The serviceRelevance is required if disabilityActionType' is NEW."
+ "The serviceRelevance (0) is required if 'disabilityActionType' is NEW."
)
end
end
diff --git a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
index ef8d1115565..15489d9caae 100644
--- a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
+++ b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
@@ -169,26 +169,26 @@ def get_file_paths_and_metadata(parsed_form_data)
end
def upload_pdf(file_path, metadata, form)
- location, uuid = prepare_for_upload(form)
+ location, uuid = prepare_for_upload(form, file_path)
log_upload_details(location, uuid)
response = perform_pdf_upload(location, file_path, metadata)
[response.status, uuid]
end
- def prepare_for_upload(form)
+ def prepare_for_upload(form, file_path)
Rails.logger.info('Simple forms api - preparing to request upload location from Lighthouse',
form_id: get_form_id)
location, uuid = lighthouse_service.request_upload
- stamp_pdf_with_uuid(form, uuid)
+ stamp_pdf_with_uuid(form, uuid, file_path)
create_form_submission_attempt(uuid)
[location, uuid]
end
- def stamp_pdf_with_uuid(form, uuid)
+ def stamp_pdf_with_uuid(form, uuid, stamped_template_path)
# Stamp uuid on 40-10007
- pdf_stamper = SimpleFormsApi::PdfStamper.new(stamped_template_path: 'tmp/vba_40_10007-tmp.pdf', form:)
+ pdf_stamper = SimpleFormsApi::PdfStamper.new(stamped_template_path:, form:)
pdf_stamper.stamp_uuid(uuid)
end
diff --git a/modules/simple_forms_api/app/services/simple_forms_api/pdf_filler.rb b/modules/simple_forms_api/app/services/simple_forms_api/pdf_filler.rb
index feb4ef30c57..59dc4d0f7ba 100644
--- a/modules/simple_forms_api/app/services/simple_forms_api/pdf_filler.rb
+++ b/modules/simple_forms_api/app/services/simple_forms_api/pdf_filler.rb
@@ -31,8 +31,8 @@ def generate(current_loa = nil, timestamp: Time.current)
private
def prepare_to_generate_pdf
- generated_form_path = Rails.root.join("tmp/#{name}-tmp.pdf").to_s
- stamped_template_path = Rails.root.join("tmp/#{name}-stamped.pdf").to_s
+ generated_form_path = Rails.root.join("tmp/#{name}-#{SecureRandom.hex}-tmp.pdf").to_s
+ stamped_template_path = Rails.root.join("tmp/#{name}-#{SecureRandom.hex}-stamped.pdf").to_s
copy_from_tempfile(stamped_template_path)
diff --git a/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb b/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb
index dc7a0277fba..1f52ed924ca 100644
--- a/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb
+++ b/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb
@@ -44,7 +44,7 @@ def get_upload_location_and_uuid(lighthouse_service, form)
# Stamp uuid on 40-10007
uuid = upload_location.dig('data', 'id')
- SimpleFormsApi::PdfStamper.new(stamped_template_path: 'tmp/vba_40_10007-tmp.pdf', form:).stamp_uuid(uuid)
+ SimpleFormsApi::PdfStamper.new(stamped_template_path: file_path, form:).stamp_uuid(uuid)
{ uuid:, location: upload_location.dig('data', 'attributes', 'location') }
end
diff --git a/modules/simple_forms_api/spec/services/pdf_filler_spec.rb b/modules/simple_forms_api/spec/services/pdf_filler_spec.rb
index 127dffe348b..48d9d69fb0e 100644
--- a/modules/simple_forms_api/spec/services/pdf_filler_spec.rb
+++ b/modules/simple_forms_api/spec/services/pdf_filler_spec.rb
@@ -36,12 +36,14 @@
forms.each do |file_name|
context "when mapping the pdf data given JSON file: #{file_name}" do
let(:form_number) { file_name.gsub('-min', '') }
- let(:expected_pdf_path) { Rails.root.join("tmp/#{name}-tmp.pdf") }
- let(:expected_stamped_path) { Rails.root.join("tmp/#{name}-stamped.pdf") }
+ let(:pseudorandom_value) { 'abc123' }
+ let(:expected_pdf_path) { Rails.root.join("tmp/#{name}-#{pseudorandom_value}-tmp.pdf") }
+ let(:expected_stamped_path) { Rails.root.join("tmp/#{name}-#{pseudorandom_value}-stamped.pdf") }
let(:data) { JSON.parse(File.read("modules/simple_forms_api/spec/fixtures/form_json/#{file_name}.json")) }
let(:form) { "SimpleFormsApi::#{form_number.titleize.gsub(' ', '')}".constantize.new(data) }
let(:name) { SecureRandom.hex }
+ before { allow(SecureRandom).to receive(:hex).and_return(pseudorandom_value) }
after { FileUtils.rm_f(expected_pdf_path) }
context 'when a legitimate JSON payload is provided' do
diff --git a/modules/travel_pay/app/services/travel_pay/client.rb b/modules/travel_pay/app/services/travel_pay/client.rb
index 16b45ff8f32..1ed7dd6157a 100644
--- a/modules/travel_pay/app/services/travel_pay/client.rb
+++ b/modules/travel_pay/app/services/travel_pay/client.rb
@@ -141,29 +141,6 @@ def veis_params
}
end
- def request_ping(veis_token)
- btsss_url = Settings.travel_pay.base_url
- api_key = Settings.travel_pay.subscription_key
-
- connection(server_url: btsss_url).get('api/v1/Sample/ping') do |req|
- req.headers['Authorization'] = "Bearer #{veis_token}"
- req.headers['Ocp-Apim-Subscription-Key'] = api_key
- req.headers['X-Correlation-ID'] = SecureRandom.uuid
- end
- end
-
- def request_authorized_ping(veis_token, btsss_token)
- btsss_url = Settings.travel_pay.base_url
- api_key = Settings.travel_pay.subscription_key
-
- connection(server_url: btsss_url).get('api/v1/Sample/authorized-ping') do |req|
- req.headers['Authorization'] = "Bearer #{veis_token}"
- req.headers['BTSSS-Access-Token'] = btsss_token
- req.headers['Ocp-Apim-Subscription-Key'] = api_key
- req.headers['X-Correlation-ID'] = SecureRandom.uuid
- end
- end
-
def request_claims(veis_token, btsss_token)
btsss_url = Settings.travel_pay.base_url
correlation_id = SecureRandom.uuid
diff --git a/modules/vye/app/models/concerns/vye/needs_enrollment_verification.rb b/modules/vye/app/models/concerns/vye/needs_enrollment_verification.rb
index b6e1bf31dad..cad0186f569 100644
--- a/modules/vye/app/models/concerns/vye/needs_enrollment_verification.rb
+++ b/modules/vye/app/models/concerns/vye/needs_enrollment_verification.rb
@@ -338,7 +338,7 @@ def eval_case8
def eval_case9
return if dlc_before_ldpm? || date_last_certified.blank?
return unless aed_before_today?
- return if are_dates_the_same(@award.award_begin_date, @award.award_end_date)
+ return unless are_dates_the_same(@award.award_begin_date, @award.award_end_date)
push_enrollment(
award_id: @award.id,
diff --git a/modules/vye/spec/models/vye/user_info_spec.rb b/modules/vye/spec/models/vye/user_info_spec.rb
index 1c5d990292a..2322b74b93d 100644
--- a/modules/vye/spec/models/vye/user_info_spec.rb
+++ b/modules/vye/spec/models/vye/user_info_spec.rb
@@ -298,7 +298,7 @@
let!(:award) do
cur_award_ind = Vye::Award.cur_award_inds[:future]
award_begin_date = Date.parse('2024-07-01')
- award_end_date = Date.parse('2024-07-18')
+ award_end_date = Date.parse('2024-07-01')
FactoryBot.create(:vye_award, user_info:, award_begin_date:, award_end_date:, cur_award_ind:)
end
diff --git a/spec/controllers/v0/profile/contacts_controller_spec.rb b/spec/controllers/v0/profile/contacts_controller_spec.rb
index 3dcc9725b4f..0a3ffd2b684 100644
--- a/spec/controllers/v0/profile/contacts_controller_spec.rb
+++ b/spec/controllers/v0/profile/contacts_controller_spec.rb
@@ -8,14 +8,13 @@
let(:idme_uuid) { 'dd681e7d6dea41ad8b80f8d39284ef29' }
let(:user) { build(:user, :loa3, idme_uuid:) }
let(:loa1_user) { build(:user, :loa1) }
+ let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_200' }
describe 'GET /v0/profile/contacts' do
subject { get :index }
around do |ex|
- VCR.use_cassette('va_profile/profile/v3/health_benefit_bio_200') do
- ex.run
- end
+ VCR.use_cassette(cassette) { ex.run }
end
context 'successful request' do
@@ -38,5 +37,26 @@
expect(subject).to have_http_status(:forbidden)
end
end
+
+ context '500 Internal Server Error from VA Profile Service' do
+ let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' }
+ let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' }
+
+ it 'returns a bad request status code' do
+ sign_in_as user
+ expect(subject).to have_http_status(:bad_request)
+ end
+ end
+
+ context '504 Gateway Timeout from VA Profile Service' do
+ let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' }
+ let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' }
+
+ it 'returns a gateway timeout status code' do
+ allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError)
+ sign_in_as user
+ expect(subject).to have_http_status(:gateway_timeout)
+ end
+ end
end
end
diff --git a/spec/lib/va_profile/profile/v3/service_spec.rb b/spec/lib/va_profile/profile/v3/service_spec.rb
index cd37ee8998d..ec9d0ce4f86 100644
--- a/spec/lib/va_profile/profile/v3/service_spec.rb
+++ b/spec/lib/va_profile/profile/v3/service_spec.rb
@@ -60,8 +60,10 @@
let(:va_profile_tx_audit_id) do
cassette_data['http_interactions'][0]['response']['headers']['Vaprofiletxauditid'][0]
end
+ let(:code) { 'MVI203' }
let(:debug_data) do
{
+ code:,
status:,
message:,
va_profile_tx_audit_id:
@@ -96,6 +98,7 @@
let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_404' }
let(:status) { 404 }
let(:message) { 'MVI201 MviNotFound The person with the identifier requested was not found in MVI.' }
+ let(:code) { 'MVI201' }
it 'includes messages received from the api' do
response = subject.get_health_benefit_bio
@@ -113,23 +116,9 @@
context '500 response' do
let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' }
let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' }
- let(:status) { 500 }
- let(:message) do
- result = 'MVI203 MviResponseError MVI returned acknowledgement error code '
- result += 'AE with error detail: More Than One Active Correlation Exists'
- result
- end
-
- it 'includes messages recieved from the api' do
- response = subject.get_health_benefit_bio
- expect(response.status).to eq(500)
- expect(response.contacts.size).to eq(0)
- expect(response.messages.size).to eq(1)
- end
- it 'calls Sentry.set_extras' do
- expect(Sentry).to receive(:set_extras).once.with(debug_data)
- subject.get_health_benefit_bio
+ it 'raises an error' do
+ expect { subject.get_health_benefit_bio }.to raise_error(Common::Exceptions::BackendServiceException)
end
end
diff --git a/spec/requests/v0/profile/contacts_spec.rb b/spec/requests/v0/profile/contacts_spec.rb
index d61e2d0f5cc..a9bd4edc179 100644
--- a/spec/requests/v0/profile/contacts_spec.rb
+++ b/spec/requests/v0/profile/contacts_spec.rb
@@ -57,14 +57,14 @@
end
end
- context '500 response' do
+ context '500 response from VA Profile' do
let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' }
let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' }
- it 'responds with 500 status' do
+ it 'responds with 400 status (excluding 5xx response from SLO)' do
sign_in_as(user)
get '/v0/profile/contacts'
- expect(response).to have_http_status(:internal_server_error)
+ expect(response).to have_http_status(:bad_request)
end
end
end
diff --git a/spec/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email_spec.rb b/spec/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email_spec.rb
new file mode 100644
index 00000000000..ae5368ae944
--- /dev/null
+++ b/spec/sidekiq/evss/disability_compensation_form/form0781_document_upload_failure_email_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail, type: :job do
+ subject { described_class }
+
+ let!(:form526_submission) { create(:form526_submission) }
+ let(:notification_client) { double('Notifications::Client') }
+ let(:formatted_submit_date) do
+ # We display dates in mailers in the format "May 1, 2024 3:01 p.m. EDT"
+ form526_submission.created_at.strftime('%B %-d, %Y %-l:%M %P %Z').sub(/([ap])m/, '\1.m.')
+ end
+
+ before do
+ Sidekiq::Job.clear_all
+ allow(Notifications::Client).to receive(:new).and_return(notification_client)
+ end
+
+ describe '#perform' do
+ it 'dispatches a failure notification email' do
+ expect(notification_client).to receive(:send_email).with(
+ # Email address and first_name are from our User fixtures
+ # form0781_upload_failure_notification_template_id is a placeholder in settings.yml
+ {
+ email_address: 'test@email.com',
+ template_id: 'form0781_upload_failure_notification_template_id',
+ personalisation: {
+ first_name: 'BEYONCE',
+ date_submitted: formatted_submit_date
+ }
+ }
+ )
+
+ subject.perform_async(form526_submission.id)
+ subject.drain
+ end
+ end
+
+ describe 'logging' do
+ it 'increments a Statsd metric' do
+ allow(notification_client).to receive(:send_email)
+
+ expect do
+ subject.perform_async(form526_submission.id)
+ subject.drain
+ end.to trigger_statsd_increment(
+ 'api.form_526.veteran_notifications.form0781_upload_failure_email.success'
+ )
+ end
+
+ it 'creates a Form526JobStatus' do
+ allow(notification_client).to receive(:send_email)
+
+ expect do
+ subject.perform_async(form526_submission.id)
+ subject.drain
+ end.to change(Form526JobStatus, :count).by(1)
+ end
+
+ context 'when an error throws when sending an email' do
+ before do
+ allow_any_instance_of(VaNotify::Service).to receive(:send_email).and_raise(Common::Client::Errors::ClientError)
+ end
+
+ it 'passes the error to the included JobTracker retryable_error_handler and re-raises the error' do
+ # Sidekiq::Form526JobStatusTracker::JobTracker is included in this job's inheritance hierarchy
+ expect_any_instance_of(
+ Sidekiq::Form526JobStatusTracker::JobTracker
+ ).to receive(:retryable_error_handler).with(an_instance_of(Common::Client::Errors::ClientError))
+
+ expect do
+ subject.perform_async(form526_submission.id)
+ subject.drain
+ end.to raise_error(Common::Client::Errors::ClientError)
+ end
+ end
+ end
+
+ context 'when retries are exhausted' do
+ let!(:form526_job_status) { create(:form526_job_status, :retryable_error, form526_submission:, job_id: 123) }
+ let(:retry_params) do
+ {
+ 'jid' => 123,
+ 'error_class' => 'JennyNotFound',
+ 'error_message' => 'I tried to call you before but I lost my nerve',
+ 'args' => [form526_submission.id]
+ }
+ end
+
+ let(:exhaustion_time) { DateTime.new(1985, 10, 26).utc }
+
+ before do
+ allow(notification_client).to receive(:send_email)
+ end
+
+ it 'increments a StatsD exhaustion metric, logs to the Rails logger and updates the job status' do
+ Timecop.freeze(exhaustion_time) do
+ described_class.within_sidekiq_retries_exhausted_block(retry_params) do
+ expect(Rails.logger).to receive(:warn).with(
+ 'Form0781DocumentUploadFailureEmail retries exhausted',
+ {
+ job_id: 123,
+ error_class: 'JennyNotFound',
+ error_message: 'I tried to call you before but I lost my nerve',
+ timestamp: exhaustion_time,
+ form526_submission_id: form526_submission.id
+ }
+ ).and_call_original
+ expect(StatsD).to receive(:increment).with(
+ 'api.form_526.veteran_notifications.form0781_upload_failure_email.exhausted'
+ )
+ end
+
+ expect(form526_job_status.reload.status).to eq(Form526JobStatus::STATUS[:exhausted])
+ end
+ end
+ end
+end
diff --git a/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb b/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb
index 65886c81c76..9b0367f4b45 100644
--- a/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb
+++ b/spec/sidekiq/evss/disability_compensation_form/submit_form0781_spec.rb
@@ -88,12 +88,53 @@
it 'updates a StatsD counter and updates the status on an exhaustion event' do
subject.within_sidekiq_retries_exhausted_block({ 'jid' => form526_job_status.job_id }) do
+ # Will receieve increment for failure mailer metric
+ allow(StatsD).to receive(:increment).with(
+ 'shared.sidekiq.default.EVSS_DisabilityCompensationForm_Form0781DocumentUploadFailureEmail.enqueue'
+ )
+
expect(StatsD).to receive(:increment).with("#{subject::STATSD_KEY_PREFIX}.exhausted")
expect(Rails).to receive(:logger).and_call_original
end
form526_job_status.reload
expect(form526_job_status.status).to eq(Form526JobStatus::STATUS[:exhausted])
end
+
+ context 'when the form526_send_0781_failure_notification Flipper is enabled' do
+ before do
+ Flipper.enable(:form526_send_0781_failure_notification)
+ end
+
+ it 'enqueues a failure notification mailer to send to the veteran' do
+ subject.within_sidekiq_retries_exhausted_block(
+ {
+ 'jid' => form526_job_status.job_id,
+ 'args' => [form526_submission.id]
+ }
+ ) do
+ expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail)
+ .to receive(:perform_async).with(form526_submission.id)
+ end
+ end
+ end
+
+ context 'when the form526_send_0781_failure_notification Flipper is disabled' do
+ before do
+ Flipper.disable(:form526_send_0781_failure_notification)
+ end
+
+ it 'does not enqueue a failure notification mailer to send to the veteran' do
+ subject.within_sidekiq_retries_exhausted_block(
+ {
+ 'jid' => form526_job_status.job_id,
+ 'args' => [form526_submission.id]
+ }
+ ) do
+ expect(EVSS::DisabilityCompensationForm::Form0781DocumentUploadFailureEmail)
+ .not_to receive(:perform_async)
+ end
+ end
+ end
end
end
end
diff --git a/spec/support/vcr_cassettes/claims_api/bgs/person_web_service/find_dependents_by_ptcpnt_id.yml b/spec/support/vcr_cassettes/claims_api/bgs/person_web_service/find_dependents_by_ptcpnt_id.yml
new file mode 100644
index 00000000000..f76a1348046
--- /dev/null
+++ b/spec/support/vcr_cassettes/claims_api/bgs/person_web_service/find_dependents_by_ptcpnt_id.yml
@@ -0,0 +1,70 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: "/PersonWebServiceBean/PersonWebService"
+ body:
+ encoding: UTF-8
+ string: |
+
+
+
+
+
+ VAgovAPI
+
+
+ 127.0.0.1
+ 281
+ VAgovAPI
+ xUid
+ xKey
+
+
+
+
+
+ 600052699
+
+
+
+ headers:
+ User-Agent:
+ - ""
+ Content-Type:
+ - text/xml;charset=UTF-8
+ Host:
+ - ".vba.va.gov"
+ Soapaction:
+ - '"findDependentsByPtcpntId"'
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - "*/*"
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Date:
+ - Tue, 03 Sep 2024 17:07:05 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: rO0ABXdUAB13ZWJsb2dpYy5hcHAuQ29ycG9yYXRlRGF0YUVBUgAAANYAAAAjd2VibG9naWMud29ya2FyZWEuU3RyaW5nV29ya0NvbnRleHQABjMuMy40MgAAN2013-03-21T06:33:24-05:00U1953-02-11T00:00:00-06:002022-08-29T00:00:00-05:00796163672MARGIEFCURTISTOKYO
+ TOWERSHIBAKOEN4
+ CHOME-2-8TOKYOAfghanistan2024-08-13T15:12:55-05:00Mailing932Unknown70312022-09-22T14:36:01-05:003397136Daytime57112022-09-22T14:36:01-05:005276860NighttimeN600052700Spouse7961636720Y1
+ recorded_at: Tue, 03 Sep 2024 17:07:06 GMT
+recorded_with: VCR 6.3.1